@livestore/common 0.0.0-snapshot-2ef046b02334f52613d31dbe06af53487685edc0 → 0.0.0-snapshot-8115ad48d5a57244358c943ecc92bb0a30274b87
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/__tests__/fixture.d.ts +83 -221
- package/dist/__tests__/fixture.d.ts.map +1 -1
- package/dist/__tests__/fixture.js +33 -11
- package/dist/__tests__/fixture.js.map +1 -1
- package/dist/adapter-types.d.ts +34 -13
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js +20 -2
- package/dist/adapter-types.js.map +1 -1
- package/dist/bounded-collections.d.ts +1 -1
- package/dist/bounded-collections.d.ts.map +1 -1
- package/dist/debug-info.d.ts.map +1 -1
- package/dist/debug-info.js +1 -0
- package/dist/debug-info.js.map +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +21 -21
- package/dist/devtools/devtools-messages-common.d.ts +6 -6
- package/dist/devtools/devtools-messages-leader.d.ts +45 -45
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-leader.js +11 -11
- package/dist/devtools/devtools-messages-leader.js.map +1 -1
- package/dist/index.d.ts +2 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -5
- package/dist/index.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +25 -12
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +146 -98
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/{apply-mutation.d.ts → apply-event.d.ts} +7 -7
- package/dist/leader-thread/apply-event.d.ts.map +1 -0
- package/dist/leader-thread/{apply-mutation.js → apply-event.js} +45 -45
- package/dist/leader-thread/apply-event.js.map +1 -0
- package/dist/leader-thread/eventlog.d.ts +27 -0
- package/dist/leader-thread/eventlog.d.ts.map +1 -0
- package/dist/leader-thread/eventlog.js +123 -0
- package/dist/leader-thread/eventlog.js.map +1 -0
- package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +21 -19
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +16 -4
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +23 -16
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/mod.d.ts +1 -1
- package/dist/leader-thread/mod.d.ts.map +1 -1
- package/dist/leader-thread/mod.js +1 -1
- package/dist/leader-thread/mod.js.map +1 -1
- package/dist/leader-thread/recreate-db.d.ts.map +1 -1
- package/dist/leader-thread/recreate-db.js +6 -7
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/leader-thread/types.d.ts +14 -15
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/materializer-helper.d.ts +23 -0
- package/dist/materializer-helper.d.ts.map +1 -0
- package/dist/materializer-helper.js +70 -0
- package/dist/materializer-helper.js.map +1 -0
- package/dist/query-builder/api.d.ts +56 -51
- package/dist/query-builder/api.d.ts.map +1 -1
- package/dist/query-builder/api.js +3 -5
- package/dist/query-builder/api.js.map +1 -1
- package/dist/query-builder/astToSql.d.ts.map +1 -1
- package/dist/query-builder/astToSql.js +59 -37
- package/dist/query-builder/astToSql.js.map +1 -1
- package/dist/query-builder/impl.d.ts +2 -3
- package/dist/query-builder/impl.d.ts.map +1 -1
- package/dist/query-builder/impl.js +48 -46
- package/dist/query-builder/impl.js.map +1 -1
- package/dist/query-builder/impl.test.d.ts +86 -1
- package/dist/query-builder/impl.test.d.ts.map +1 -1
- package/dist/query-builder/impl.test.js +223 -36
- package/dist/query-builder/impl.test.js.map +1 -1
- package/dist/rehydrate-from-eventlog.d.ts +15 -0
- package/dist/rehydrate-from-eventlog.d.ts.map +1 -0
- package/dist/{rehydrate-from-mutationlog.js → rehydrate-from-eventlog.js} +26 -25
- package/dist/rehydrate-from-eventlog.js.map +1 -0
- package/dist/schema/EventDef.d.ts +136 -0
- package/dist/schema/EventDef.d.ts.map +1 -0
- package/dist/schema/EventDef.js +58 -0
- package/dist/schema/EventDef.js.map +1 -0
- package/dist/schema/EventId.d.ts +7 -2
- package/dist/schema/EventId.d.ts.map +1 -1
- package/dist/schema/EventId.js +18 -3
- package/dist/schema/EventId.js.map +1 -1
- package/dist/schema/{MutationEvent.d.ts → LiveStoreEvent.d.ts} +56 -56
- package/dist/schema/LiveStoreEvent.d.ts.map +1 -0
- package/dist/schema/{MutationEvent.js → LiveStoreEvent.js} +25 -25
- package/dist/schema/LiveStoreEvent.js.map +1 -0
- package/dist/schema/client-document-def.d.ts +223 -0
- package/dist/schema/client-document-def.d.ts.map +1 -0
- package/dist/schema/client-document-def.js +170 -0
- package/dist/schema/client-document-def.js.map +1 -0
- package/dist/schema/client-document-def.test.d.ts +2 -0
- package/dist/schema/client-document-def.test.d.ts.map +1 -0
- package/dist/schema/client-document-def.test.js +201 -0
- package/dist/schema/client-document-def.test.js.map +1 -0
- package/dist/schema/db-schema/dsl/mod.d.ts.map +1 -1
- package/dist/schema/events.d.ts +2 -0
- package/dist/schema/events.d.ts.map +1 -0
- package/dist/schema/events.js +2 -0
- package/dist/schema/events.js.map +1 -0
- package/dist/schema/mod.d.ts +4 -3
- package/dist/schema/mod.d.ts.map +1 -1
- package/dist/schema/mod.js +4 -3
- package/dist/schema/mod.js.map +1 -1
- package/dist/schema/schema.d.ts +26 -22
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js +45 -43
- package/dist/schema/schema.js.map +1 -1
- package/dist/schema/sqlite-state.d.ts +12 -0
- package/dist/schema/sqlite-state.d.ts.map +1 -0
- package/dist/schema/sqlite-state.js +36 -0
- package/dist/schema/sqlite-state.js.map +1 -0
- package/dist/schema/system-tables.d.ts +67 -98
- package/dist/schema/system-tables.d.ts.map +1 -1
- package/dist/schema/system-tables.js +62 -48
- package/dist/schema/system-tables.js.map +1 -1
- package/dist/schema/table-def.d.ts +26 -96
- package/dist/schema/table-def.d.ts.map +1 -1
- package/dist/schema/table-def.js +14 -64
- package/dist/schema/table-def.js.map +1 -1
- package/dist/schema/view.d.ts +3 -0
- package/dist/schema/view.d.ts.map +1 -0
- package/dist/schema/view.js +3 -0
- package/dist/schema/view.js.map +1 -0
- package/dist/schema-management/common.d.ts +4 -4
- package/dist/schema-management/common.d.ts.map +1 -1
- package/dist/schema-management/migrations.d.ts.map +1 -1
- package/dist/schema-management/migrations.js +6 -6
- package/dist/schema-management/migrations.js.map +1 -1
- package/dist/schema-management/validate-mutation-defs.d.ts +3 -3
- package/dist/schema-management/validate-mutation-defs.d.ts.map +1 -1
- package/dist/schema-management/validate-mutation-defs.js +17 -17
- package/dist/schema-management/validate-mutation-defs.js.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +7 -7
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +33 -30
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/next/facts.d.ts +19 -19
- package/dist/sync/next/facts.d.ts.map +1 -1
- package/dist/sync/next/facts.js +2 -2
- package/dist/sync/next/facts.js.map +1 -1
- package/dist/sync/next/history-dag-common.d.ts +3 -3
- 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-common.js.map +1 -1
- package/dist/sync/next/history-dag.js +1 -1
- package/dist/sync/next/history-dag.js.map +1 -1
- package/dist/sync/next/rebase-events.d.ts +7 -7
- package/dist/sync/next/rebase-events.d.ts.map +1 -1
- package/dist/sync/next/rebase-events.js +5 -5
- package/dist/sync/next/rebase-events.js.map +1 -1
- package/dist/sync/next/test/compact-events.calculator.test.js +38 -33
- package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
- package/dist/sync/next/test/compact-events.test.js +71 -71
- package/dist/sync/next/test/compact-events.test.js.map +1 -1
- package/dist/sync/next/test/{mutation-fixtures.d.ts → event-fixtures.d.ts} +25 -25
- package/dist/sync/next/test/event-fixtures.d.ts.map +1 -0
- package/dist/sync/next/test/{mutation-fixtures.js → event-fixtures.js} +60 -25
- package/dist/sync/next/test/event-fixtures.js.map +1 -0
- package/dist/sync/next/test/mod.d.ts +1 -1
- package/dist/sync/next/test/mod.d.ts.map +1 -1
- package/dist/sync/next/test/mod.js +1 -1
- package/dist/sync/next/test/mod.js.map +1 -1
- package/dist/sync/sync.d.ts +3 -3
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/syncstate.d.ts +30 -30
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +73 -40
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +175 -184
- package/dist/sync/syncstate.test.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.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/src/__tests__/fixture.ts +36 -15
- package/src/adapter-types.ts +33 -13
- package/src/debug-info.ts +1 -0
- package/src/devtools/devtools-messages-leader.ts +13 -13
- package/src/index.ts +2 -5
- package/src/leader-thread/LeaderSyncProcessor.ts +210 -138
- package/src/leader-thread/{apply-mutation.ts → apply-event.ts} +61 -61
- package/src/leader-thread/eventlog.ts +199 -0
- package/src/leader-thread/leader-worker-devtools.ts +22 -19
- package/src/leader-thread/make-leader-thread-layer.ts +51 -29
- package/src/leader-thread/mod.ts +1 -1
- package/src/leader-thread/recreate-db.ts +6 -8
- package/src/leader-thread/types.ts +15 -16
- package/src/materializer-helper.ts +110 -0
- package/src/query-builder/api.ts +77 -103
- package/src/query-builder/astToSql.ts +68 -39
- package/src/query-builder/impl.test.ts +239 -42
- package/src/query-builder/impl.ts +72 -56
- package/src/{rehydrate-from-mutationlog.ts → rehydrate-from-eventlog.ts} +35 -38
- package/src/schema/EventDef.ts +216 -0
- package/src/schema/EventId.ts +23 -4
- package/src/schema/{MutationEvent.ts → LiveStoreEvent.ts} +68 -69
- package/src/schema/client-document-def.test.ts +239 -0
- package/src/schema/client-document-def.ts +444 -0
- package/src/schema/db-schema/dsl/mod.ts +0 -1
- package/src/schema/events.ts +1 -0
- package/src/schema/mod.ts +4 -3
- package/src/schema/schema.ts +78 -68
- package/src/schema/sqlite-state.ts +62 -0
- package/src/schema/system-tables.ts +42 -53
- package/src/schema/table-def.ts +51 -209
- package/src/schema/view.ts +2 -0
- package/src/schema-management/common.ts +4 -4
- package/src/schema-management/migrations.ts +8 -9
- package/src/schema-management/validate-mutation-defs.ts +22 -24
- package/src/sync/ClientSessionSyncProcessor.ts +41 -36
- package/src/sync/next/facts.ts +31 -32
- package/src/sync/next/history-dag-common.ts +4 -4
- package/src/sync/next/history-dag.ts +1 -1
- package/src/sync/next/rebase-events.ts +13 -13
- package/src/sync/next/test/compact-events.calculator.test.ts +45 -45
- package/src/sync/next/test/compact-events.test.ts +73 -73
- package/src/sync/next/test/event-fixtures.ts +219 -0
- package/src/sync/next/test/mod.ts +1 -1
- package/src/sync/sync.ts +3 -3
- package/src/sync/syncstate.test.ts +180 -189
- package/src/sync/syncstate.ts +162 -100
- package/src/sync/validate-push-payload.ts +2 -2
- package/src/version.ts +1 -1
- package/tsconfig.json +1 -0
- package/dist/derived-mutations.d.ts +0 -109
- package/dist/derived-mutations.d.ts.map +0 -1
- package/dist/derived-mutations.js +0 -54
- package/dist/derived-mutations.js.map +0 -1
- package/dist/derived-mutations.test.d.ts +0 -2
- package/dist/derived-mutations.test.d.ts.map +0 -1
- package/dist/derived-mutations.test.js +0 -93
- package/dist/derived-mutations.test.js.map +0 -1
- package/dist/init-singleton-tables.d.ts +0 -4
- package/dist/init-singleton-tables.d.ts.map +0 -1
- package/dist/init-singleton-tables.js +0 -16
- package/dist/init-singleton-tables.js.map +0 -1
- package/dist/leader-thread/apply-mutation.d.ts.map +0 -1
- package/dist/leader-thread/apply-mutation.js.map +0 -1
- package/dist/leader-thread/mutationlog.d.ts +0 -27
- package/dist/leader-thread/mutationlog.d.ts.map +0 -1
- package/dist/leader-thread/mutationlog.js +0 -124
- package/dist/leader-thread/mutationlog.js.map +0 -1
- package/dist/mutation.d.ts +0 -20
- package/dist/mutation.d.ts.map +0 -1
- package/dist/mutation.js +0 -68
- package/dist/mutation.js.map +0 -1
- package/dist/query-info.d.ts +0 -41
- package/dist/query-info.d.ts.map +0 -1
- package/dist/query-info.js +0 -7
- package/dist/query-info.js.map +0 -1
- package/dist/rehydrate-from-mutationlog.d.ts +0 -15
- package/dist/rehydrate-from-mutationlog.d.ts.map +0 -1
- package/dist/rehydrate-from-mutationlog.js.map +0 -1
- package/dist/schema/MutationEvent.d.ts.map +0 -1
- package/dist/schema/MutationEvent.js.map +0 -1
- package/dist/schema/mutations.d.ts +0 -115
- package/dist/schema/mutations.d.ts.map +0 -1
- package/dist/schema/mutations.js +0 -42
- package/dist/schema/mutations.js.map +0 -1
- package/dist/sync/next/test/mutation-fixtures.d.ts.map +0 -1
- package/dist/sync/next/test/mutation-fixtures.js.map +0 -1
- package/src/derived-mutations.test.ts +0 -101
- package/src/derived-mutations.ts +0 -170
- package/src/init-singleton-tables.ts +0 -24
- package/src/leader-thread/mutationlog.ts +0 -202
- package/src/mutation.ts +0 -108
- package/src/query-info.ts +0 -83
- package/src/schema/mutations.ts +0 -193
- package/src/sync/next/test/mutation-fixtures.ts +0 -228
|
@@ -20,39 +20,36 @@ import { UnexpectedError } from '../adapter-types.js'
|
|
|
20
20
|
import type { LiveStoreSchema } from '../schema/mod.js'
|
|
21
21
|
import {
|
|
22
22
|
EventId,
|
|
23
|
-
|
|
23
|
+
getEventDef,
|
|
24
24
|
LEADER_MERGE_COUNTER_TABLE,
|
|
25
|
-
|
|
25
|
+
LiveStoreEvent,
|
|
26
26
|
SESSION_CHANGESET_META_TABLE,
|
|
27
27
|
} from '../schema/mod.js'
|
|
28
28
|
import { LeaderAheadError } from '../sync/sync.js'
|
|
29
29
|
import * as SyncState from '../sync/syncstate.js'
|
|
30
30
|
import { sql } from '../util.js'
|
|
31
|
-
import { rollback } from './apply-
|
|
32
|
-
import * as
|
|
31
|
+
import { rollback } from './apply-event.js'
|
|
32
|
+
import * as Eventlog from './eventlog.js'
|
|
33
33
|
import type { InitialBlockingSyncContext, LeaderSyncProcessor } from './types.js'
|
|
34
34
|
import { LeaderThreadCtx } from './types.js'
|
|
35
35
|
|
|
36
|
-
export const BACKEND_PUSH_BATCH_SIZE = 50
|
|
37
|
-
export const LOCAL_PUSH_BATCH_SIZE = 10
|
|
38
|
-
|
|
39
36
|
type LocalPushQueueItem = [
|
|
40
|
-
|
|
37
|
+
event: LiveStoreEvent.EncodedWithMeta,
|
|
41
38
|
deferred: Deferred.Deferred<void, LeaderAheadError> | undefined,
|
|
42
39
|
/** Used to determine whether the batch has become invalid due to a rejected local push batch */
|
|
43
40
|
generation: number,
|
|
44
41
|
]
|
|
45
42
|
|
|
46
43
|
/**
|
|
47
|
-
* The LeaderSyncProcessor manages synchronization of
|
|
44
|
+
* The LeaderSyncProcessor manages synchronization of events between
|
|
48
45
|
* the local state and the sync backend, ensuring efficient and orderly processing.
|
|
49
46
|
*
|
|
50
47
|
* In the LeaderSyncProcessor, pulling always has precedence over pushing.
|
|
51
48
|
*
|
|
52
49
|
* Responsibilities:
|
|
53
|
-
* - Queueing incoming local
|
|
54
|
-
* - Broadcasting
|
|
55
|
-
* - Pushing
|
|
50
|
+
* - Queueing incoming local events in a localPushesQueue.
|
|
51
|
+
* - Broadcasting events to client sessions via pull queues.
|
|
52
|
+
* - Pushing events to the sync backend.
|
|
56
53
|
*
|
|
57
54
|
* Notes:
|
|
58
55
|
*
|
|
@@ -60,12 +57,12 @@ type LocalPushQueueItem = [
|
|
|
60
57
|
* - localPushesQueue:
|
|
61
58
|
* - Maintains events in ascending order.
|
|
62
59
|
* - Uses `Deferred` objects to resolve/reject events based on application success.
|
|
63
|
-
* - Processes events from the queue, applying
|
|
60
|
+
* - Processes events from the queue, applying events in batches.
|
|
64
61
|
* - Controlled by a `Latch` to manage execution flow.
|
|
65
62
|
* - The latch closes on pull receipt and re-opens post-pull completion.
|
|
66
63
|
* - Processes up to `maxBatchSize` events per cycle.
|
|
67
64
|
*
|
|
68
|
-
* Currently we're advancing the db read model and
|
|
65
|
+
* Currently we're advancing the db read model and eventlog in lockstep, but we could also decouple this in the future
|
|
69
66
|
*
|
|
70
67
|
* Tricky concurrency scenarios:
|
|
71
68
|
* - Queued local push batches becoming invalid due to a prior local push item being rejected.
|
|
@@ -74,31 +71,50 @@ type LocalPushQueueItem = [
|
|
|
74
71
|
*/
|
|
75
72
|
export const makeLeaderSyncProcessor = ({
|
|
76
73
|
schema,
|
|
77
|
-
|
|
78
|
-
|
|
74
|
+
dbEventlogMissing,
|
|
75
|
+
dbEventlog,
|
|
79
76
|
dbReadModel,
|
|
80
77
|
dbReadModelMissing,
|
|
81
78
|
initialBlockingSyncContext,
|
|
82
79
|
onError,
|
|
80
|
+
params,
|
|
81
|
+
testing,
|
|
83
82
|
}: {
|
|
84
83
|
schema: LiveStoreSchema
|
|
85
|
-
/** Only used to know whether we can safely query
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
/** Only used to know whether we can safely query dbEventlog during setup execution */
|
|
85
|
+
dbEventlogMissing: boolean
|
|
86
|
+
dbEventlog: SqliteDb
|
|
88
87
|
dbReadModel: SqliteDb
|
|
89
88
|
/** Only used to know whether we can safely query dbReadModel during setup execution */
|
|
90
89
|
dbReadModelMissing: boolean
|
|
91
90
|
initialBlockingSyncContext: InitialBlockingSyncContext
|
|
92
91
|
onError: 'shutdown' | 'ignore'
|
|
92
|
+
params: {
|
|
93
|
+
/**
|
|
94
|
+
* @default 10
|
|
95
|
+
*/
|
|
96
|
+
localPushBatchSize?: number
|
|
97
|
+
/**
|
|
98
|
+
* @default 50
|
|
99
|
+
*/
|
|
100
|
+
backendPushBatchSize?: number
|
|
101
|
+
}
|
|
102
|
+
testing: {
|
|
103
|
+
delays?: {
|
|
104
|
+
localPushProcessing?: Effect.Effect<void>
|
|
105
|
+
}
|
|
106
|
+
}
|
|
93
107
|
}): Effect.Effect<LeaderSyncProcessor, UnexpectedError, Scope.Scope> =>
|
|
94
108
|
Effect.gen(function* () {
|
|
95
|
-
const syncBackendPushQueue = yield* BucketQueue.make<
|
|
109
|
+
const syncBackendPushQueue = yield* BucketQueue.make<LiveStoreEvent.EncodedWithMeta>()
|
|
110
|
+
const localPushBatchSize = params.localPushBatchSize ?? 10
|
|
111
|
+
const backendPushBatchSize = params.backendPushBatchSize ?? 50
|
|
96
112
|
|
|
97
113
|
const syncStateSref = yield* SubscriptionRef.make<SyncState.SyncState | undefined>(undefined)
|
|
98
114
|
|
|
99
|
-
const isClientEvent = (
|
|
100
|
-
const
|
|
101
|
-
return
|
|
115
|
+
const isClientEvent = (eventEncoded: LiveStoreEvent.EncodedWithMeta) => {
|
|
116
|
+
const eventDef = getEventDef(schema, eventEncoded.name)
|
|
117
|
+
return eventDef.eventDef.options.clientOnly
|
|
102
118
|
}
|
|
103
119
|
|
|
104
120
|
const connectedClientSessionPullQueues = yield* makePullQueueSet
|
|
@@ -108,10 +124,12 @@ export const makeLeaderSyncProcessor = ({
|
|
|
108
124
|
* If a local-push batch is rejected, all subsequent push queue items with the same generation are also rejected,
|
|
109
125
|
* even if they would be valid on their own.
|
|
110
126
|
*/
|
|
127
|
+
// TODO get rid of this in favour of the `mergeGeneration` event id field
|
|
111
128
|
const currentLocalPushGenerationRef = { current: 0 }
|
|
112
129
|
|
|
130
|
+
type MergeCounter = number
|
|
113
131
|
const mergeCounterRef = { current: dbReadModelMissing ? 0 : yield* getMergeCounterFromDb(dbReadModel) }
|
|
114
|
-
const mergePayloads = new Map<
|
|
132
|
+
const mergePayloads = new Map<MergeCounter, typeof SyncState.PayloadUpstream.Type>()
|
|
115
133
|
|
|
116
134
|
// This context depends on data from `boot`, we should find a better implementation to avoid this ref indirection.
|
|
117
135
|
const ctxRef = {
|
|
@@ -129,12 +147,29 @@ export const makeLeaderSyncProcessor = ({
|
|
|
129
147
|
const localPushesLatch = yield* Effect.makeLatch(true)
|
|
130
148
|
const pullLatch = yield* Effect.makeLatch(true)
|
|
131
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Additionally to the `syncStateSref` we also need the `pushHeadRef` in order to prevent old/duplicate
|
|
152
|
+
* events from being pushed in a scenario like this:
|
|
153
|
+
* - client session A pushes e1
|
|
154
|
+
* - leader sync processor takes a bit and hasn't yet taken e1 from the localPushesQueue
|
|
155
|
+
* - client session B also pushes e1 (which should be rejected)
|
|
156
|
+
*
|
|
157
|
+
* Thus the purpoe of the pushHeadRef is the guard the integrity of the local push queue
|
|
158
|
+
*/
|
|
159
|
+
const pushHeadRef = { current: EventId.ROOT }
|
|
160
|
+
const advancePushHead = (eventId: EventId.EventId) => {
|
|
161
|
+
pushHeadRef.current = EventId.max(pushHeadRef.current, eventId)
|
|
162
|
+
}
|
|
163
|
+
|
|
132
164
|
// NOTE: New events are only pushed to sync backend after successful local push processing
|
|
133
165
|
const push: LeaderSyncProcessor['push'] = (newEvents, options) =>
|
|
134
166
|
Effect.gen(function* () {
|
|
135
|
-
// TODO validate batch
|
|
136
167
|
if (newEvents.length === 0) return
|
|
137
168
|
|
|
169
|
+
yield* validatePushBatch(newEvents, pushHeadRef.current)
|
|
170
|
+
|
|
171
|
+
advancePushHead(newEvents.at(-1)!.id)
|
|
172
|
+
|
|
138
173
|
const waitForProcessing = options?.waitForProcessing ?? false
|
|
139
174
|
const generation = currentLocalPushGenerationRef.current
|
|
140
175
|
|
|
@@ -142,20 +177,18 @@ export const makeLeaderSyncProcessor = ({
|
|
|
142
177
|
const deferreds = yield* Effect.forEach(newEvents, () => Deferred.make<void, LeaderAheadError>())
|
|
143
178
|
|
|
144
179
|
const items = newEvents.map(
|
|
145
|
-
(
|
|
180
|
+
(eventEncoded, i) => [eventEncoded, deferreds[i], generation] as LocalPushQueueItem,
|
|
146
181
|
)
|
|
147
182
|
|
|
148
183
|
yield* BucketQueue.offerAll(localPushesQueue, items)
|
|
149
184
|
|
|
150
185
|
yield* Effect.all(deferreds)
|
|
151
186
|
} else {
|
|
152
|
-
const items = newEvents.map(
|
|
153
|
-
(mutationEventEncoded) => [mutationEventEncoded, undefined, generation] as LocalPushQueueItem,
|
|
154
|
-
)
|
|
187
|
+
const items = newEvents.map((eventEncoded) => [eventEncoded, undefined, generation] as LocalPushQueueItem)
|
|
155
188
|
yield* BucketQueue.offerAll(localPushesQueue, items)
|
|
156
189
|
}
|
|
157
190
|
}).pipe(
|
|
158
|
-
Effect.withSpan('@livestore/common:LeaderSyncProcessor:
|
|
191
|
+
Effect.withSpan('@livestore/common:LeaderSyncProcessor:push', {
|
|
159
192
|
attributes: {
|
|
160
193
|
batchSize: newEvents.length,
|
|
161
194
|
batch: TRACE_VERBOSE ? newEvents : undefined,
|
|
@@ -164,26 +197,22 @@ export const makeLeaderSyncProcessor = ({
|
|
|
164
197
|
}),
|
|
165
198
|
)
|
|
166
199
|
|
|
167
|
-
const pushPartial: LeaderSyncProcessor['pushPartial'] = ({
|
|
168
|
-
mutationEvent: { mutation, args },
|
|
169
|
-
clientId,
|
|
170
|
-
sessionId,
|
|
171
|
-
}) =>
|
|
200
|
+
const pushPartial: LeaderSyncProcessor['pushPartial'] = ({ event: { name, args }, clientId, sessionId }) =>
|
|
172
201
|
Effect.gen(function* () {
|
|
173
202
|
const syncState = yield* syncStateSref
|
|
174
203
|
if (syncState === undefined) return shouldNeverHappen('Not initialized')
|
|
175
204
|
|
|
176
|
-
const
|
|
205
|
+
const eventDef = getEventDef(schema, name)
|
|
177
206
|
|
|
178
|
-
const
|
|
179
|
-
|
|
207
|
+
const eventEncoded = new LiveStoreEvent.EncodedWithMeta({
|
|
208
|
+
name,
|
|
180
209
|
args,
|
|
181
210
|
clientId,
|
|
182
211
|
sessionId,
|
|
183
|
-
...EventId.nextPair(syncState.localHead,
|
|
212
|
+
...EventId.nextPair(syncState.localHead, eventDef.eventDef.options.clientOnly),
|
|
184
213
|
})
|
|
185
214
|
|
|
186
|
-
yield* push([
|
|
215
|
+
yield* push([eventEncoded])
|
|
187
216
|
}).pipe(Effect.catchTag('LeaderAheadError', Effect.orDie))
|
|
188
217
|
|
|
189
218
|
// Starts various background loops
|
|
@@ -200,10 +229,9 @@ export const makeLeaderSyncProcessor = ({
|
|
|
200
229
|
runtime,
|
|
201
230
|
}
|
|
202
231
|
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const initialLocalHead = dbMutationLogMissing ? EventId.ROOT : Mutationlog.getClientHeadFromDb(dbMutationLog)
|
|
232
|
+
const initialLocalHead = dbEventlogMissing ? EventId.ROOT : Eventlog.getClientHeadFromDb(dbEventlog)
|
|
233
|
+
|
|
234
|
+
const initialBackendHead = dbEventlogMissing ? EventId.ROOT.global : Eventlog.getBackendHeadFromDb(dbEventlog)
|
|
207
235
|
|
|
208
236
|
if (initialBackendHead > initialLocalHead.global) {
|
|
209
237
|
return shouldNeverHappen(
|
|
@@ -211,12 +239,12 @@ export const makeLeaderSyncProcessor = ({
|
|
|
211
239
|
)
|
|
212
240
|
}
|
|
213
241
|
|
|
214
|
-
const
|
|
242
|
+
const pendingEvents = dbEventlogMissing
|
|
215
243
|
? []
|
|
216
|
-
: yield*
|
|
244
|
+
: yield* Eventlog.getEventsSince({ global: initialBackendHead, client: EventId.clientDefault })
|
|
217
245
|
|
|
218
246
|
const initialSyncState = new SyncState.SyncState({
|
|
219
|
-
pending:
|
|
247
|
+
pending: pendingEvents,
|
|
220
248
|
upstreamHead: { global: initialBackendHead, client: EventId.clientDefault },
|
|
221
249
|
localHead: initialLocalHead,
|
|
222
250
|
})
|
|
@@ -225,16 +253,16 @@ export const makeLeaderSyncProcessor = ({
|
|
|
225
253
|
yield* SubscriptionRef.set(syncStateSref, initialSyncState)
|
|
226
254
|
|
|
227
255
|
// Rehydrate sync queue
|
|
228
|
-
if (
|
|
229
|
-
const
|
|
230
|
-
// Don't sync clientOnly
|
|
231
|
-
.filter((
|
|
232
|
-
const
|
|
233
|
-
return
|
|
256
|
+
if (pendingEvents.length > 0) {
|
|
257
|
+
const globalPendingEvents = pendingEvents
|
|
258
|
+
// Don't sync clientOnly events
|
|
259
|
+
.filter((eventEncoded) => {
|
|
260
|
+
const eventDef = getEventDef(schema, eventEncoded.name)
|
|
261
|
+
return eventDef.eventDef.options.clientOnly === false
|
|
234
262
|
})
|
|
235
263
|
|
|
236
|
-
if (
|
|
237
|
-
yield* BucketQueue.offerAll(syncBackendPushQueue,
|
|
264
|
+
if (globalPendingEvents.length > 0) {
|
|
265
|
+
yield* BucketQueue.offerAll(syncBackendPushQueue, globalPendingEvents)
|
|
238
266
|
}
|
|
239
267
|
}
|
|
240
268
|
|
|
@@ -259,18 +287,21 @@ export const makeLeaderSyncProcessor = ({
|
|
|
259
287
|
connectedClientSessionPullQueues,
|
|
260
288
|
mergeCounterRef,
|
|
261
289
|
mergePayloads,
|
|
290
|
+
localPushBatchSize,
|
|
291
|
+
testing: {
|
|
292
|
+
delay: testing?.delays?.localPushProcessing,
|
|
293
|
+
},
|
|
262
294
|
}).pipe(Effect.tapCauseLogPretty, Effect.catchAllCause(shutdownOnError), Effect.forkScoped)
|
|
263
295
|
|
|
264
296
|
const backendPushingFiberHandle = yield* FiberHandle.make()
|
|
297
|
+
const backendPushingEffect = backgroundBackendPushing({
|
|
298
|
+
syncBackendPushQueue,
|
|
299
|
+
otelSpan,
|
|
300
|
+
devtoolsLatch: ctxRef.current?.devtoolsLatch,
|
|
301
|
+
backendPushBatchSize,
|
|
302
|
+
}).pipe(Effect.tapCauseLogPretty, Effect.catchAllCause(shutdownOnError))
|
|
265
303
|
|
|
266
|
-
yield* FiberHandle.run(
|
|
267
|
-
backendPushingFiberHandle,
|
|
268
|
-
backgroundBackendPushing({
|
|
269
|
-
syncBackendPushQueue,
|
|
270
|
-
otelSpan,
|
|
271
|
-
devtoolsLatch: ctxRef.current?.devtoolsLatch,
|
|
272
|
-
}).pipe(Effect.tapCauseLogPretty, Effect.catchAllCause(shutdownOnError)),
|
|
273
|
-
)
|
|
304
|
+
yield* FiberHandle.run(backendPushingFiberHandle, backendPushingEffect)
|
|
274
305
|
|
|
275
306
|
yield* backgroundBackendPulling({
|
|
276
307
|
initialBackendHead,
|
|
@@ -285,14 +316,7 @@ export const makeLeaderSyncProcessor = ({
|
|
|
285
316
|
yield* BucketQueue.offerAll(syncBackendPushQueue, filteredRebasedPending)
|
|
286
317
|
|
|
287
318
|
// Restart pushing fiber
|
|
288
|
-
yield* FiberHandle.run(
|
|
289
|
-
backendPushingFiberHandle,
|
|
290
|
-
backgroundBackendPushing({
|
|
291
|
-
syncBackendPushQueue,
|
|
292
|
-
otelSpan,
|
|
293
|
-
devtoolsLatch: ctxRef.current?.devtoolsLatch,
|
|
294
|
-
}).pipe(Effect.tapCauseLogPretty, Effect.catchAllCause(shutdownOnError)),
|
|
295
|
-
)
|
|
319
|
+
yield* FiberHandle.run(backendPushingFiberHandle, backendPushingEffect)
|
|
296
320
|
}),
|
|
297
321
|
syncStateSref,
|
|
298
322
|
localPushesLatch,
|
|
@@ -303,26 +327,41 @@ export const makeLeaderSyncProcessor = ({
|
|
|
303
327
|
connectedClientSessionPullQueues,
|
|
304
328
|
mergeCounterRef,
|
|
305
329
|
mergePayloads,
|
|
330
|
+
advancePushHead,
|
|
306
331
|
}).pipe(Effect.tapCauseLogPretty, Effect.catchAllCause(shutdownOnError), Effect.forkScoped)
|
|
307
332
|
|
|
308
333
|
return { initialLeaderHead: initialLocalHead }
|
|
309
334
|
}).pipe(Effect.withSpanScoped('@livestore/common:LeaderSyncProcessor:boot'))
|
|
310
335
|
|
|
311
|
-
const pull: LeaderSyncProcessor['pull'] = ({ cursor }) =>
|
|
312
|
-
|
|
336
|
+
const pull: LeaderSyncProcessor['pull'] = ({ cursor }) =>
|
|
337
|
+
Effect.gen(function* () {
|
|
313
338
|
const queue = yield* pullQueue({ cursor })
|
|
314
339
|
return Stream.fromQueue(queue)
|
|
315
340
|
}).pipe(Stream.unwrapScoped)
|
|
316
|
-
}
|
|
317
341
|
|
|
318
342
|
const pullQueue: LeaderSyncProcessor['pullQueue'] = ({ cursor }) => {
|
|
319
343
|
const runtime = ctxRef.current?.runtime ?? shouldNeverHappen('Not initialized')
|
|
320
344
|
return Effect.gen(function* () {
|
|
321
|
-
const queue = yield* connectedClientSessionPullQueues.makeQueue
|
|
345
|
+
const queue = yield* connectedClientSessionPullQueues.makeQueue
|
|
322
346
|
const payloadsSinceCursor = Array.from(mergePayloads.entries())
|
|
323
347
|
.map(([mergeCounter, payload]) => ({ payload, mergeCounter }))
|
|
324
|
-
.filter(({ mergeCounter }) => mergeCounter > cursor)
|
|
348
|
+
.filter(({ mergeCounter }) => mergeCounter > cursor.mergeCounter)
|
|
325
349
|
.toSorted((a, b) => a.mergeCounter - b.mergeCounter)
|
|
350
|
+
.map(({ payload, mergeCounter }) => {
|
|
351
|
+
if (payload._tag === 'upstream-advance') {
|
|
352
|
+
return {
|
|
353
|
+
payload: {
|
|
354
|
+
_tag: 'upstream-advance' as const,
|
|
355
|
+
newEvents: ReadonlyArray.dropWhile(payload.newEvents, (eventEncoded) =>
|
|
356
|
+
EventId.isGreaterThanOrEqual(cursor.eventId, eventEncoded.id),
|
|
357
|
+
),
|
|
358
|
+
},
|
|
359
|
+
mergeCounter,
|
|
360
|
+
}
|
|
361
|
+
} else {
|
|
362
|
+
return { payload, mergeCounter }
|
|
363
|
+
}
|
|
364
|
+
})
|
|
326
365
|
|
|
327
366
|
yield* queue.offerAll(payloadsSinceCursor)
|
|
328
367
|
|
|
@@ -363,24 +402,33 @@ const backgroundApplyLocalPushes = ({
|
|
|
363
402
|
connectedClientSessionPullQueues,
|
|
364
403
|
mergeCounterRef,
|
|
365
404
|
mergePayloads,
|
|
405
|
+
localPushBatchSize,
|
|
406
|
+
testing,
|
|
366
407
|
}: {
|
|
367
408
|
pullLatch: Effect.Latch
|
|
368
409
|
localPushesLatch: Effect.Latch
|
|
369
410
|
localPushesQueue: BucketQueue.BucketQueue<LocalPushQueueItem>
|
|
370
411
|
syncStateSref: SubscriptionRef.SubscriptionRef<SyncState.SyncState | undefined>
|
|
371
|
-
syncBackendPushQueue: BucketQueue.BucketQueue<
|
|
412
|
+
syncBackendPushQueue: BucketQueue.BucketQueue<LiveStoreEvent.EncodedWithMeta>
|
|
372
413
|
schema: LiveStoreSchema
|
|
373
|
-
isClientEvent: (
|
|
414
|
+
isClientEvent: (eventEncoded: LiveStoreEvent.EncodedWithMeta) => boolean
|
|
374
415
|
otelSpan: otel.Span | undefined
|
|
375
416
|
currentLocalPushGenerationRef: { current: number }
|
|
376
417
|
connectedClientSessionPullQueues: PullQueueSet
|
|
377
418
|
mergeCounterRef: { current: number }
|
|
378
419
|
mergePayloads: Map<number, typeof SyncState.PayloadUpstream.Type>
|
|
420
|
+
localPushBatchSize: number
|
|
421
|
+
testing: {
|
|
422
|
+
delay: Effect.Effect<void> | undefined
|
|
423
|
+
}
|
|
379
424
|
}) =>
|
|
380
425
|
Effect.gen(function* () {
|
|
381
426
|
while (true) {
|
|
382
|
-
|
|
383
|
-
|
|
427
|
+
if (testing.delay !== undefined) {
|
|
428
|
+
yield* testing.delay.pipe(Effect.withSpan('localPushProcessingDelay'))
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const batchItems = yield* BucketQueue.takeBetween(localPushesQueue, 1, localPushBatchSize)
|
|
384
432
|
|
|
385
433
|
// Wait for the backend pulling to finish
|
|
386
434
|
yield* localPushesLatch.await
|
|
@@ -392,7 +440,7 @@ const backgroundApplyLocalPushes = ({
|
|
|
392
440
|
// It's important that we filter after we got localPushesLatch, otherwise we might filter with the old generation
|
|
393
441
|
const filteredBatchItems = batchItems
|
|
394
442
|
.filter(([_1, _2, generation]) => generation === currentLocalPushGenerationRef.current)
|
|
395
|
-
.map(([
|
|
443
|
+
.map(([eventEncoded, deferred]) => [eventEncoded, deferred] as const)
|
|
396
444
|
|
|
397
445
|
if (filteredBatchItems.length === 0) {
|
|
398
446
|
// console.log('dropping old-gen batch', currentLocalPushGenerationRef.current)
|
|
@@ -410,14 +458,14 @@ const backgroundApplyLocalPushes = ({
|
|
|
410
458
|
syncState,
|
|
411
459
|
payload: { _tag: 'local-push', newEvents },
|
|
412
460
|
isClientEvent,
|
|
413
|
-
isEqualEvent:
|
|
461
|
+
isEqualEvent: LiveStoreEvent.isEqualEncoded,
|
|
414
462
|
})
|
|
415
463
|
|
|
416
464
|
const mergeCounter = yield* incrementMergeCounter(mergeCounterRef)
|
|
417
465
|
|
|
418
466
|
switch (mergeResult._tag) {
|
|
419
467
|
case 'unexpected-error': {
|
|
420
|
-
otelSpan?.addEvent(`
|
|
468
|
+
otelSpan?.addEvent(`[${mergeCounter}]:push:unexpected-error`, {
|
|
421
469
|
batchSize: newEvents.length,
|
|
422
470
|
newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
|
|
423
471
|
})
|
|
@@ -427,7 +475,7 @@ const backgroundApplyLocalPushes = ({
|
|
|
427
475
|
return shouldNeverHappen('The leader thread should never have to rebase due to a local push')
|
|
428
476
|
}
|
|
429
477
|
case 'reject': {
|
|
430
|
-
otelSpan?.addEvent(`
|
|
478
|
+
otelSpan?.addEvent(`[${mergeCounter}]:push:reject`, {
|
|
431
479
|
batchSize: newEvents.length,
|
|
432
480
|
mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
|
433
481
|
})
|
|
@@ -491,28 +539,28 @@ const backgroundApplyLocalPushes = ({
|
|
|
491
539
|
})
|
|
492
540
|
mergePayloads.set(mergeCounter, SyncState.PayloadUpstreamAdvance.make({ newEvents: mergeResult.newEvents }))
|
|
493
541
|
|
|
494
|
-
otelSpan?.addEvent(`
|
|
542
|
+
otelSpan?.addEvent(`[${mergeCounter}]:push:advance`, {
|
|
495
543
|
batchSize: newEvents.length,
|
|
496
544
|
mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
|
497
545
|
})
|
|
498
546
|
|
|
499
|
-
// Don't sync clientOnly
|
|
500
|
-
const filteredBatch = mergeResult.newEvents.filter((
|
|
501
|
-
const
|
|
502
|
-
return
|
|
547
|
+
// Don't sync clientOnly events
|
|
548
|
+
const filteredBatch = mergeResult.newEvents.filter((eventEncoded) => {
|
|
549
|
+
const eventDef = getEventDef(schema, eventEncoded.name)
|
|
550
|
+
return eventDef.eventDef.options.clientOnly === false
|
|
503
551
|
})
|
|
504
552
|
|
|
505
553
|
yield* BucketQueue.offerAll(syncBackendPushQueue, filteredBatch)
|
|
506
554
|
|
|
507
|
-
yield*
|
|
555
|
+
yield* applyEventsBatch({ batchItems: mergeResult.newEvents, deferreds })
|
|
508
556
|
|
|
509
557
|
// Allow the backend pulling to start
|
|
510
558
|
yield* pullLatch.open
|
|
511
559
|
}
|
|
512
560
|
})
|
|
513
561
|
|
|
514
|
-
type
|
|
515
|
-
batchItems: ReadonlyArray<
|
|
562
|
+
type ApplyEventsBatch = (_: {
|
|
563
|
+
batchItems: ReadonlyArray<LiveStoreEvent.EncodedWithMeta>
|
|
516
564
|
/**
|
|
517
565
|
* The deferreds are used by the caller to know when the mutation has been processed.
|
|
518
566
|
* Indexes are aligned with `batchItems`
|
|
@@ -521,13 +569,13 @@ type ApplyMutationsBatch = (_: {
|
|
|
521
569
|
}) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx>
|
|
522
570
|
|
|
523
571
|
// TODO how to handle errors gracefully
|
|
524
|
-
const
|
|
572
|
+
const applyEventsBatch: ApplyEventsBatch = ({ batchItems, deferreds }) =>
|
|
525
573
|
Effect.gen(function* () {
|
|
526
|
-
const { dbReadModel: db,
|
|
574
|
+
const { dbReadModel: db, dbEventlog, applyEvent } = yield* LeaderThreadCtx
|
|
527
575
|
|
|
528
|
-
// NOTE We always start a transaction to ensure consistency between db and
|
|
576
|
+
// NOTE We always start a transaction to ensure consistency between db and eventlog (even for single-item batches)
|
|
529
577
|
db.execute('BEGIN TRANSACTION', undefined) // Start the transaction
|
|
530
|
-
|
|
578
|
+
dbEventlog.execute('BEGIN TRANSACTION', undefined) // Start the transaction
|
|
531
579
|
|
|
532
580
|
yield* Effect.addFinalizer((exit) =>
|
|
533
581
|
Effect.gen(function* () {
|
|
@@ -535,12 +583,12 @@ const applyMutationsBatch: ApplyMutationsBatch = ({ batchItems, deferreds }) =>
|
|
|
535
583
|
|
|
536
584
|
// Rollback in case of an error
|
|
537
585
|
db.execute('ROLLBACK', undefined)
|
|
538
|
-
|
|
586
|
+
dbEventlog.execute('ROLLBACK', undefined)
|
|
539
587
|
}),
|
|
540
588
|
)
|
|
541
589
|
|
|
542
590
|
for (let i = 0; i < batchItems.length; i++) {
|
|
543
|
-
const { sessionChangeset } = yield*
|
|
591
|
+
const { sessionChangeset } = yield* applyEvent(batchItems[i]!)
|
|
544
592
|
batchItems[i]!.meta.sessionChangeset = sessionChangeset
|
|
545
593
|
|
|
546
594
|
if (deferreds?.[i] !== undefined) {
|
|
@@ -549,11 +597,11 @@ const applyMutationsBatch: ApplyMutationsBatch = ({ batchItems, deferreds }) =>
|
|
|
549
597
|
}
|
|
550
598
|
|
|
551
599
|
db.execute('COMMIT', undefined) // Commit the transaction
|
|
552
|
-
|
|
600
|
+
dbEventlog.execute('COMMIT', undefined) // Commit the transaction
|
|
553
601
|
}).pipe(
|
|
554
602
|
Effect.uninterruptible,
|
|
555
603
|
Effect.scoped,
|
|
556
|
-
Effect.withSpan('@livestore/common:LeaderSyncProcessor:
|
|
604
|
+
Effect.withSpan('@livestore/common:LeaderSyncProcessor:applyEventItems', {
|
|
557
605
|
attributes: { batchSize: batchItems.length },
|
|
558
606
|
}),
|
|
559
607
|
Effect.tapCauseLogPretty,
|
|
@@ -573,11 +621,12 @@ const backgroundBackendPulling = ({
|
|
|
573
621
|
connectedClientSessionPullQueues,
|
|
574
622
|
mergeCounterRef,
|
|
575
623
|
mergePayloads,
|
|
624
|
+
advancePushHead,
|
|
576
625
|
}: {
|
|
577
626
|
initialBackendHead: EventId.GlobalEventId
|
|
578
|
-
isClientEvent: (
|
|
627
|
+
isClientEvent: (eventEncoded: LiveStoreEvent.EncodedWithMeta) => boolean
|
|
579
628
|
restartBackendPushing: (
|
|
580
|
-
filteredRebasedPending: ReadonlyArray<
|
|
629
|
+
filteredRebasedPending: ReadonlyArray<LiveStoreEvent.EncodedWithMeta>,
|
|
581
630
|
) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx | HttpClient.HttpClient>
|
|
582
631
|
otelSpan: otel.Span | undefined
|
|
583
632
|
syncStateSref: SubscriptionRef.SubscriptionRef<SyncState.SyncState | undefined>
|
|
@@ -588,13 +637,14 @@ const backgroundBackendPulling = ({
|
|
|
588
637
|
connectedClientSessionPullQueues: PullQueueSet
|
|
589
638
|
mergeCounterRef: { current: number }
|
|
590
639
|
mergePayloads: Map<number, typeof SyncState.PayloadUpstream.Type>
|
|
640
|
+
advancePushHead: (eventId: EventId.EventId) => void
|
|
591
641
|
}) =>
|
|
592
642
|
Effect.gen(function* () {
|
|
593
|
-
const { syncBackend, dbReadModel: db,
|
|
643
|
+
const { syncBackend, dbReadModel: db, dbEventlog, schema } = yield* LeaderThreadCtx
|
|
594
644
|
|
|
595
645
|
if (syncBackend === undefined) return
|
|
596
646
|
|
|
597
|
-
const onNewPullChunk = (newEvents:
|
|
647
|
+
const onNewPullChunk = (newEvents: LiveStoreEvent.EncodedWithMeta[], remaining: number) =>
|
|
598
648
|
Effect.gen(function* () {
|
|
599
649
|
if (newEvents.length === 0) return
|
|
600
650
|
|
|
@@ -615,7 +665,7 @@ const backgroundBackendPulling = ({
|
|
|
615
665
|
syncState,
|
|
616
666
|
payload: SyncState.PayloadUpstreamAdvance.make({ newEvents }),
|
|
617
667
|
isClientEvent,
|
|
618
|
-
isEqualEvent:
|
|
668
|
+
isEqualEvent: LiveStoreEvent.isEqualEncoded,
|
|
619
669
|
ignoreClientEvents: true,
|
|
620
670
|
})
|
|
621
671
|
|
|
@@ -624,7 +674,7 @@ const backgroundBackendPulling = ({
|
|
|
624
674
|
if (mergeResult._tag === 'reject') {
|
|
625
675
|
return shouldNeverHappen('The leader thread should never reject upstream advances')
|
|
626
676
|
} else if (mergeResult._tag === 'unexpected-error') {
|
|
627
|
-
otelSpan?.addEvent(`
|
|
677
|
+
otelSpan?.addEvent(`[${mergeCounter}]:pull:unexpected-error`, {
|
|
628
678
|
newEventsCount: newEvents.length,
|
|
629
679
|
newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
|
|
630
680
|
})
|
|
@@ -633,24 +683,24 @@ const backgroundBackendPulling = ({
|
|
|
633
683
|
|
|
634
684
|
const newBackendHead = newEvents.at(-1)!.id
|
|
635
685
|
|
|
636
|
-
|
|
686
|
+
Eventlog.updateBackendHead(dbEventlog, newBackendHead)
|
|
637
687
|
|
|
638
688
|
if (mergeResult._tag === 'rebase') {
|
|
639
|
-
otelSpan?.addEvent(`
|
|
689
|
+
otelSpan?.addEvent(`[${mergeCounter}]:pull:rebase`, {
|
|
640
690
|
newEventsCount: newEvents.length,
|
|
641
691
|
newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
|
|
642
692
|
rollbackCount: mergeResult.rollbackEvents.length,
|
|
643
693
|
mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
|
644
694
|
})
|
|
645
695
|
|
|
646
|
-
const globalRebasedPendingEvents = mergeResult.newSyncState.pending.filter((
|
|
647
|
-
const
|
|
648
|
-
return
|
|
696
|
+
const globalRebasedPendingEvents = mergeResult.newSyncState.pending.filter((event) => {
|
|
697
|
+
const eventDef = getEventDef(schema, event.name)
|
|
698
|
+
return eventDef.eventDef.options.clientOnly === false
|
|
649
699
|
})
|
|
650
700
|
yield* restartBackendPushing(globalRebasedPendingEvents)
|
|
651
701
|
|
|
652
702
|
if (mergeResult.rollbackEvents.length > 0) {
|
|
653
|
-
yield* rollback({ db,
|
|
703
|
+
yield* rollback({ db, dbEventlog, eventIdsToRollback: mergeResult.rollbackEvents.map((_) => _.id) })
|
|
654
704
|
}
|
|
655
705
|
|
|
656
706
|
yield* connectedClientSessionPullQueues.offer({
|
|
@@ -668,7 +718,7 @@ const backgroundBackendPulling = ({
|
|
|
668
718
|
}),
|
|
669
719
|
)
|
|
670
720
|
} else {
|
|
671
|
-
otelSpan?.addEvent(`
|
|
721
|
+
otelSpan?.addEvent(`[${mergeCounter}]:pull:advance`, {
|
|
672
722
|
newEventsCount: newEvents.length,
|
|
673
723
|
mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
|
674
724
|
})
|
|
@@ -682,19 +732,19 @@ const backgroundBackendPulling = ({
|
|
|
682
732
|
if (mergeResult.confirmedEvents.length > 0) {
|
|
683
733
|
// `mergeResult.confirmedEvents` don't contain the correct sync metadata, so we need to use
|
|
684
734
|
// `newEvents` instead which we filter via `mergeResult.confirmedEvents`
|
|
685
|
-
const confirmedNewEvents = newEvents.filter((
|
|
686
|
-
mergeResult.confirmedEvents.some((confirmedEvent) =>
|
|
687
|
-
EventId.isEqual(mutationEvent.id, confirmedEvent.id),
|
|
688
|
-
),
|
|
735
|
+
const confirmedNewEvents = newEvents.filter((event) =>
|
|
736
|
+
mergeResult.confirmedEvents.some((confirmedEvent) => EventId.isEqual(event.id, confirmedEvent.id)),
|
|
689
737
|
)
|
|
690
|
-
yield*
|
|
738
|
+
yield* Eventlog.updateSyncMetadata(confirmedNewEvents)
|
|
691
739
|
}
|
|
692
740
|
}
|
|
693
741
|
|
|
694
742
|
// Removes the changeset rows which are no longer needed as we'll never have to rollback beyond this point
|
|
695
743
|
trimChangesetRows(db, newBackendHead)
|
|
696
744
|
|
|
697
|
-
|
|
745
|
+
advancePushHead(mergeResult.newSyncState.localHead)
|
|
746
|
+
|
|
747
|
+
yield* applyEventsBatch({ batchItems: mergeResult.newEvents, deferreds: undefined })
|
|
698
748
|
|
|
699
749
|
yield* SubscriptionRef.set(syncStateSref, mergeResult.newSyncState)
|
|
700
750
|
|
|
@@ -704,7 +754,7 @@ const backgroundBackendPulling = ({
|
|
|
704
754
|
}
|
|
705
755
|
})
|
|
706
756
|
|
|
707
|
-
const cursorInfo = yield*
|
|
757
|
+
const cursorInfo = yield* Eventlog.getSyncBackendCursorInfo(initialBackendHead)
|
|
708
758
|
|
|
709
759
|
yield* syncBackend.pull(cursorInfo).pipe(
|
|
710
760
|
// TODO only take from queue while connected
|
|
@@ -717,13 +767,13 @@ const backgroundBackendPulling = ({
|
|
|
717
767
|
// },
|
|
718
768
|
// })
|
|
719
769
|
|
|
720
|
-
// NOTE we only want to take process
|
|
770
|
+
// NOTE we only want to take process events when the sync backend is connected
|
|
721
771
|
// (e.g. needed for simulating being offline)
|
|
722
772
|
// TODO remove when there's a better way to handle this in stream above
|
|
723
773
|
yield* SubscriptionRef.waitUntil(syncBackend.isConnected, (isConnected) => isConnected === true)
|
|
724
774
|
|
|
725
775
|
yield* onNewPullChunk(
|
|
726
|
-
batch.map((_) =>
|
|
776
|
+
batch.map((_) => LiveStoreEvent.EncodedWithMeta.fromGlobal(_.eventEncoded, _.metadata)),
|
|
727
777
|
remaining,
|
|
728
778
|
)
|
|
729
779
|
|
|
@@ -739,10 +789,12 @@ const backgroundBackendPushing = ({
|
|
|
739
789
|
syncBackendPushQueue,
|
|
740
790
|
otelSpan,
|
|
741
791
|
devtoolsLatch,
|
|
792
|
+
backendPushBatchSize,
|
|
742
793
|
}: {
|
|
743
|
-
syncBackendPushQueue: BucketQueue.BucketQueue<
|
|
794
|
+
syncBackendPushQueue: BucketQueue.BucketQueue<LiveStoreEvent.EncodedWithMeta>
|
|
744
795
|
otelSpan: otel.Span | undefined
|
|
745
796
|
devtoolsLatch: Effect.Latch | undefined
|
|
797
|
+
backendPushBatchSize: number
|
|
746
798
|
}) =>
|
|
747
799
|
Effect.gen(function* () {
|
|
748
800
|
const { syncBackend } = yield* LeaderThreadCtx
|
|
@@ -751,8 +803,7 @@ const backgroundBackendPushing = ({
|
|
|
751
803
|
while (true) {
|
|
752
804
|
yield* SubscriptionRef.waitUntil(syncBackend.isConnected, (isConnected) => isConnected === true)
|
|
753
805
|
|
|
754
|
-
|
|
755
|
-
const queueItems = yield* BucketQueue.takeBetween(syncBackendPushQueue, 1, BACKEND_PUSH_BATCH_SIZE)
|
|
806
|
+
const queueItems = yield* BucketQueue.takeBetween(syncBackendPushQueue, 1, backendPushBatchSize)
|
|
756
807
|
|
|
757
808
|
yield* SubscriptionRef.waitUntil(syncBackend.isConnected, (isConnected) => isConnected === true)
|
|
758
809
|
|
|
@@ -786,9 +837,7 @@ const trimChangesetRows = (db: SqliteDb, newHead: EventId.EventId) => {
|
|
|
786
837
|
}
|
|
787
838
|
|
|
788
839
|
interface PullQueueSet {
|
|
789
|
-
makeQueue:
|
|
790
|
-
cursor: number,
|
|
791
|
-
) => Effect.Effect<
|
|
840
|
+
makeQueue: Effect.Effect<
|
|
792
841
|
Queue.Queue<{ payload: typeof SyncState.PayloadUpstream.Type; mergeCounter: number }>,
|
|
793
842
|
UnexpectedError,
|
|
794
843
|
Scope.Scope | LeaderThreadCtx
|
|
@@ -812,19 +861,18 @@ const makePullQueueSet = Effect.gen(function* () {
|
|
|
812
861
|
}),
|
|
813
862
|
)
|
|
814
863
|
|
|
815
|
-
const makeQueue: PullQueueSet['makeQueue'] = ()
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
}>().pipe(Effect.acquireRelease(Queue.shutdown))
|
|
864
|
+
const makeQueue: PullQueueSet['makeQueue'] = Effect.gen(function* () {
|
|
865
|
+
const queue = yield* Queue.unbounded<{
|
|
866
|
+
payload: typeof SyncState.PayloadUpstream.Type
|
|
867
|
+
mergeCounter: number
|
|
868
|
+
}>().pipe(Effect.acquireRelease(Queue.shutdown))
|
|
821
869
|
|
|
822
|
-
|
|
870
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => set.delete(queue)))
|
|
823
871
|
|
|
824
|
-
|
|
872
|
+
set.add(queue)
|
|
825
873
|
|
|
826
|
-
|
|
827
|
-
|
|
874
|
+
return queue
|
|
875
|
+
})
|
|
828
876
|
|
|
829
877
|
const offer: PullQueueSet['offer'] = (item) =>
|
|
830
878
|
Effect.gen(function* () {
|
|
@@ -861,3 +909,27 @@ const getMergeCounterFromDb = (dbReadModel: SqliteDb) =>
|
|
|
861
909
|
)
|
|
862
910
|
return result[0]?.mergeCounter ?? 0
|
|
863
911
|
})
|
|
912
|
+
|
|
913
|
+
const validatePushBatch = (batch: ReadonlyArray<LiveStoreEvent.EncodedWithMeta>, pushHead: EventId.EventId) =>
|
|
914
|
+
Effect.gen(function* () {
|
|
915
|
+
if (batch.length === 0) {
|
|
916
|
+
return
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// Make sure batch is monotonically increasing
|
|
920
|
+
for (let i = 1; i < batch.length; i++) {
|
|
921
|
+
if (EventId.isGreaterThanOrEqual(batch[i - 1]!.id, batch[i]!.id)) {
|
|
922
|
+
shouldNeverHappen(
|
|
923
|
+
`Events must be ordered in monotonically ascending order by eventId. Received: [${batch.map((e) => EventId.toString(e.id)).join(', ')}]`,
|
|
924
|
+
)
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// Make sure smallest event id is > pushHead
|
|
929
|
+
if (EventId.isGreaterThanOrEqual(pushHead, batch[0]!.id)) {
|
|
930
|
+
return yield* LeaderAheadError.make({
|
|
931
|
+
minimumExpectedId: pushHead,
|
|
932
|
+
providedId: batch[0]!.id,
|
|
933
|
+
})
|
|
934
|
+
}
|
|
935
|
+
})
|