@livestore/common 0.5.0-dev.0 → 0.5.0-dev.1
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.map +1 -1
- package/dist/MaterializationJournal.d.ts +2 -2
- package/dist/MaterializationJournal.d.ts.map +1 -1
- package/dist/MaterializationJournal.js +7 -7
- package/dist/StateHead.d.ts +1 -1
- package/dist/StateHead.d.ts.map +1 -1
- package/dist/StateHead.js +4 -4
- package/dist/WorkerTransportError.d.ts.map +1 -1
- package/dist/__tests__/fixture.d.ts +7 -7
- package/dist/__tests__/fixture.js +2 -2
- package/dist/adapter-types.d.ts +4 -0
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js +4 -4
- package/dist/bounded-collections.d.ts.map +1 -1
- package/dist/bounded-collections.js.map +1 -1
- package/dist/debug-info.d.ts.map +1 -1
- package/dist/debug-info.js +9 -8
- package/dist/debug-info.js.map +1 -1
- package/dist/defs.d.ts +4 -4
- package/dist/defs.js +2 -2
- package/dist/devtools/devtools-compatibility.test.js +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +4 -4
- package/dist/devtools/devtools-messages-client-session.js +4 -4
- package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-common.js +1 -1
- package/dist/devtools/devtools-messages-leader.d.ts +9 -9
- package/dist/devtools/devtools-messages-leader.js +4 -4
- package/dist/devtools/devtools-messages.js +3 -3
- package/dist/devtools/devtools-sessioninfo.d.ts.map +1 -1
- package/dist/devtools/mod.d.ts.map +1 -1
- package/dist/devtools/mod.js +3 -3
- package/dist/errors.d.ts +4 -4
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.js +22 -22
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +27 -6
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +65 -55
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/RejectedPushError.d.ts +9 -9
- package/dist/leader-thread/RejectedPushError.d.ts.map +1 -1
- package/dist/leader-thread/RejectedPushError.js +1 -1
- package/dist/leader-thread/connection.d.ts.map +1 -1
- package/dist/leader-thread/connection.js +2 -2
- package/dist/leader-thread/eventlog.d.ts +11 -1
- package/dist/leader-thread/eventlog.d.ts.map +1 -1
- package/dist/leader-thread/eventlog.js +13 -13
- 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 +4 -4
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +6 -0
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +43 -29
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.test.js +2 -2
- package/dist/leader-thread/materialize-event.d.ts.map +1 -1
- package/dist/leader-thread/materialize-event.js +9 -9
- package/dist/leader-thread/mod.js +9 -9
- package/dist/leader-thread/recreate-db.d.ts +5 -1
- package/dist/leader-thread/recreate-db.d.ts.map +1 -1
- package/dist/leader-thread/recreate-db.js +21 -25
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/leader-thread/shutdown-channel.js +1 -1
- package/dist/leader-thread/stream-events.d.ts.map +1 -1
- package/dist/leader-thread/stream-events.js +3 -5
- package/dist/leader-thread/stream-events.js.map +1 -1
- package/dist/leader-thread/types.d.ts +6 -4
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/leader-thread/types.js +5 -1
- package/dist/leader-thread/types.js.map +1 -1
- package/dist/make-client-session.d.ts +1 -1
- package/dist/make-client-session.d.ts.map +1 -1
- package/dist/make-client-session.js +2 -2
- package/dist/materializer-helper.d.ts.map +1 -1
- package/dist/materializer-helper.js +4 -4
- package/dist/materializer-helper.js.map +1 -1
- package/dist/otel.d.ts.map +1 -1
- package/dist/rematerialize-from-eventlog.d.ts +3 -1
- package/dist/rematerialize-from-eventlog.d.ts.map +1 -1
- package/dist/rematerialize-from-eventlog.js +16 -9
- package/dist/rematerialize-from-eventlog.js.map +1 -1
- package/dist/schema/EventDef/define.d.ts +2 -2
- package/dist/schema/EventDef/define.d.ts.map +1 -1
- package/dist/schema/EventDef/define.test.js +1 -1
- package/dist/schema/EventDef/deprecated.d.ts +1 -1
- package/dist/schema/EventDef/deprecated.d.ts.map +1 -1
- package/dist/schema/EventDef/deprecated.test.js +2 -2
- package/dist/schema/EventDef/facts.d.ts.map +1 -1
- package/dist/schema/EventDef/materializer.d.ts +2 -2
- package/dist/schema/EventDef/materializer.d.ts.map +1 -1
- package/dist/schema/EventDef/mod.js +5 -5
- package/dist/schema/EventSequenceNumber/client.d.ts +3 -8
- package/dist/schema/EventSequenceNumber/client.d.ts.map +1 -1
- package/dist/schema/EventSequenceNumber/client.js +12 -15
- package/dist/schema/EventSequenceNumber/client.js.map +1 -1
- package/dist/schema/EventSequenceNumber/mod.js +2 -2
- package/dist/schema/EventSequenceNumber.test.js +1 -1
- package/dist/schema/LiveStoreEvent/client.d.ts +8 -8
- package/dist/schema/LiveStoreEvent/client.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent/client.js +1 -1
- package/dist/schema/LiveStoreEvent/client.js.map +1 -1
- package/dist/schema/LiveStoreEvent/client.test.js +2 -2
- package/dist/schema/LiveStoreEvent/global.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent/global.js +1 -1
- package/dist/schema/LiveStoreEvent/input.d.ts +1 -1
- package/dist/schema/LiveStoreEvent/input.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent/mod.js +4 -4
- package/dist/schema/events.js +1 -1
- package/dist/schema/mod.js +11 -11
- package/dist/schema/schema.d.ts +1 -1
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js +3 -3
- package/dist/schema/state/mod.js +2 -2
- package/dist/schema/state/sqlite/client-document-def.d.ts +1 -1
- package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.js +8 -8
- package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
- package/dist/schema/state/sqlite/client-document-def.test.js +24 -10
- 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.test.js +1 -1
- package/dist/schema/state/sqlite/column-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/column-def.js +17 -3
- package/dist/schema/state/sqlite/column-def.js.map +1 -1
- package/dist/schema/state/sqlite/column-def.test.js +60 -5
- 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 +1 -1
- package/dist/schema/state/sqlite/column-spec.test.js +2 -2
- package/dist/schema/state/sqlite/column-spec.test.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/ast/fingerprint-digest.d.ts +3 -0
- package/dist/schema/state/sqlite/db-schema/ast/fingerprint-digest.d.ts.map +1 -0
- package/dist/schema/state/sqlite/db-schema/ast/fingerprint-digest.js +5 -0
- package/dist/schema/state/sqlite/db-schema/ast/fingerprint-digest.js.map +1 -0
- package/dist/schema/state/sqlite/db-schema/ast/fingerprint.d.ts +5 -0
- package/dist/schema/state/sqlite/db-schema/ast/fingerprint.d.ts.map +1 -0
- package/dist/schema/state/sqlite/db-schema/ast/fingerprint.js +183 -0
- package/dist/schema/state/sqlite/db-schema/ast/fingerprint.js.map +1 -0
- package/dist/schema/state/sqlite/db-schema/ast/fingerprint.test.d.ts +2 -0
- package/dist/schema/state/sqlite/db-schema/ast/fingerprint.test.d.ts.map +1 -0
- package/dist/schema/state/sqlite/db-schema/ast/fingerprint.test.js +142 -0
- package/dist/schema/state/sqlite/db-schema/ast/fingerprint.test.js.map +1 -0
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +1 -6
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -1
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +1 -82
- package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/ast/validate.d.ts.map +1 -1
- 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 +2 -1
- 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/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/has-json-string-encoding.d.ts +3 -0
- package/dist/schema/state/sqlite/db-schema/has-json-string-encoding.d.ts.map +1 -0
- package/dist/schema/state/sqlite/db-schema/has-json-string-encoding.js +6 -0
- package/dist/schema/state/sqlite/db-schema/has-json-string-encoding.js.map +1 -0
- package/dist/schema/state/sqlite/db-schema/mod.js +2 -2
- package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/mod.js +10 -9
- package/dist/schema/state/sqlite/mod.js.map +1 -1
- package/dist/schema/state/sqlite/persistence.d.ts.map +1 -1
- 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 +2 -7
- package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.d.ts +1 -3
- package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.js +2 -2
- package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.test.js +2 -2
- package/dist/schema/state/sqlite/query-builder/mod.js +2 -2
- package/dist/schema/state/sqlite/schema-helpers.d.ts.map +1 -1
- package/dist/schema/state/sqlite/schema-helpers.js +1 -1
- package/dist/schema/state/sqlite/schema-helpers.test.js +2 -2
- package/dist/schema/state/sqlite/system-tables/eventlog-tables.js +3 -3
- package/dist/schema/state/sqlite/system-tables/mod.js +2 -2
- package/dist/schema/state/sqlite/system-tables/state-tables.d.ts +60 -17
- package/dist/schema/state/sqlite/system-tables/state-tables.d.ts.map +1 -1
- package/dist/schema/state/sqlite/system-tables/state-tables.js +14 -5
- package/dist/schema/state/sqlite/system-tables/state-tables.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.d.ts +1 -1
- package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
- package/dist/schema/state/sqlite/table-def.js +24 -8
- package/dist/schema/state/sqlite/table-def.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.test.js +54 -1
- 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 +1 -1
- package/dist/schema/unknown-events.js.map +1 -1
- package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js +26 -4
- package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js.map +1 -1
- package/dist/schema-management/common.d.ts.map +1 -1
- package/dist/schema-management/common.js +2 -2
- package/dist/schema-management/migrations.d.ts +4 -4
- package/dist/schema-management/migrations.d.ts.map +1 -1
- package/dist/schema-management/migrations.js +9 -9
- package/dist/schema-management/migrations.js.map +1 -1
- package/dist/schema-management/validate-schema.d.ts.map +1 -1
- package/dist/schema-management/validate-schema.js +1 -1
- package/dist/session-id-symbol.d.ts.map +1 -1
- package/dist/sql-queries/index.js +3 -3
- package/dist/sql-queries/sql-queries.d.ts.map +1 -1
- package/dist/sql-queries/sql-queries.js +3 -3
- 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 +1 -1
- package/dist/sql-queries/types.d.ts.map +1 -1
- package/dist/sqlite-db-helper.d.ts +2 -2
- package/dist/sqlite-db-helper.d.ts.map +1 -1
- package/dist/sqlite-db-helper.js +2 -2
- package/dist/sync/ClientSessionSyncProcessor.d.ts +3 -3
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +7 -7
- package/dist/sync/errors.js +1 -1
- package/dist/sync/index.js +6 -6
- package/dist/sync/mock-sync-backend.d.ts.map +1 -1
- package/dist/sync/mock-sync-backend.js +4 -4
- package/dist/sync/mock-sync-backend.test.js +2 -2
- package/dist/sync/next/compact-events.d.ts.map +1 -1
- package/dist/sync/next/compact-events.js +3 -3
- package/dist/sync/next/facts.d.ts.map +1 -1
- package/dist/sync/next/facts.js +1 -1
- package/dist/sync/next/history-dag-common.d.ts +1 -1
- package/dist/sync/next/history-dag-common.d.ts.map +1 -1
- package/dist/sync/next/history-dag-common.js +1 -1
- package/dist/sync/next/history-dag.d.ts.map +1 -1
- package/dist/sync/next/history-dag.js +3 -3
- package/dist/sync/next/mod.js +5 -5
- package/dist/sync/next/rebase-events.d.ts.map +1 -1
- package/dist/sync/next/rebase-events.js +2 -2
- package/dist/sync/next/test/compact-events.calculator.test.js +4 -4
- package/dist/sync/next/test/compact-events.test.js +4 -4
- package/dist/sync/next/test/event-fixtures.d.ts +1 -1
- package/dist/sync/next/test/event-fixtures.d.ts.map +1 -1
- package/dist/sync/next/test/event-fixtures.js +5 -5
- package/dist/sync/next/test/history-dag-common.test.js +2 -2
- package/dist/sync/next/test/mod.js +1 -1
- package/dist/sync/sync-backend-kv.js +1 -1
- package/dist/sync/sync-backend.d.ts.map +1 -1
- package/dist/sync/sync-backend.js +1 -1
- package/dist/sync/sync.js +2 -2
- package/dist/sync/syncstate.d.ts +3 -3
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +2 -2
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +3 -3
- package/dist/sync/transport-chunking.d.ts.map +1 -1
- package/dist/sync/validate-push-payload.d.ts.map +1 -1
- package/dist/sync/validate-push-payload.js +2 -2
- package/dist/testing/event-factory.d.ts.map +1 -1
- package/dist/testing/event-factory.js +2 -2
- package/dist/testing/event-factory.js.map +1 -1
- package/dist/testing/mod.js +1 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +1 -0
- 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 +40 -39
- package/src/adapter-types.ts +4 -0
- package/src/debug-info.ts +11 -8
- package/src/defs.ts +2 -2
- package/src/leader-thread/LeaderSyncProcessor.ts +121 -97
- package/src/leader-thread/eventlog.ts +13 -5
- package/src/leader-thread/leader-worker-devtools.ts +1 -1
- package/src/leader-thread/make-leader-thread-layer.ts +49 -23
- package/src/leader-thread/recreate-db.ts +26 -25
- package/src/leader-thread/stream-events.ts +1 -3
- package/src/leader-thread/types.ts +5 -0
- package/src/materializer-helper.ts +1 -1
- package/src/rematerialize-from-eventlog.ts +28 -13
- package/src/schema/EventSequenceNumber/client.ts +12 -15
- package/src/schema/schema.ts +1 -1
- package/src/schema/state/sqlite/client-document-def.test.ts +23 -7
- package/src/schema/state/sqlite/client-document-def.ts +2 -2
- package/src/schema/state/sqlite/column-def.test.ts +84 -3
- package/src/schema/state/sqlite/column-def.ts +16 -1
- package/src/schema/state/sqlite/db-schema/ast/fingerprint-digest.ts +6 -0
- package/src/schema/state/sqlite/db-schema/ast/fingerprint.test.ts +209 -0
- package/src/schema/state/sqlite/db-schema/ast/fingerprint.ts +225 -0
- package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +1 -90
- package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +1 -0
- package/src/schema/state/sqlite/db-schema/has-json-string-encoding.ts +9 -0
- package/src/schema/state/sqlite/mod.ts +2 -1
- package/src/schema/state/sqlite/query-builder/astToSql.ts +1 -8
- package/src/schema/state/sqlite/system-tables/state-tables.ts +13 -2
- package/src/schema/state/sqlite/table-def.test.ts +66 -0
- package/src/schema/state/sqlite/table-def.ts +21 -5
- package/src/schema-management/__tests__/migrations-autoincrement-quoting.test.ts +33 -2
- package/src/schema-management/migrations.ts +7 -6
- package/src/sql-queries/sql-queries.ts +1 -1
- package/src/util.ts +1 -0
- package/dist/schema/state/sqlite/db-schema/hash.d.ts +0 -2
- package/dist/schema/state/sqlite/db-schema/hash.d.ts.map +0 -1
- package/dist/schema/state/sqlite/db-schema/hash.js +0 -15
- package/dist/schema/state/sqlite/db-schema/hash.js.map +0 -1
- package/src/schema/state/sqlite/db-schema/hash.ts +0 -13
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
Option,
|
|
16
16
|
Predicate,
|
|
17
17
|
Queue,
|
|
18
|
+
Random,
|
|
18
19
|
ReadonlyArray,
|
|
19
20
|
References,
|
|
20
21
|
Result,
|
|
@@ -49,8 +50,7 @@ import {
|
|
|
49
50
|
StaleRebaseGenerationError,
|
|
50
51
|
} from './RejectedPushError.ts'
|
|
51
52
|
import type { ShutdownChannel } from './shutdown-channel.ts'
|
|
52
|
-
import type { InitialBlockingSyncContext } from './types.ts'
|
|
53
|
-
import { LeaderThreadCtx } from './types.ts'
|
|
53
|
+
import type { InitialBlockingSyncContext, MaterializeEvent } from './types.ts'
|
|
54
54
|
|
|
55
55
|
export const TypeId = '~@livestore/common/LeaderSyncProcessor' as const
|
|
56
56
|
export type TypeId = typeof TypeId
|
|
@@ -120,14 +120,15 @@ export interface Service {
|
|
|
120
120
|
readonly boot: Effect.Effect<
|
|
121
121
|
{ initialLeaderHead: EventSequenceNumber.Client.Composite },
|
|
122
122
|
never,
|
|
123
|
-
|
|
123
|
+
Scope.Scope | HttpClient.HttpClient
|
|
124
124
|
>
|
|
125
125
|
readonly syncState: Subscribable.Subscribable<SyncState.SyncState>
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
interface Options {
|
|
129
129
|
readonly schema: LiveStoreSchema
|
|
130
|
-
|
|
130
|
+
/** Complete runtime capabilities supplied before the processor is constructed. */
|
|
131
|
+
readonly runtime: Runtime
|
|
131
132
|
readonly initialBlockingSyncContext: InitialBlockingSyncContext
|
|
132
133
|
/** Initial sync state rehydrated from the persisted eventlog or initial sync state */
|
|
133
134
|
readonly initialSyncState: SyncState.SyncState
|
|
@@ -197,16 +198,33 @@ interface Options {
|
|
|
197
198
|
readonly testing: {
|
|
198
199
|
readonly delays?: {
|
|
199
200
|
readonly localPushProcessing?: Effect.Effect<void>
|
|
201
|
+
readonly backendPullReconnect?: (fallbackMs: number) => number
|
|
200
202
|
}
|
|
201
203
|
readonly hooks?: {
|
|
202
204
|
readonly localPushAdmitted?: (events: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>) => Effect.Effect<void>
|
|
205
|
+
/** Observes connectivity after the offline wait has subscribed, allowing tests to synchronize without sleeps. */
|
|
206
|
+
readonly backendPullReconnectWait?: (state: { isConnected: boolean; fallbackMs: number }) => Effect.Effect<void>
|
|
203
207
|
}
|
|
204
208
|
}
|
|
205
209
|
}
|
|
206
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Runtime capabilities used by sync processing. Keeping these explicit prevents the processor from
|
|
213
|
+
* depending on the outward-facing leader aggregate that contains the processor itself.
|
|
214
|
+
*/
|
|
215
|
+
interface Runtime {
|
|
216
|
+
readonly dbState: SqliteDb
|
|
217
|
+
readonly dbEventlog: SqliteDb
|
|
218
|
+
readonly materializeEvent: MaterializeEvent
|
|
219
|
+
readonly syncBackend: SyncBackend.SyncBackend | undefined
|
|
220
|
+
readonly shutdownChannel: ShutdownChannel
|
|
221
|
+
readonly devtoolsLatch: Latch.Latch | undefined
|
|
222
|
+
readonly span: Tracer.Span | undefined
|
|
223
|
+
}
|
|
224
|
+
|
|
207
225
|
export const make = Effect.fnUntraced(function* ({
|
|
208
226
|
schema,
|
|
209
|
-
|
|
227
|
+
runtime,
|
|
210
228
|
initialBlockingSyncContext,
|
|
211
229
|
initialSyncState,
|
|
212
230
|
onError,
|
|
@@ -216,6 +234,7 @@ export const make = Effect.fnUntraced(function* ({
|
|
|
216
234
|
testing,
|
|
217
235
|
}: Options) {
|
|
218
236
|
const stateHead = yield* StateHead.StateHead
|
|
237
|
+
const { dbState, dbEventlog, devtoolsLatch, materializeEvent, shutdownChannel, span, syncBackend } = runtime
|
|
219
238
|
const syncBackendPushQueue = yield* TxQueue.unbounded<LiveStoreEvent.Client.EncodedWithMeta>()
|
|
220
239
|
const localPushBatchSize = params.localPushBatchSize ?? 10
|
|
221
240
|
const backendPushBatchSize = params.backendPushBatchSize ?? 50
|
|
@@ -226,17 +245,7 @@ export const make = Effect.fnUntraced(function* ({
|
|
|
226
245
|
schema.eventsDefsMap.get(eventEncoded.name)?.options.clientOnly ?? false
|
|
227
246
|
|
|
228
247
|
const connectedClientSessionPullQueues = yield* makePullQueueSet
|
|
229
|
-
|
|
230
|
-
// This context depends on data from `boot`, we should find a better implementation to avoid this ref indirection.
|
|
231
|
-
const ctxRef = {
|
|
232
|
-
current: undefined as
|
|
233
|
-
| undefined
|
|
234
|
-
| {
|
|
235
|
-
span: Tracer.Span
|
|
236
|
-
devtoolsLatch: Latch.Latch | undefined
|
|
237
|
-
services: Context.Context<LeaderThreadCtx>
|
|
238
|
-
},
|
|
239
|
-
}
|
|
248
|
+
const materializeEventsBatch = makeMaterializeEventsBatch({ dbState, dbEventlog, materializeEvent })
|
|
240
249
|
|
|
241
250
|
type LocalPushQueueItem = [
|
|
242
251
|
event: LiveStoreEvent.Client.EncodedWithMeta,
|
|
@@ -311,7 +320,7 @@ export const make = Effect.fnUntraced(function* ({
|
|
|
311
320
|
|
|
312
321
|
// Since the rebase generation might have changed since enqueuing, we need to filter out items with older generation
|
|
313
322
|
// It's important that we filter after acquiring the localPushBackendPullMutex, otherwise we might filter with the old generation
|
|
314
|
-
const [
|
|
323
|
+
const [filteredItems, droppedItems] = ReadonlyArray.partition(batchItems, (batchItem) =>
|
|
315
324
|
batchItem[0].seqNum.rebaseGeneration >= currentRebaseGeneration
|
|
316
325
|
? Result.succeed(batchItem)
|
|
317
326
|
: Result.fail(batchItem),
|
|
@@ -381,7 +390,7 @@ export const make = Effect.fnUntraced(function* ({
|
|
|
381
390
|
const remainingLocalPushes = yield* snapshotTxQueue(localPushesQueue)
|
|
382
391
|
if (LS_DEV === true && remainingLocalPushes.length > 0) {
|
|
383
392
|
console.log('localPushesQueue is not empty', remainingLocalPushes.length)
|
|
384
|
-
// oxlint-disable-next-line
|
|
393
|
+
// oxlint-disable-next-line no-debugger -- intentional breakpoint for unexpected queue state
|
|
385
394
|
debugger
|
|
386
395
|
}
|
|
387
396
|
|
|
@@ -458,13 +467,13 @@ export const make = Effect.fnUntraced(function* ({
|
|
|
458
467
|
|
|
459
468
|
const backgroundBackendPulling = Effect.fn('@livestore/common:LeaderSyncProcessor:backend-pulling')(function* ({
|
|
460
469
|
restartBackendPushing,
|
|
470
|
+
onPullSuccess,
|
|
461
471
|
}: {
|
|
462
472
|
restartBackendPushing: (
|
|
463
473
|
filteredRebasedPending: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>,
|
|
464
|
-
) => Effect.Effect<void, never,
|
|
474
|
+
) => Effect.Effect<void, never, HttpClient.HttpClient>
|
|
475
|
+
onPullSuccess: () => void
|
|
465
476
|
}) {
|
|
466
|
-
const { syncBackend, dbState: db, dbEventlog, schema } = yield* LeaderThreadCtx
|
|
467
|
-
|
|
468
477
|
if (syncBackend === undefined) return
|
|
469
478
|
|
|
470
479
|
let pullMutexHeld = false
|
|
@@ -482,8 +491,8 @@ export const make = Effect.fnUntraced(function* ({
|
|
|
482
491
|
pageInfo: SyncBackend.PullResPageInfo,
|
|
483
492
|
) =>
|
|
484
493
|
Effect.gen(function* () {
|
|
485
|
-
if (
|
|
486
|
-
yield*
|
|
494
|
+
if (devtoolsLatch !== undefined) {
|
|
495
|
+
yield* devtoolsLatch.await
|
|
487
496
|
}
|
|
488
497
|
|
|
489
498
|
if (newEvents.length === 0) {
|
|
@@ -540,7 +549,7 @@ export const make = Effect.fnUntraced(function* ({
|
|
|
540
549
|
|
|
541
550
|
if (mergeResult.rollbackEvents.length > 0) {
|
|
542
551
|
yield* rollback({
|
|
543
|
-
dbState
|
|
552
|
+
dbState,
|
|
544
553
|
dbEventlog,
|
|
545
554
|
eventNumsToRollback: mergeResult.rollbackEvents.map((_) => _.seqNum),
|
|
546
555
|
})
|
|
@@ -576,12 +585,12 @@ export const make = Effect.fnUntraced(function* ({
|
|
|
576
585
|
EventSequenceNumber.Client.isEqual(event.seqNum, confirmedEvent.seqNum),
|
|
577
586
|
),
|
|
578
587
|
)
|
|
579
|
-
yield* Eventlog.
|
|
588
|
+
yield* Eventlog.updateSyncMetadataForDb(dbEventlog, confirmedNewEvents).pipe(Effect.orDieDebugger)
|
|
580
589
|
}
|
|
581
590
|
}
|
|
582
591
|
|
|
583
592
|
// Removes the changeset rows which are no longer needed as we'll never have to rollback beyond this point
|
|
584
|
-
trimChangesetRows(
|
|
593
|
+
trimChangesetRows(dbState, newBackendHead)
|
|
585
594
|
|
|
586
595
|
// The backend merge may advance or rebase the authoritative head. Realign the admission
|
|
587
596
|
// fence now so newly arriving pushes are validated against that history, not the pre-pull head.
|
|
@@ -605,7 +614,9 @@ export const make = Effect.fnUntraced(function* ({
|
|
|
605
614
|
})
|
|
606
615
|
|
|
607
616
|
const syncState = yield* Effect.fromNullishOr(yield* SubscriptionRef.get(syncStateSref)).pipe(Effect.orDieDebugger)
|
|
608
|
-
const cursorInfo = yield* Eventlog.
|
|
617
|
+
const cursorInfo = yield* Eventlog.getSyncBackendCursorInfoForDb(dbEventlog, {
|
|
618
|
+
remoteHead: syncState.upstreamHead.global,
|
|
619
|
+
})
|
|
609
620
|
|
|
610
621
|
const hashMaterializerResult = makeMaterializerHash({ schema, dbState })
|
|
611
622
|
|
|
@@ -613,6 +624,8 @@ export const make = Effect.fnUntraced(function* ({
|
|
|
613
624
|
// TODO only take from queue while connected
|
|
614
625
|
Stream.tap(({ batch, pageInfo }) =>
|
|
615
626
|
Effect.gen(function* () {
|
|
627
|
+
// Any response (including an empty batch) proves backend contact, even with stale connectivity.
|
|
628
|
+
onPullSuccess()
|
|
616
629
|
// NOTE we only want to take process events when the sync backend is connected
|
|
617
630
|
// (e.g. needed for simulating being offline)
|
|
618
631
|
// TODO remove when there's a better way to handle this in stream above
|
|
@@ -642,7 +655,6 @@ export const make = Effect.fnUntraced(function* ({
|
|
|
642
655
|
})
|
|
643
656
|
|
|
644
657
|
const backgroundBackendPushing = Effect.gen(function* () {
|
|
645
|
-
const { syncBackend } = yield* LeaderThreadCtx
|
|
646
658
|
if (syncBackend === undefined) return
|
|
647
659
|
|
|
648
660
|
while (true) {
|
|
@@ -652,8 +664,8 @@ export const make = Effect.fnUntraced(function* ({
|
|
|
652
664
|
|
|
653
665
|
yield* SubscriptionRef.waitUntil(syncBackend.isConnected, (isConnected) => isConnected === true)
|
|
654
666
|
|
|
655
|
-
if (
|
|
656
|
-
yield*
|
|
667
|
+
if (devtoolsLatch !== undefined) {
|
|
668
|
+
yield* devtoolsLatch.await
|
|
657
669
|
}
|
|
658
670
|
|
|
659
671
|
yield* Effect.spanEvent('backend-push', {
|
|
@@ -732,14 +744,14 @@ export const make = Effect.fnUntraced(function* ({
|
|
|
732
744
|
}).pipe(Effect.uninterruptible),
|
|
733
745
|
)
|
|
734
746
|
|
|
735
|
-
yield* Effect.
|
|
747
|
+
yield* Effect.forEach(deferreds, Deferred.await)
|
|
736
748
|
}).pipe(
|
|
737
749
|
Effect.withSpan('@livestore/common:LeaderSyncProcessor:push', {
|
|
738
750
|
attributes: {
|
|
739
751
|
batchSize: newEvents.length,
|
|
740
752
|
batch: TRACE_VERBOSE === true ? newEvents : undefined,
|
|
741
753
|
},
|
|
742
|
-
links:
|
|
754
|
+
links: span !== undefined ? [{ span, attributes: {} }] : undefined,
|
|
743
755
|
}),
|
|
744
756
|
)
|
|
745
757
|
|
|
@@ -747,16 +759,6 @@ export const make = Effect.fnUntraced(function* ({
|
|
|
747
759
|
[TypeId]: TypeId,
|
|
748
760
|
// Starts various background loops
|
|
749
761
|
boot: Effect.gen(function* () {
|
|
750
|
-
const span = yield* Effect.currentSpan.pipe(Effect.orDie)
|
|
751
|
-
const { devtools, shutdownChannel } = yield* LeaderThreadCtx
|
|
752
|
-
const services = yield* Effect.context<LeaderThreadCtx>()
|
|
753
|
-
|
|
754
|
-
ctxRef.current = {
|
|
755
|
-
span,
|
|
756
|
-
devtoolsLatch: devtools.enabled === true ? devtools.syncBackendLatch : undefined,
|
|
757
|
-
services,
|
|
758
|
-
}
|
|
759
|
-
|
|
760
762
|
/** State transitions need to happen atomically, so we use a Ref to track the state */
|
|
761
763
|
yield* SubscriptionRef.set(syncStateSref, initialSyncState)
|
|
762
764
|
|
|
@@ -772,7 +774,7 @@ export const make = Effect.fnUntraced(function* ({
|
|
|
772
774
|
}
|
|
773
775
|
|
|
774
776
|
const handleBackendIdMismatchError = (error: BackendIdMismatchError) =>
|
|
775
|
-
handleBackendIdMismatch({ error, onBackendIdMismatch, shutdownChannel })
|
|
777
|
+
handleBackendIdMismatch({ error, onBackendIdMismatch, shutdownChannel, dbEventlog, dbState })
|
|
776
778
|
|
|
777
779
|
const maybeShutdownOnError = (cause: Cause.Cause<UnknownError | MaterializeError>) =>
|
|
778
780
|
Effect.gen(function* () {
|
|
@@ -803,7 +805,12 @@ export const make = Effect.fnUntraced(function* ({
|
|
|
803
805
|
|
|
804
806
|
yield* FiberHandle.run(backendPushingFiberHandle, backendPushingEffect)
|
|
805
807
|
|
|
808
|
+
let offlinePullFallbackMs = 1_000
|
|
809
|
+
|
|
806
810
|
yield* backgroundBackendPulling({
|
|
811
|
+
onPullSuccess: () => {
|
|
812
|
+
offlinePullFallbackMs = 1_000
|
|
813
|
+
},
|
|
807
814
|
restartBackendPushing: (filteredRebasedPending) =>
|
|
808
815
|
Effect.gen(function* () {
|
|
809
816
|
// Stop current pushing fiber
|
|
@@ -817,10 +824,34 @@ export const make = Effect.fnUntraced(function* ({
|
|
|
817
824
|
yield* FiberHandle.run(backendPushingFiberHandle, backendPushingEffect)
|
|
818
825
|
}),
|
|
819
826
|
}).pipe(
|
|
827
|
+
Effect.tapError((error) => {
|
|
828
|
+
if (error._tag !== 'IsOfflineError' || syncBackend === undefined) return Effect.void
|
|
829
|
+
const isConnected = syncBackend.isConnected
|
|
830
|
+
return Effect.gen(function* () {
|
|
831
|
+
// Wake immediately on reconnection. Probe on a capped fallback timer if connectivity is stale.
|
|
832
|
+
const jitter = yield* Random.nextBetween(0.8, 1.2)
|
|
833
|
+
const fallbackMs =
|
|
834
|
+
testing.delays?.backendPullReconnect?.(offlinePullFallbackMs * jitter) ?? offlinePullFallbackMs * jitter
|
|
835
|
+
offlinePullFallbackMs = Math.min(offlinePullFallbackMs * 2, 30_000)
|
|
836
|
+
// A stale initial true is not a reconnect. Watch false→true even while the fallback timer is pending.
|
|
837
|
+
const waitHook = testing.hooks?.backendPullReconnectWait
|
|
838
|
+
const changes = SubscriptionRef.changes(isConnected)
|
|
839
|
+
const observedChanges =
|
|
840
|
+
waitHook === undefined
|
|
841
|
+
? changes
|
|
842
|
+
: changes.pipe(Stream.tap((value) => waitHook({ isConnected: value, fallbackMs })))
|
|
843
|
+
const reconnected = yield* observedChanges.pipe(
|
|
844
|
+
Stream.dropWhile((value) => value === true),
|
|
845
|
+
Stream.filter((value) => value === true),
|
|
846
|
+
Stream.runHead,
|
|
847
|
+
Effect.as(true),
|
|
848
|
+
Effect.timeoutOrElse({ duration: fallbackMs, orElse: () => Effect.succeed(false) }),
|
|
849
|
+
)
|
|
850
|
+
if (reconnected === true) offlinePullFallbackMs = 1_000
|
|
851
|
+
})
|
|
852
|
+
}),
|
|
820
853
|
Effect.retry({
|
|
821
|
-
//
|
|
822
|
-
// We're using `until` with a refinement instead of `while` to narrow `IsOfflineError` out of the error type.
|
|
823
|
-
// See https://github.com/Effect-TS/effect/issues/6122
|
|
854
|
+
// The unbounded offline-only retry keeps IsOfflineError out of the terminal error channel.
|
|
824
855
|
until: (error): error is Exclude<typeof error, IsOfflineError> => error._tag !== 'IsOfflineError',
|
|
825
856
|
}),
|
|
826
857
|
Effect.catchTag('BackendIdMismatchError', handleBackendIdMismatchError),
|
|
@@ -875,12 +906,7 @@ export const make = Effect.fnUntraced(function* ({
|
|
|
875
906
|
),
|
|
876
907
|
pull: ({ cursor }) =>
|
|
877
908
|
Effect.gen(function* () {
|
|
878
|
-
const queue = yield*
|
|
879
|
-
Effect.orDieDebugger,
|
|
880
|
-
Effect.flatMap((services) =>
|
|
881
|
-
connectedClientSessionPullQueues.makeQueue(cursor).pipe(Effect.provide(services)),
|
|
882
|
-
),
|
|
883
|
-
)
|
|
909
|
+
const queue = yield* connectedClientSessionPullQueues.makeQueue(cursor)
|
|
884
910
|
return Stream.fromQueue(queue)
|
|
885
911
|
}).pipe(Stream.unwrap),
|
|
886
912
|
/*
|
|
@@ -897,11 +923,7 @@ export const make = Effect.fnUntraced(function* ({
|
|
|
897
923
|
- full new state db snapshot in the "rebase" case
|
|
898
924
|
- downside: importing the snapshot is expensive
|
|
899
925
|
*/
|
|
900
|
-
pullQueue: ({ cursor }) =>
|
|
901
|
-
Effect.fromNullishOr(ctxRef.current?.services).pipe(
|
|
902
|
-
Effect.orDieDebugger,
|
|
903
|
-
Effect.flatMap((services) => connectedClientSessionPullQueues.makeQueue(cursor).pipe(Effect.provide(services))),
|
|
904
|
-
),
|
|
926
|
+
pullQueue: ({ cursor }) => connectedClientSessionPullQueues.makeQueue(cursor),
|
|
905
927
|
syncState: Subscribable.make({
|
|
906
928
|
get: SubscriptionRef.get(syncStateSref).pipe(Effect.flatMap(Effect.fromNullishOr), Effect.orDieDebugger),
|
|
907
929
|
changes: SubscriptionRef.changes(syncStateSref).pipe(Stream.filter(Predicate.isNotUndefined)),
|
|
@@ -911,46 +933,50 @@ export const make = Effect.fnUntraced(function* ({
|
|
|
911
933
|
|
|
912
934
|
export const layer = (options: Options) => Layer.effect(LeaderSyncProcessor, make(options))
|
|
913
935
|
|
|
914
|
-
type MaterializeEventsBatch = (_: {
|
|
915
|
-
batchItems: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>
|
|
916
|
-
}) => Effect.Effect<void, MaterializeError, LeaderThreadCtx>
|
|
917
|
-
|
|
918
936
|
// TODO how to handle errors gracefully
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
937
|
+
const makeMaterializeEventsBatch =
|
|
938
|
+
({
|
|
939
|
+
dbState,
|
|
940
|
+
dbEventlog,
|
|
941
|
+
materializeEvent,
|
|
942
|
+
}: {
|
|
943
|
+
dbState: SqliteDb
|
|
944
|
+
dbEventlog: SqliteDb
|
|
945
|
+
materializeEvent: MaterializeEvent
|
|
946
|
+
}) =>
|
|
947
|
+
({ batchItems }: { batchItems: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta> }) =>
|
|
948
|
+
Effect.gen(function* () {
|
|
949
|
+
// NOTE We always start a transaction to ensure consistency between db and eventlog (even for single-item batches)
|
|
950
|
+
dbState.execute('BEGIN TRANSACTION', undefined) // Start the transaction
|
|
951
|
+
dbEventlog.execute('BEGIN TRANSACTION', undefined) // Start the transaction
|
|
922
952
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
953
|
+
yield* Effect.addFinalizer((exit) =>
|
|
954
|
+
Effect.gen(function* () {
|
|
955
|
+
if (Exit.isSuccess(exit) === true) return
|
|
926
956
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
957
|
+
// Rollback in case of an error
|
|
958
|
+
dbState.execute('ROLLBACK', undefined)
|
|
959
|
+
dbEventlog.execute('ROLLBACK', undefined)
|
|
960
|
+
}),
|
|
961
|
+
)
|
|
962
|
+
|
|
963
|
+
for (let i = 0; i < batchItems.length; i++) {
|
|
964
|
+
const { sessionChangeset, hash } = yield* materializeEvent(batchItems[i]!)
|
|
965
|
+
batchItems[i]!.meta.sessionChangeset = sessionChangeset
|
|
966
|
+
batchItems[i]!.meta.materializerHashLeader = hash
|
|
967
|
+
}
|
|
930
968
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
969
|
+
dbState.execute('COMMIT', undefined) // Commit the transaction
|
|
970
|
+
dbEventlog.execute('COMMIT', undefined) // Commit the transaction
|
|
971
|
+
}).pipe(
|
|
972
|
+
Effect.uninterruptible,
|
|
973
|
+
Effect.scoped,
|
|
974
|
+
Effect.withSpan('@livestore/common:LeaderSyncProcessor:materializeEventItems', {
|
|
975
|
+
attributes: { batchSize: batchItems.length },
|
|
934
976
|
}),
|
|
977
|
+
Effect.tapCauseLogPretty,
|
|
935
978
|
)
|
|
936
979
|
|
|
937
|
-
for (let i = 0; i < batchItems.length; i++) {
|
|
938
|
-
const { sessionChangeset, hash } = yield* materializeEvent(batchItems[i]!)
|
|
939
|
-
batchItems[i]!.meta.sessionChangeset = sessionChangeset
|
|
940
|
-
batchItems[i]!.meta.materializerHashLeader = hash
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
db.execute('COMMIT', undefined) // Commit the transaction
|
|
944
|
-
dbEventlog.execute('COMMIT', undefined) // Commit the transaction
|
|
945
|
-
}).pipe(
|
|
946
|
-
Effect.uninterruptible,
|
|
947
|
-
Effect.scoped,
|
|
948
|
-
Effect.withSpan('@livestore/common:LeaderSyncProcessor:materializeEventItems', {
|
|
949
|
-
attributes: { batchSize: batchItems.length },
|
|
950
|
-
}),
|
|
951
|
-
Effect.tapCauseLogPretty,
|
|
952
|
-
)
|
|
953
|
-
|
|
954
980
|
const trimChangesetRows = (db: SqliteDb, newHead: EventSequenceNumber.Client.Composite) => {
|
|
955
981
|
// Since we're using the session changeset rows to query for the current head,
|
|
956
982
|
// we're keeping at least one row for the current head, and thus are using `<` instead of `<=`
|
|
@@ -960,11 +986,7 @@ const trimChangesetRows = (db: SqliteDb, newHead: EventSequenceNumber.Client.Com
|
|
|
960
986
|
interface PullQueueSet {
|
|
961
987
|
makeQueue: (
|
|
962
988
|
cursor: EventSequenceNumber.Client.Composite,
|
|
963
|
-
) => Effect.Effect<
|
|
964
|
-
Queue.Queue<{ payload: typeof SyncState.PayloadUpstream.Type }>,
|
|
965
|
-
never,
|
|
966
|
-
Scope.Scope | LeaderThreadCtx
|
|
967
|
-
>
|
|
989
|
+
) => Effect.Effect<Queue.Queue<{ payload: typeof SyncState.PayloadUpstream.Type }>, never, Scope.Scope>
|
|
968
990
|
offer: (item: {
|
|
969
991
|
payload: typeof SyncState.PayloadUpstream.Type
|
|
970
992
|
leaderHead: EventSequenceNumber.Client.Composite
|
|
@@ -1176,13 +1198,15 @@ const handleBackendIdMismatch = Effect.fn('@livestore/common:LeaderSyncProcessor
|
|
|
1176
1198
|
error,
|
|
1177
1199
|
onBackendIdMismatch,
|
|
1178
1200
|
shutdownChannel,
|
|
1201
|
+
dbEventlog,
|
|
1202
|
+
dbState,
|
|
1179
1203
|
}: {
|
|
1180
1204
|
error: BackendIdMismatchError
|
|
1181
1205
|
onBackendIdMismatch: 'reset' | 'shutdown' | 'ignore'
|
|
1182
1206
|
shutdownChannel: ShutdownChannel
|
|
1207
|
+
dbEventlog: SqliteDb
|
|
1208
|
+
dbState: SqliteDb
|
|
1183
1209
|
}) {
|
|
1184
|
-
const { dbEventlog, dbState } = yield* LeaderThreadCtx
|
|
1185
|
-
|
|
1186
1210
|
if (onBackendIdMismatch === 'reset') {
|
|
1187
1211
|
yield* Effect.logWarning(
|
|
1188
1212
|
'Sync backend identity changed (backend was reset). Clearing local storage and shutting down.',
|
|
@@ -258,9 +258,13 @@ export const insertIntoEventlog = (
|
|
|
258
258
|
})
|
|
259
259
|
|
|
260
260
|
export const updateSyncMetadata = (items: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>) =>
|
|
261
|
-
Effect.
|
|
262
|
-
const { dbEventlog } = yield* LeaderThreadCtx
|
|
261
|
+
LeaderThreadCtx.pipe(Effect.flatMap(({ dbEventlog }) => updateSyncMetadataForDb(dbEventlog, items)))
|
|
263
262
|
|
|
263
|
+
export const updateSyncMetadataForDb = (
|
|
264
|
+
dbEventlog: SqliteDb,
|
|
265
|
+
items: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>,
|
|
266
|
+
) =>
|
|
267
|
+
Effect.gen(function* () {
|
|
264
268
|
// TODO try to do this in a single query
|
|
265
269
|
for (let i = 0; i < items.length; i++) {
|
|
266
270
|
const event = items[i]!
|
|
@@ -277,10 +281,14 @@ export const updateSyncMetadata = (items: ReadonlyArray<LiveStoreEvent.Client.En
|
|
|
277
281
|
}
|
|
278
282
|
})
|
|
279
283
|
|
|
280
|
-
export const getSyncBackendCursorInfo = (
|
|
281
|
-
Effect.
|
|
282
|
-
const { dbEventlog } = yield* LeaderThreadCtx
|
|
284
|
+
export const getSyncBackendCursorInfo = (args: { remoteHead: EventSequenceNumber.Global.Type }) =>
|
|
285
|
+
LeaderThreadCtx.pipe(Effect.flatMap(({ dbEventlog }) => getSyncBackendCursorInfoForDb(dbEventlog, args)))
|
|
283
286
|
|
|
287
|
+
export const getSyncBackendCursorInfoForDb = (
|
|
288
|
+
dbEventlog: SqliteDb,
|
|
289
|
+
{ remoteHead }: { remoteHead: EventSequenceNumber.Global.Type },
|
|
290
|
+
) =>
|
|
291
|
+
Effect.gen(function* () {
|
|
284
292
|
if (remoteHead === EventSequenceNumber.Client.ROOT.global) return Option.none()
|
|
285
293
|
|
|
286
294
|
const EventlogQuerySchema = Schema.Struct({
|
|
@@ -42,7 +42,7 @@ export const bootDevtools = Effect.fn('@livestore/common:leader-thread:devtools:
|
|
|
42
42
|
}).pipe(Effect.tapCauseLogPretty, Effect.forkScoped)
|
|
43
43
|
|
|
44
44
|
const bootResult = yield* options.boot.pipe(
|
|
45
|
-
Effect.
|
|
45
|
+
Effect.asSome,
|
|
46
46
|
Effect.catchIf(isDevtoolsViteNotInstalledError, (error) =>
|
|
47
47
|
Effect.logWarning(`[@livestore/devtools] ${error.message} Devtools will be disabled.`).pipe(
|
|
48
48
|
Effect.as(Option.none()),
|
|
@@ -31,11 +31,12 @@ import type * as StateHead from '../StateHead.ts'
|
|
|
31
31
|
import type { SyncBackend, SyncOptions } from '../sync/sync.ts'
|
|
32
32
|
import { SyncState } from '../sync/syncstate.ts'
|
|
33
33
|
import { sql } from '../util.ts'
|
|
34
|
+
import { configureConnection } from './connection.ts'
|
|
34
35
|
import * as Eventlog from './eventlog.ts'
|
|
35
36
|
import { bootDevtools } from './leader-worker-devtools.ts'
|
|
36
37
|
import * as LeaderSyncProcessor from './LeaderSyncProcessor.ts'
|
|
37
38
|
import { makeMaterializeEvent } from './materialize-event.ts'
|
|
38
|
-
import { recreateDb } from './recreate-db.ts'
|
|
39
|
+
import { hasCompletedState, recreateDb } from './recreate-db.ts'
|
|
39
40
|
import type { ShutdownChannel } from './shutdown-channel.ts'
|
|
40
41
|
import type {
|
|
41
42
|
DevtoolsContext,
|
|
@@ -64,14 +65,17 @@ export interface MakeLeaderThreadLayerParams {
|
|
|
64
65
|
params?: {
|
|
65
66
|
localPushBatchSize?: number
|
|
66
67
|
backendPushBatchSize?: number
|
|
68
|
+
stateRebuildBatchSize?: number
|
|
67
69
|
}
|
|
68
70
|
testing?: {
|
|
69
71
|
syncProcessor?: {
|
|
70
72
|
delays?: {
|
|
71
73
|
localPushProcessing?: Effect.Effect<void>
|
|
74
|
+
backendPullReconnect?: (fallbackMs: number) => number
|
|
72
75
|
}
|
|
73
76
|
hooks?: {
|
|
74
77
|
localPushAdmitted?: (events: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>) => Effect.Effect<void>
|
|
78
|
+
backendPullReconnectWait?: (state: { isConnected: boolean; fallbackMs: number }) => Effect.Effect<void>
|
|
75
79
|
}
|
|
76
80
|
}
|
|
77
81
|
}
|
|
@@ -99,9 +103,7 @@ export const makeLeaderThreadLayer = ({
|
|
|
99
103
|
> =>
|
|
100
104
|
Effect.gen(function* () {
|
|
101
105
|
const syncPayloadDecoded =
|
|
102
|
-
syncPayloadEncoded === undefined
|
|
103
|
-
? undefined
|
|
104
|
-
: yield* Schema.decodeUnknownEffect(syncPayloadSchema)(syncPayloadEncoded)
|
|
106
|
+
syncPayloadEncoded === undefined ? undefined : yield* Schema.decodeEffect(syncPayloadSchema)(syncPayloadEncoded)
|
|
105
107
|
|
|
106
108
|
const bootStatusQueue = yield* Effect.acquireRelease(Queue.unbounded<BootStatus>(), Queue.shutdown)
|
|
107
109
|
|
|
@@ -112,8 +114,20 @@ export const makeLeaderThreadLayer = ({
|
|
|
112
114
|
|
|
113
115
|
const dbEventlogMissing = !hasEventlogTables(dbEventlog)
|
|
114
116
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
const stateNeedsRebuild = !hasCompletedState(dbState)
|
|
118
|
+
|
|
119
|
+
if (stateNeedsRebuild === true) {
|
|
120
|
+
// Import also clears hook-created objects while preserving the open connection.
|
|
121
|
+
yield* Effect.acquireUseRelease(
|
|
122
|
+
makeSqliteDb({ _tag: 'in-memory' }),
|
|
123
|
+
(emptyDb) =>
|
|
124
|
+
configureConnection(emptyDb, { foreignKeys: false }).pipe(
|
|
125
|
+
Effect.andThen(Effect.sync(() => dbState.import(emptyDb))),
|
|
126
|
+
UnknownError.mapToUnknownError,
|
|
127
|
+
),
|
|
128
|
+
(emptyDb) => Effect.sync(() => emptyDb.close()),
|
|
129
|
+
)
|
|
130
|
+
}
|
|
117
131
|
|
|
118
132
|
yield* Eventlog.initEventlogDb(dbEventlog)
|
|
119
133
|
|
|
@@ -171,13 +185,39 @@ export const makeLeaderThreadLayer = ({
|
|
|
171
185
|
// Recreate state database if needed BEFORE creating sync processor
|
|
172
186
|
// This ensures all system tables exist before any queries are made
|
|
173
187
|
const { migrationsReport } =
|
|
174
|
-
|
|
175
|
-
? yield* recreateDb({
|
|
188
|
+
stateNeedsRebuild === true
|
|
189
|
+
? yield* recreateDb({
|
|
190
|
+
dbState,
|
|
191
|
+
dbEventlog,
|
|
192
|
+
schema,
|
|
193
|
+
bootStatusQueue,
|
|
194
|
+
materializeEvent,
|
|
195
|
+
...omitUndefineds({ stateRebuildBatchSize: params?.stateRebuildBatchSize }),
|
|
196
|
+
})
|
|
176
197
|
: { migrationsReport: { migrations: [] } }
|
|
177
198
|
|
|
199
|
+
const devtoolsContext =
|
|
200
|
+
devtoolsOptions.enabled === true
|
|
201
|
+
? {
|
|
202
|
+
enabled: true as const,
|
|
203
|
+
syncBackendLatch: yield* Latch.make(true),
|
|
204
|
+
syncBackendLatchState: yield* SubscriptionRef.make<{ latchClosed: boolean }>({ latchClosed: false }),
|
|
205
|
+
}
|
|
206
|
+
: { enabled: false as const }
|
|
207
|
+
|
|
208
|
+
const span = yield* Effect.currentSpan.pipe(Effect.option, Effect.map(Option.getOrUndefined))
|
|
209
|
+
|
|
178
210
|
const syncProcessor = yield* LeaderSyncProcessor.make({
|
|
179
211
|
schema,
|
|
180
|
-
|
|
212
|
+
runtime: {
|
|
213
|
+
dbState,
|
|
214
|
+
dbEventlog,
|
|
215
|
+
materializeEvent,
|
|
216
|
+
syncBackend,
|
|
217
|
+
shutdownChannel,
|
|
218
|
+
devtoolsLatch: devtoolsContext.enabled === true ? devtoolsContext.syncBackendLatch : undefined,
|
|
219
|
+
span,
|
|
220
|
+
},
|
|
181
221
|
initialSyncState: getInitialSyncState({ dbEventlog, dbState, dbEventlogMissing }),
|
|
182
222
|
initialBlockingSyncContext,
|
|
183
223
|
onError: syncOptions?.onSyncError ?? 'ignore',
|
|
@@ -199,15 +239,6 @@ export const makeLeaderThreadLayer = ({
|
|
|
199
239
|
Queue.shutdown,
|
|
200
240
|
)
|
|
201
241
|
|
|
202
|
-
const devtoolsContext =
|
|
203
|
-
devtoolsOptions.enabled === true
|
|
204
|
-
? {
|
|
205
|
-
enabled: true as const,
|
|
206
|
-
syncBackendLatch: yield* Latch.make(true),
|
|
207
|
-
syncBackendLatchState: yield* SubscriptionRef.make<{ latchClosed: boolean }>({ latchClosed: false }),
|
|
208
|
-
}
|
|
209
|
-
: { enabled: false as const }
|
|
210
|
-
|
|
211
242
|
const networkStatus = yield* makeNetworkStatusSubscribable({ syncBackend, devtoolsContext })
|
|
212
243
|
|
|
213
244
|
const ctx = {
|
|
@@ -256,11 +287,6 @@ const hasEventlogTables = (db: SqliteDb) => {
|
|
|
256
287
|
return ReadonlyArray.every(SystemTables.eventlogSystemTables, (_) => tableNames.has(_.sqliteDef.name))
|
|
257
288
|
}
|
|
258
289
|
|
|
259
|
-
const hasStateTables = (db: SqliteDb) => {
|
|
260
|
-
const tableNames = new Set(db.select<{ name: string }>(sql`select name from sqlite_master`).map((_) => _.name))
|
|
261
|
-
return ReadonlyArray.every(SystemTables.stateSystemTables, (_) => tableNames.has(_.sqliteDef.name))
|
|
262
|
-
}
|
|
263
|
-
|
|
264
290
|
const getInitialSyncState = ({
|
|
265
291
|
dbEventlog,
|
|
266
292
|
dbState,
|