@livestore/common 0.3.1-dev.0 → 0.3.2-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/ClientSessionLeaderThreadProxy.d.ts +35 -0
- package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -0
- package/dist/ClientSessionLeaderThreadProxy.js +6 -0
- package/dist/ClientSessionLeaderThreadProxy.js.map +1 -0
- package/dist/adapter-types.d.ts +10 -156
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js +5 -49
- package/dist/adapter-types.js.map +1 -1
- package/dist/defs.d.ts +20 -0
- package/dist/defs.d.ts.map +1 -0
- package/dist/defs.js +12 -0
- package/dist/defs.js.map +1 -0
- package/dist/devtools/devtools-messages-client-session.d.ts +23 -21
- package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-common.d.ts +6 -6
- package/dist/devtools/devtools-messages-leader.d.ts +26 -24
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
- package/dist/errors.d.ts +50 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +36 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +6 -7
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +122 -123
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/eventlog.d.ts +17 -6
- package/dist/leader-thread/eventlog.d.ts.map +1 -1
- package/dist/leader-thread/eventlog.js +34 -17
- package/dist/leader-thread/eventlog.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +1 -2
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +37 -7
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/materialize-event.d.ts +3 -3
- package/dist/leader-thread/materialize-event.d.ts.map +1 -1
- package/dist/leader-thread/materialize-event.js +27 -10
- package/dist/leader-thread/materialize-event.js.map +1 -1
- package/dist/leader-thread/mod.d.ts +2 -0
- package/dist/leader-thread/mod.d.ts.map +1 -1
- package/dist/leader-thread/mod.js +2 -0
- package/dist/leader-thread/mod.js.map +1 -1
- package/dist/leader-thread/recreate-db.d.ts +13 -6
- package/dist/leader-thread/recreate-db.d.ts.map +1 -1
- package/dist/leader-thread/recreate-db.js +1 -3
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/leader-thread/types.d.ts +6 -7
- package/dist/leader-thread/types.d.ts.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 +1 -1
- package/dist/make-client-session.js.map +1 -1
- package/dist/materializer-helper.d.ts +13 -2
- package/dist/materializer-helper.d.ts.map +1 -1
- package/dist/materializer-helper.js +25 -11
- package/dist/materializer-helper.js.map +1 -1
- package/dist/rematerialize-from-eventlog.d.ts +1 -1
- package/dist/rematerialize-from-eventlog.d.ts.map +1 -1
- package/dist/rematerialize-from-eventlog.js +12 -4
- package/dist/rematerialize-from-eventlog.js.map +1 -1
- package/dist/schema/EventDef.d.ts +8 -3
- package/dist/schema/EventDef.d.ts.map +1 -1
- package/dist/schema/EventDef.js +5 -2
- package/dist/schema/EventDef.js.map +1 -1
- package/dist/schema/EventSequenceNumber.d.ts +20 -2
- package/dist/schema/EventSequenceNumber.d.ts.map +1 -1
- package/dist/schema/EventSequenceNumber.js +71 -19
- package/dist/schema/EventSequenceNumber.js.map +1 -1
- package/dist/schema/EventSequenceNumber.test.js +88 -3
- package/dist/schema/EventSequenceNumber.test.js.map +1 -1
- package/dist/schema/LiveStoreEvent.d.ts +56 -8
- package/dist/schema/LiveStoreEvent.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent.js +34 -8
- package/dist/schema/LiveStoreEvent.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +2 -2
- package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -1
- package/dist/schema/state/sqlite/db-schema/hash.js +3 -1
- package/dist/schema/state/sqlite/db-schema/hash.js.map +1 -1
- package/dist/schema/state/sqlite/mod.d.ts +1 -1
- package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/api.d.ts +36 -9
- package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/api.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.js +16 -11
- package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.test.d.ts +1 -86
- package/dist/schema/state/sqlite/query-builder/impl.test.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.test.js +34 -20
- package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
- package/dist/schema/state/sqlite/system-tables.d.ts +380 -432
- package/dist/schema/state/sqlite/system-tables.d.ts.map +1 -1
- package/dist/schema/state/sqlite/system-tables.js +8 -17
- package/dist/schema/state/sqlite/system-tables.js.map +1 -1
- package/dist/schema/state/sqlite/table-def.d.ts +2 -2
- package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
- package/dist/schema-management/migrations.d.ts +3 -1
- package/dist/schema-management/migrations.d.ts.map +1 -1
- package/dist/schema-management/migrations.js.map +1 -1
- package/dist/sql-queries/sql-queries.d.ts.map +1 -1
- package/dist/sql-queries/sql-queries.js +2 -0
- package/dist/sql-queries/sql-queries.js.map +1 -1
- package/dist/sqlite-db-helper.d.ts +7 -0
- package/dist/sqlite-db-helper.d.ts.map +1 -0
- package/dist/sqlite-db-helper.js +29 -0
- package/dist/sqlite-db-helper.js.map +1 -0
- package/dist/sqlite-types.d.ts +72 -0
- package/dist/sqlite-types.d.ts.map +1 -0
- package/dist/sqlite-types.js +5 -0
- package/dist/sqlite-types.js.map +1 -0
- package/dist/sync/ClientSessionSyncProcessor.d.ts +12 -3
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +37 -19
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/next/graphology.d.ts.map +1 -1
- package/dist/sync/next/graphology.js +0 -6
- package/dist/sync/next/graphology.js.map +1 -1
- package/dist/sync/next/rebase-events.d.ts.map +1 -1
- package/dist/sync/next/rebase-events.js +1 -0
- package/dist/sync/next/rebase-events.js.map +1 -1
- package/dist/sync/next/test/compact-events.test.js +1 -1
- package/dist/sync/next/test/compact-events.test.js.map +1 -1
- package/dist/sync/next/test/event-fixtures.d.ts.map +1 -1
- package/dist/sync/next/test/event-fixtures.js +12 -3
- package/dist/sync/next/test/event-fixtures.js.map +1 -1
- package/dist/sync/sync.d.ts +2 -0
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/sync.js +3 -0
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/syncstate.d.ts +13 -4
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +23 -10
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +17 -17
- package/dist/sync/syncstate.test.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -6
- package/src/ClientSessionLeaderThreadProxy.ts +40 -0
- package/src/adapter-types.ts +19 -161
- package/src/defs.ts +17 -0
- package/src/errors.ts +49 -0
- package/src/index.ts +1 -0
- package/src/leader-thread/LeaderSyncProcessor.ts +157 -181
- package/src/leader-thread/eventlog.ts +78 -54
- package/src/leader-thread/leader-worker-devtools.ts +1 -2
- package/src/leader-thread/make-leader-thread-layer.ts +52 -8
- package/src/leader-thread/materialize-event.ts +33 -12
- package/src/leader-thread/mod.ts +2 -0
- package/src/leader-thread/recreate-db.ts +99 -91
- package/src/leader-thread/types.ts +10 -12
- package/src/make-client-session.ts +2 -2
- package/src/materializer-helper.ts +45 -19
- package/src/rematerialize-from-eventlog.ts +12 -4
- package/src/schema/EventDef.ts +16 -4
- package/src/schema/EventSequenceNumber.test.ts +120 -3
- package/src/schema/EventSequenceNumber.ts +95 -23
- package/src/schema/LiveStoreEvent.ts +49 -8
- package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +2 -2
- package/src/schema/state/sqlite/db-schema/hash.ts +3 -3
- package/src/schema/state/sqlite/mod.ts +1 -1
- package/src/schema/state/sqlite/query-builder/api.ts +39 -9
- package/src/schema/state/sqlite/query-builder/impl.test.ts +60 -20
- package/src/schema/state/sqlite/query-builder/impl.ts +15 -12
- package/src/schema/state/sqlite/system-tables.ts +9 -22
- package/src/schema/state/sqlite/table-def.ts +2 -2
- package/src/schema-management/migrations.ts +3 -1
- package/src/sql-queries/sql-queries.ts +2 -0
- package/src/sqlite-db-helper.ts +41 -0
- package/src/sqlite-types.ts +76 -0
- package/src/sync/ClientSessionSyncProcessor.ts +51 -28
- package/src/sync/next/graphology.ts +0 -6
- package/src/sync/next/rebase-events.ts +1 -0
- package/src/sync/next/test/compact-events.test.ts +1 -1
- package/src/sync/next/test/event-fixtures.ts +12 -3
- package/src/sync/sync.ts +3 -0
- package/src/sync/syncstate.test.ts +17 -17
- package/src/sync/syncstate.ts +31 -10
- package/src/version.ts +1 -1
@@ -6,7 +6,9 @@ import {
|
|
6
6
|
Effect,
|
7
7
|
Exit,
|
8
8
|
FiberHandle,
|
9
|
+
Option,
|
9
10
|
OtelTracer,
|
11
|
+
pipe,
|
10
12
|
Queue,
|
11
13
|
ReadonlyArray,
|
12
14
|
Stream,
|
@@ -16,7 +18,8 @@ import {
|
|
16
18
|
import type * as otel from '@opentelemetry/api'
|
17
19
|
|
18
20
|
import type { SqliteDb } from '../adapter-types.js'
|
19
|
-
import { UnexpectedError } from '../adapter-types.js'
|
21
|
+
import { SyncError, UnexpectedError } from '../adapter-types.js'
|
22
|
+
import { makeMaterializerHash } from '../materializer-helper.js'
|
20
23
|
import type { LiveStoreSchema } from '../schema/mod.js'
|
21
24
|
import { EventSequenceNumber, getEventDef, LiveStoreEvent, SystemTables } from '../schema/mod.js'
|
22
25
|
import { LeaderAheadError } from '../sync/sync.js'
|
@@ -30,8 +33,6 @@ import { LeaderThreadCtx } from './types.js'
|
|
30
33
|
type LocalPushQueueItem = [
|
31
34
|
event: LiveStoreEvent.EncodedWithMeta,
|
32
35
|
deferred: Deferred.Deferred<void, LeaderAheadError> | undefined,
|
33
|
-
/** Used to determine whether the batch has become invalid due to a rejected local push batch */
|
34
|
-
generation: number,
|
35
36
|
]
|
36
37
|
|
37
38
|
/**
|
@@ -56,32 +57,28 @@ type LocalPushQueueItem = [
|
|
56
57
|
* - The latch closes on pull receipt and re-opens post-pull completion.
|
57
58
|
* - Processes up to `maxBatchSize` events per cycle.
|
58
59
|
*
|
59
|
-
* Currently we're advancing the db
|
60
|
+
* Currently we're advancing the state db and eventlog in lockstep, but we could also decouple this in the future
|
60
61
|
*
|
61
62
|
* Tricky concurrency scenarios:
|
62
63
|
* - Queued local push batches becoming invalid due to a prior local push item being rejected.
|
63
64
|
* Solution: Introduce a generation number for local push batches which is used to filter out old batches items in case of rejection.
|
64
65
|
*
|
66
|
+
* See ClientSessionSyncProcessor for how the leader and session sync processors are similar/different.
|
65
67
|
*/
|
66
68
|
export const makeLeaderSyncProcessor = ({
|
67
69
|
schema,
|
68
|
-
dbEventlogMissing,
|
69
|
-
dbEventlog,
|
70
70
|
dbState,
|
71
|
-
dbStateMissing,
|
72
71
|
initialBlockingSyncContext,
|
72
|
+
initialSyncState,
|
73
73
|
onError,
|
74
74
|
params,
|
75
75
|
testing,
|
76
76
|
}: {
|
77
77
|
schema: LiveStoreSchema
|
78
|
-
/** Only used to know whether we can safely query dbEventlog during setup execution */
|
79
|
-
dbEventlogMissing: boolean
|
80
|
-
dbEventlog: SqliteDb
|
81
78
|
dbState: SqliteDb
|
82
|
-
/** Only used to know whether we can safely query dbState during setup execution */
|
83
|
-
dbStateMissing: boolean
|
84
79
|
initialBlockingSyncContext: InitialBlockingSyncContext
|
80
|
+
/** Initial sync state rehydrated from the persisted eventlog or initial sync state */
|
81
|
+
initialSyncState: SyncState.SyncState
|
85
82
|
onError: 'shutdown' | 'ignore'
|
86
83
|
params: {
|
87
84
|
/**
|
@@ -113,18 +110,6 @@ export const makeLeaderSyncProcessor = ({
|
|
113
110
|
|
114
111
|
const connectedClientSessionPullQueues = yield* makePullQueueSet
|
115
112
|
|
116
|
-
/**
|
117
|
-
* Tracks generations of queued local push events.
|
118
|
-
* If a local-push batch is rejected, all subsequent push queue items with the same generation are also rejected,
|
119
|
-
* even if they would be valid on their own.
|
120
|
-
*/
|
121
|
-
// TODO get rid of this in favour of the `mergeGeneration` event sequence number field
|
122
|
-
const currentLocalPushGenerationRef = { current: 0 }
|
123
|
-
|
124
|
-
type MergeCounter = number
|
125
|
-
const mergeCounterRef = { current: dbStateMissing ? 0 : yield* getMergeCounterFromDb(dbState) }
|
126
|
-
const mergePayloads = new Map<MergeCounter, typeof SyncState.PayloadUpstream.Type>()
|
127
|
-
|
128
113
|
// This context depends on data from `boot`, we should find a better implementation to avoid this ref indirection.
|
129
114
|
const ctxRef = {
|
130
115
|
current: undefined as
|
@@ -148,7 +133,7 @@ export const makeLeaderSyncProcessor = ({
|
|
148
133
|
* - leader sync processor takes a bit and hasn't yet taken e1 from the localPushesQueue
|
149
134
|
* - client session B also pushes e1 (which should be rejected)
|
150
135
|
*
|
151
|
-
* Thus the
|
136
|
+
* Thus the purpose of the pushHeadRef is the guard the integrity of the local push queue
|
152
137
|
*/
|
153
138
|
const pushHeadRef = { current: EventSequenceNumber.ROOT }
|
154
139
|
const advancePushHead = (eventNum: EventSequenceNumber.EventSequenceNumber) => {
|
@@ -160,25 +145,24 @@ export const makeLeaderSyncProcessor = ({
|
|
160
145
|
Effect.gen(function* () {
|
161
146
|
if (newEvents.length === 0) return
|
162
147
|
|
148
|
+
// console.debug('push', newEvents)
|
149
|
+
|
163
150
|
yield* validatePushBatch(newEvents, pushHeadRef.current)
|
164
151
|
|
165
152
|
advancePushHead(newEvents.at(-1)!.seqNum)
|
166
153
|
|
167
154
|
const waitForProcessing = options?.waitForProcessing ?? false
|
168
|
-
const generation = currentLocalPushGenerationRef.current
|
169
155
|
|
170
156
|
if (waitForProcessing) {
|
171
157
|
const deferreds = yield* Effect.forEach(newEvents, () => Deferred.make<void, LeaderAheadError>())
|
172
158
|
|
173
|
-
const items = newEvents.map(
|
174
|
-
(eventEncoded, i) => [eventEncoded, deferreds[i], generation] as LocalPushQueueItem,
|
175
|
-
)
|
159
|
+
const items = newEvents.map((eventEncoded, i) => [eventEncoded, deferreds[i]] as LocalPushQueueItem)
|
176
160
|
|
177
161
|
yield* BucketQueue.offerAll(localPushesQueue, items)
|
178
162
|
|
179
163
|
yield* Effect.all(deferreds)
|
180
164
|
} else {
|
181
|
-
const items = newEvents.map((eventEncoded) => [eventEncoded, undefined
|
165
|
+
const items = newEvents.map((eventEncoded) => [eventEncoded, undefined] as LocalPushQueueItem)
|
182
166
|
yield* BucketQueue.offerAll(localPushesQueue, items)
|
183
167
|
}
|
184
168
|
}).pipe(
|
@@ -203,7 +187,7 @@ export const makeLeaderSyncProcessor = ({
|
|
203
187
|
args,
|
204
188
|
clientId,
|
205
189
|
sessionId,
|
206
|
-
...EventSequenceNumber.nextPair(syncState.localHead, eventDef.options.clientOnly),
|
190
|
+
...EventSequenceNumber.nextPair({ seqNum: syncState.localHead, isClient: eventDef.options.clientOnly }),
|
207
191
|
})
|
208
192
|
|
209
193
|
yield* push([eventEncoded])
|
@@ -223,34 +207,12 @@ export const makeLeaderSyncProcessor = ({
|
|
223
207
|
runtime,
|
224
208
|
}
|
225
209
|
|
226
|
-
const initialLocalHead = dbEventlogMissing ? EventSequenceNumber.ROOT : Eventlog.getClientHeadFromDb(dbEventlog)
|
227
|
-
|
228
|
-
const initialBackendHead = dbEventlogMissing
|
229
|
-
? EventSequenceNumber.ROOT.global
|
230
|
-
: Eventlog.getBackendHeadFromDb(dbEventlog)
|
231
|
-
|
232
|
-
if (initialBackendHead > initialLocalHead.global) {
|
233
|
-
return shouldNeverHappen(
|
234
|
-
`During boot the backend head (${initialBackendHead}) should never be greater than the local head (${initialLocalHead.global})`,
|
235
|
-
)
|
236
|
-
}
|
237
|
-
|
238
|
-
const pendingEvents = dbEventlogMissing
|
239
|
-
? []
|
240
|
-
: yield* Eventlog.getEventsSince({ global: initialBackendHead, client: EventSequenceNumber.clientDefault })
|
241
|
-
|
242
|
-
const initialSyncState = new SyncState.SyncState({
|
243
|
-
pending: pendingEvents,
|
244
|
-
upstreamHead: { global: initialBackendHead, client: EventSequenceNumber.clientDefault },
|
245
|
-
localHead: initialLocalHead,
|
246
|
-
})
|
247
|
-
|
248
210
|
/** State transitions need to happen atomically, so we use a Ref to track the state */
|
249
211
|
yield* SubscriptionRef.set(syncStateSref, initialSyncState)
|
250
212
|
|
251
213
|
// Rehydrate sync queue
|
252
|
-
if (
|
253
|
-
const globalPendingEvents =
|
214
|
+
if (initialSyncState.pending.length > 0) {
|
215
|
+
const globalPendingEvents = initialSyncState.pending
|
254
216
|
// Don't sync clientOnly events
|
255
217
|
.filter((eventEncoded) => {
|
256
218
|
const { eventDef } = getEventDef(schema, eventEncoded.name)
|
@@ -279,10 +241,7 @@ export const makeLeaderSyncProcessor = ({
|
|
279
241
|
schema,
|
280
242
|
isClientEvent,
|
281
243
|
otelSpan,
|
282
|
-
currentLocalPushGenerationRef,
|
283
244
|
connectedClientSessionPullQueues,
|
284
|
-
mergeCounterRef,
|
285
|
-
mergePayloads,
|
286
245
|
localPushBatchSize,
|
287
246
|
testing: {
|
288
247
|
delay: testing?.delays?.localPushProcessing,
|
@@ -300,7 +259,7 @@ export const makeLeaderSyncProcessor = ({
|
|
300
259
|
yield* FiberHandle.run(backendPushingFiberHandle, backendPushingEffect)
|
301
260
|
|
302
261
|
yield* backgroundBackendPulling({
|
303
|
-
initialBackendHead,
|
262
|
+
initialBackendHead: initialSyncState.upstreamHead.global,
|
304
263
|
isClientEvent,
|
305
264
|
restartBackendPushing: (filteredRebasedPending) =>
|
306
265
|
Effect.gen(function* () {
|
@@ -317,16 +276,15 @@ export const makeLeaderSyncProcessor = ({
|
|
317
276
|
syncStateSref,
|
318
277
|
localPushesLatch,
|
319
278
|
pullLatch,
|
279
|
+
dbState,
|
320
280
|
otelSpan,
|
321
281
|
initialBlockingSyncContext,
|
322
282
|
devtoolsLatch: ctxRef.current?.devtoolsLatch,
|
323
283
|
connectedClientSessionPullQueues,
|
324
|
-
mergeCounterRef,
|
325
|
-
mergePayloads,
|
326
284
|
advancePushHead,
|
327
285
|
}).pipe(Effect.tapCauseLogPretty, Effect.catchAllCause(shutdownOnError), Effect.forkScoped)
|
328
286
|
|
329
|
-
return { initialLeaderHead:
|
287
|
+
return { initialLeaderHead: initialSyncState.localHead }
|
330
288
|
}).pipe(Effect.withSpanScoped('@livestore/common:LeaderSyncProcessor:boot'))
|
331
289
|
|
332
290
|
const pull: LeaderSyncProcessor['pull'] = ({ cursor }) =>
|
@@ -335,34 +293,23 @@ export const makeLeaderSyncProcessor = ({
|
|
335
293
|
return Stream.fromQueue(queue)
|
336
294
|
}).pipe(Stream.unwrapScoped)
|
337
295
|
|
338
|
-
|
339
|
-
|
340
|
-
return Effect.gen(function* () {
|
341
|
-
const queue = yield* connectedClientSessionPullQueues.makeQueue
|
342
|
-
const payloadsSinceCursor = Array.from(mergePayloads.entries())
|
343
|
-
.map(([mergeCounter, payload]) => ({ payload, mergeCounter }))
|
344
|
-
.filter(({ mergeCounter }) => mergeCounter > cursor.mergeCounter)
|
345
|
-
.toSorted((a, b) => a.mergeCounter - b.mergeCounter)
|
346
|
-
.map(({ payload, mergeCounter }) => {
|
347
|
-
if (payload._tag === 'upstream-advance') {
|
348
|
-
return {
|
349
|
-
payload: {
|
350
|
-
_tag: 'upstream-advance' as const,
|
351
|
-
newEvents: ReadonlyArray.dropWhile(payload.newEvents, (eventEncoded) =>
|
352
|
-
EventSequenceNumber.isGreaterThanOrEqual(cursor.eventNum, eventEncoded.seqNum),
|
353
|
-
),
|
354
|
-
},
|
355
|
-
mergeCounter,
|
356
|
-
}
|
357
|
-
} else {
|
358
|
-
return { payload, mergeCounter }
|
359
|
-
}
|
360
|
-
})
|
296
|
+
/*
|
297
|
+
Notes for a potential new `LeaderSyncProcessor.pull` implementation:
|
361
298
|
|
362
|
-
|
299
|
+
- Doesn't take cursor but is "atomically called" in the leader during the snapshot phase
|
300
|
+
- TODO: how is this done "atomically" in the web adapter where the snapshot is read optimistically?
|
301
|
+
- Would require a new kind of "boot-phase" API which is stream based:
|
302
|
+
- initial message: state snapshot + seq num head
|
303
|
+
- subsequent messages: sync state payloads
|
363
304
|
|
364
|
-
|
365
|
-
|
305
|
+
- alternative: instead of session pulling sync state payloads from leader, we could send
|
306
|
+
- events in the "advance" case
|
307
|
+
- full new state db snapshot in the "rebase" case
|
308
|
+
- downside: importing the snapshot is expensive
|
309
|
+
*/
|
310
|
+
const pullQueue: LeaderSyncProcessor['pullQueue'] = ({ cursor }) => {
|
311
|
+
const runtime = ctxRef.current?.runtime ?? shouldNeverHappen('Not initialized')
|
312
|
+
return connectedClientSessionPullQueues.makeQueue(cursor).pipe(Effect.provide(runtime))
|
366
313
|
}
|
367
314
|
|
368
315
|
const syncState = Subscribable.make({
|
@@ -381,7 +328,6 @@ export const makeLeaderSyncProcessor = ({
|
|
381
328
|
pushPartial,
|
382
329
|
boot,
|
383
330
|
syncState,
|
384
|
-
getMergeCounter: () => mergeCounterRef.current,
|
385
331
|
} satisfies LeaderSyncProcessor
|
386
332
|
})
|
387
333
|
|
@@ -394,10 +340,7 @@ const backgroundApplyLocalPushes = ({
|
|
394
340
|
schema,
|
395
341
|
isClientEvent,
|
396
342
|
otelSpan,
|
397
|
-
currentLocalPushGenerationRef,
|
398
343
|
connectedClientSessionPullQueues,
|
399
|
-
mergeCounterRef,
|
400
|
-
mergePayloads,
|
401
344
|
localPushBatchSize,
|
402
345
|
testing,
|
403
346
|
}: {
|
@@ -409,10 +352,7 @@ const backgroundApplyLocalPushes = ({
|
|
409
352
|
schema: LiveStoreSchema
|
410
353
|
isClientEvent: (eventEncoded: LiveStoreEvent.EncodedWithMeta) => boolean
|
411
354
|
otelSpan: otel.Span | undefined
|
412
|
-
currentLocalPushGenerationRef: { current: number }
|
413
355
|
connectedClientSessionPullQueues: PullQueueSet
|
414
|
-
mergeCounterRef: { current: number }
|
415
|
-
mergePayloads: Map<number, typeof SyncState.PayloadUpstream.Type>
|
416
356
|
localPushBatchSize: number
|
417
357
|
testing: {
|
418
358
|
delay: Effect.Effect<void> | undefined
|
@@ -432,24 +372,26 @@ const backgroundApplyLocalPushes = ({
|
|
432
372
|
// Prevent backend pull processing until this local push is finished
|
433
373
|
yield* pullLatch.close
|
434
374
|
|
435
|
-
|
375
|
+
const syncState = yield* syncStateSref
|
376
|
+
if (syncState === undefined) return shouldNeverHappen('Not initialized')
|
377
|
+
|
378
|
+
const currentRebaseGeneration = syncState.localHead.rebaseGeneration
|
379
|
+
|
380
|
+
// Since the rebase generation might have changed since enqueuing, we need to filter out items with older generation
|
436
381
|
// It's important that we filter after we got localPushesLatch, otherwise we might filter with the old generation
|
437
|
-
const
|
438
|
-
|
439
|
-
.
|
382
|
+
const [newEvents, deferreds] = pipe(
|
383
|
+
batchItems,
|
384
|
+
ReadonlyArray.filter(([eventEncoded]) => eventEncoded.seqNum.rebaseGeneration === currentRebaseGeneration),
|
385
|
+
ReadonlyArray.unzip,
|
386
|
+
)
|
440
387
|
|
441
|
-
if (
|
388
|
+
if (newEvents.length === 0) {
|
442
389
|
// console.log('dropping old-gen batch', currentLocalPushGenerationRef.current)
|
443
390
|
// Allow the backend pulling to start
|
444
391
|
yield* pullLatch.open
|
445
392
|
continue
|
446
393
|
}
|
447
394
|
|
448
|
-
const [newEvents, deferreds] = ReadonlyArray.unzip(filteredBatchItems)
|
449
|
-
|
450
|
-
const syncState = yield* syncStateSref
|
451
|
-
if (syncState === undefined) return shouldNeverHappen('Not initialized')
|
452
|
-
|
453
395
|
const mergeResult = SyncState.merge({
|
454
396
|
syncState,
|
455
397
|
payload: { _tag: 'local-push', newEvents },
|
@@ -457,29 +399,25 @@ const backgroundApplyLocalPushes = ({
|
|
457
399
|
isEqualEvent: LiveStoreEvent.isEqualEncoded,
|
458
400
|
})
|
459
401
|
|
460
|
-
const mergeCounter = yield* incrementMergeCounter(mergeCounterRef)
|
461
|
-
|
462
402
|
switch (mergeResult._tag) {
|
463
403
|
case 'unexpected-error': {
|
464
|
-
otelSpan?.addEvent(`
|
404
|
+
otelSpan?.addEvent(`push:unexpected-error`, {
|
465
405
|
batchSize: newEvents.length,
|
466
406
|
newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
|
467
407
|
})
|
468
|
-
return yield*
|
408
|
+
return yield* new SyncError({ cause: mergeResult.message })
|
469
409
|
}
|
470
410
|
case 'rebase': {
|
471
411
|
return shouldNeverHappen('The leader thread should never have to rebase due to a local push')
|
472
412
|
}
|
473
413
|
case 'reject': {
|
474
|
-
otelSpan?.addEvent(`
|
414
|
+
otelSpan?.addEvent(`push:reject`, {
|
475
415
|
batchSize: newEvents.length,
|
476
416
|
mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
477
417
|
})
|
478
418
|
|
479
419
|
// TODO: how to test this?
|
480
|
-
|
481
|
-
|
482
|
-
const nextGeneration = currentLocalPushGenerationRef.current
|
420
|
+
const nextRebaseGeneration = currentRebaseGeneration + 1
|
483
421
|
|
484
422
|
const providedNum = newEvents.at(0)!.seqNum
|
485
423
|
// All subsequent pushes with same generation should be rejected as well
|
@@ -487,12 +425,13 @@ const backgroundApplyLocalPushes = ({
|
|
487
425
|
// from the next generation which we preserve in the queue
|
488
426
|
const remainingEventsMatchingGeneration = yield* BucketQueue.takeSplitWhere(
|
489
427
|
localPushesQueue,
|
490
|
-
(
|
428
|
+
([eventEncoded]) => eventEncoded.seqNum.rebaseGeneration >= nextRebaseGeneration,
|
491
429
|
)
|
492
430
|
|
493
431
|
// TODO we still need to better understand and handle this scenario
|
494
432
|
if (LS_DEV && (yield* BucketQueue.size(localPushesQueue)) > 0) {
|
495
433
|
console.log('localPushesQueue is not empty', yield* BucketQueue.size(localPushesQueue))
|
434
|
+
// biome-ignore lint/suspicious/noDebugger: debugging
|
496
435
|
debugger
|
497
436
|
}
|
498
437
|
|
@@ -504,11 +443,7 @@ const backgroundApplyLocalPushes = ({
|
|
504
443
|
yield* Effect.forEach(allDeferredsToReject, (deferred) =>
|
505
444
|
Deferred.fail(
|
506
445
|
deferred,
|
507
|
-
LeaderAheadError.make({
|
508
|
-
minimumExpectedNum: mergeResult.expectedMinimumId,
|
509
|
-
providedNum,
|
510
|
-
// nextGeneration,
|
511
|
-
}),
|
446
|
+
LeaderAheadError.make({ minimumExpectedNum: mergeResult.expectedMinimumId, providedNum }),
|
512
447
|
),
|
513
448
|
)
|
514
449
|
|
@@ -531,11 +466,10 @@ const backgroundApplyLocalPushes = ({
|
|
531
466
|
|
532
467
|
yield* connectedClientSessionPullQueues.offer({
|
533
468
|
payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: mergeResult.newEvents }),
|
534
|
-
|
469
|
+
leaderHead: mergeResult.newSyncState.localHead,
|
535
470
|
})
|
536
|
-
mergePayloads.set(mergeCounter, SyncState.PayloadUpstreamAdvance.make({ newEvents: mergeResult.newEvents }))
|
537
471
|
|
538
|
-
otelSpan?.addEvent(`
|
472
|
+
otelSpan?.addEvent(`push:advance`, {
|
539
473
|
batchSize: newEvents.length,
|
540
474
|
mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
541
475
|
})
|
@@ -584,8 +518,9 @@ const materializeEventsBatch: MaterializeEventsBatch = ({ batchItems, deferreds
|
|
584
518
|
)
|
585
519
|
|
586
520
|
for (let i = 0; i < batchItems.length; i++) {
|
587
|
-
const { sessionChangeset } = yield* materializeEvent(batchItems[i]!)
|
521
|
+
const { sessionChangeset, hash } = yield* materializeEvent(batchItems[i]!)
|
588
522
|
batchItems[i]!.meta.sessionChangeset = sessionChangeset
|
523
|
+
batchItems[i]!.meta.materializerHashLeader = hash
|
589
524
|
|
590
525
|
if (deferreds?.[i] !== undefined) {
|
591
526
|
yield* Deferred.succeed(deferreds[i]!, void 0)
|
@@ -609,14 +544,13 @@ const backgroundBackendPulling = ({
|
|
609
544
|
isClientEvent,
|
610
545
|
restartBackendPushing,
|
611
546
|
otelSpan,
|
547
|
+
dbState,
|
612
548
|
syncStateSref,
|
613
549
|
localPushesLatch,
|
614
550
|
pullLatch,
|
615
551
|
devtoolsLatch,
|
616
552
|
initialBlockingSyncContext,
|
617
553
|
connectedClientSessionPullQueues,
|
618
|
-
mergeCounterRef,
|
619
|
-
mergePayloads,
|
620
554
|
advancePushHead,
|
621
555
|
}: {
|
622
556
|
initialBackendHead: EventSequenceNumber.GlobalEventSequenceNumber
|
@@ -626,13 +560,12 @@ const backgroundBackendPulling = ({
|
|
626
560
|
) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx | HttpClient.HttpClient>
|
627
561
|
otelSpan: otel.Span | undefined
|
628
562
|
syncStateSref: SubscriptionRef.SubscriptionRef<SyncState.SyncState | undefined>
|
563
|
+
dbState: SqliteDb
|
629
564
|
localPushesLatch: Effect.Latch
|
630
565
|
pullLatch: Effect.Latch
|
631
566
|
devtoolsLatch: Effect.Latch | undefined
|
632
567
|
initialBlockingSyncContext: InitialBlockingSyncContext
|
633
568
|
connectedClientSessionPullQueues: PullQueueSet
|
634
|
-
mergeCounterRef: { current: number }
|
635
|
-
mergePayloads: Map<number, typeof SyncState.PayloadUpstream.Type>
|
636
569
|
advancePushHead: (eventNum: EventSequenceNumber.EventSequenceNumber) => void
|
637
570
|
}) =>
|
638
571
|
Effect.gen(function* () {
|
@@ -665,16 +598,14 @@ const backgroundBackendPulling = ({
|
|
665
598
|
ignoreClientEvents: true,
|
666
599
|
})
|
667
600
|
|
668
|
-
const mergeCounter = yield* incrementMergeCounter(mergeCounterRef)
|
669
|
-
|
670
601
|
if (mergeResult._tag === 'reject') {
|
671
602
|
return shouldNeverHappen('The leader thread should never reject upstream advances')
|
672
603
|
} else if (mergeResult._tag === 'unexpected-error') {
|
673
|
-
otelSpan?.addEvent(`
|
604
|
+
otelSpan?.addEvent(`pull:unexpected-error`, {
|
674
605
|
newEventsCount: newEvents.length,
|
675
606
|
newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
|
676
607
|
})
|
677
|
-
return yield*
|
608
|
+
return yield* new SyncError({ cause: mergeResult.message })
|
678
609
|
}
|
679
610
|
|
680
611
|
const newBackendHead = newEvents.at(-1)!.seqNum
|
@@ -682,7 +613,7 @@ const backgroundBackendPulling = ({
|
|
682
613
|
Eventlog.updateBackendHead(dbEventlog, newBackendHead)
|
683
614
|
|
684
615
|
if (mergeResult._tag === 'rebase') {
|
685
|
-
otelSpan?.addEvent(`[${
|
616
|
+
otelSpan?.addEvent(`pull:rebase[${mergeResult.newSyncState.localHead.rebaseGeneration}]`, {
|
686
617
|
newEventsCount: newEvents.length,
|
687
618
|
newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
|
688
619
|
rollbackCount: mergeResult.rollbackEvents.length,
|
@@ -704,30 +635,19 @@ const backgroundBackendPulling = ({
|
|
704
635
|
}
|
705
636
|
|
706
637
|
yield* connectedClientSessionPullQueues.offer({
|
707
|
-
payload: SyncState.
|
708
|
-
|
709
|
-
rollbackEvents: mergeResult.rollbackEvents,
|
710
|
-
}),
|
711
|
-
mergeCounter,
|
638
|
+
payload: SyncState.payloadFromMergeResult(mergeResult),
|
639
|
+
leaderHead: mergeResult.newSyncState.localHead,
|
712
640
|
})
|
713
|
-
mergePayloads.set(
|
714
|
-
mergeCounter,
|
715
|
-
SyncState.PayloadUpstreamRebase.make({
|
716
|
-
newEvents: mergeResult.newEvents,
|
717
|
-
rollbackEvents: mergeResult.rollbackEvents,
|
718
|
-
}),
|
719
|
-
)
|
720
641
|
} else {
|
721
|
-
otelSpan?.addEvent(`
|
642
|
+
otelSpan?.addEvent(`pull:advance`, {
|
722
643
|
newEventsCount: newEvents.length,
|
723
644
|
mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
724
645
|
})
|
725
646
|
|
726
647
|
yield* connectedClientSessionPullQueues.offer({
|
727
|
-
payload: SyncState.
|
728
|
-
|
648
|
+
payload: SyncState.payloadFromMergeResult(mergeResult),
|
649
|
+
leaderHead: mergeResult.newSyncState.localHead,
|
729
650
|
})
|
730
|
-
mergePayloads.set(mergeCounter, SyncState.PayloadUpstreamAdvance.make({ newEvents: mergeResult.newEvents }))
|
731
651
|
|
732
652
|
if (mergeResult.confirmedEvents.length > 0) {
|
733
653
|
// `mergeResult.confirmedEvents` don't contain the correct sync metadata, so we need to use
|
@@ -756,7 +676,9 @@ const backgroundBackendPulling = ({
|
|
756
676
|
}
|
757
677
|
})
|
758
678
|
|
759
|
-
const cursorInfo = yield* Eventlog.getSyncBackendCursorInfo(initialBackendHead)
|
679
|
+
const cursorInfo = yield* Eventlog.getSyncBackendCursorInfo({ remoteHead: initialBackendHead })
|
680
|
+
|
681
|
+
const hashMaterializerResult = makeMaterializerHash({ schema, dbState })
|
760
682
|
|
761
683
|
yield* syncBackend.pull(cursorInfo).pipe(
|
762
684
|
// TODO only take from queue while connected
|
@@ -775,7 +697,13 @@ const backgroundBackendPulling = ({
|
|
775
697
|
yield* SubscriptionRef.waitUntil(syncBackend.isConnected, (isConnected) => isConnected === true)
|
776
698
|
|
777
699
|
yield* onNewPullChunk(
|
778
|
-
batch.map((_) =>
|
700
|
+
batch.map((_) =>
|
701
|
+
LiveStoreEvent.EncodedWithMeta.fromGlobal(_.eventEncoded, {
|
702
|
+
syncMetadata: _.metadata,
|
703
|
+
materializerHashLeader: hashMaterializerResult(_.eventEncoded),
|
704
|
+
materializerHashSession: Option.none(),
|
705
|
+
}),
|
706
|
+
),
|
779
707
|
remaining,
|
780
708
|
)
|
781
709
|
|
@@ -839,19 +767,25 @@ const trimChangesetRows = (db: SqliteDb, newHead: EventSequenceNumber.EventSeque
|
|
839
767
|
}
|
840
768
|
|
841
769
|
interface PullQueueSet {
|
842
|
-
makeQueue:
|
843
|
-
|
770
|
+
makeQueue: (
|
771
|
+
cursor: EventSequenceNumber.EventSequenceNumber,
|
772
|
+
) => Effect.Effect<
|
773
|
+
Queue.Queue<{ payload: typeof SyncState.PayloadUpstream.Type }>,
|
844
774
|
UnexpectedError,
|
845
775
|
Scope.Scope | LeaderThreadCtx
|
846
776
|
>
|
847
777
|
offer: (item: {
|
848
778
|
payload: typeof SyncState.PayloadUpstream.Type
|
849
|
-
|
779
|
+
leaderHead: EventSequenceNumber.EventSequenceNumber
|
850
780
|
}) => Effect.Effect<void, UnexpectedError>
|
851
781
|
}
|
852
782
|
|
853
783
|
const makePullQueueSet = Effect.gen(function* () {
|
854
|
-
const set = new Set<Queue.Queue<{ payload: typeof SyncState.PayloadUpstream.Type
|
784
|
+
const set = new Set<Queue.Queue<{ payload: typeof SyncState.PayloadUpstream.Type }>>()
|
785
|
+
|
786
|
+
type StringifiedSeqNum = string
|
787
|
+
// NOTE this could grow unbounded for long running sessions
|
788
|
+
const cachedPayloads = new Map<StringifiedSeqNum, (typeof SyncState.PayloadUpstream.Type)[]>()
|
855
789
|
|
856
790
|
yield* Effect.addFinalizer(() =>
|
857
791
|
Effect.gen(function* () {
|
@@ -863,21 +797,81 @@ const makePullQueueSet = Effect.gen(function* () {
|
|
863
797
|
}),
|
864
798
|
)
|
865
799
|
|
866
|
-
const makeQueue: PullQueueSet['makeQueue'] =
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
800
|
+
const makeQueue: PullQueueSet['makeQueue'] = (cursor) =>
|
801
|
+
Effect.gen(function* () {
|
802
|
+
const queue = yield* Queue.unbounded<{
|
803
|
+
payload: typeof SyncState.PayloadUpstream.Type
|
804
|
+
}>().pipe(Effect.acquireRelease(Queue.shutdown))
|
871
805
|
|
872
|
-
|
806
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => set.delete(queue)))
|
873
807
|
|
874
|
-
|
808
|
+
const payloadsSinceCursor = Array.from(cachedPayloads.entries())
|
809
|
+
.flatMap(([seqNumStr, payloads]) =>
|
810
|
+
payloads.map((payload) => ({ payload, seqNum: EventSequenceNumber.fromString(seqNumStr) })),
|
811
|
+
)
|
812
|
+
.filter(({ seqNum }) => EventSequenceNumber.isGreaterThan(seqNum, cursor))
|
813
|
+
.toSorted((a, b) => EventSequenceNumber.compare(a.seqNum, b.seqNum))
|
814
|
+
.map(({ payload }) => {
|
815
|
+
if (payload._tag === 'upstream-advance') {
|
816
|
+
return {
|
817
|
+
payload: {
|
818
|
+
_tag: 'upstream-advance' as const,
|
819
|
+
newEvents: ReadonlyArray.dropWhile(payload.newEvents, (eventEncoded) =>
|
820
|
+
EventSequenceNumber.isGreaterThanOrEqual(cursor, eventEncoded.seqNum),
|
821
|
+
),
|
822
|
+
},
|
823
|
+
}
|
824
|
+
} else {
|
825
|
+
return { payload }
|
826
|
+
}
|
827
|
+
})
|
875
828
|
|
876
|
-
|
877
|
-
|
829
|
+
// console.debug(
|
830
|
+
// 'seeding new queue',
|
831
|
+
// {
|
832
|
+
// cursor,
|
833
|
+
// },
|
834
|
+
// '\n mergePayloads',
|
835
|
+
// ...Array.from(cachedPayloads.entries())
|
836
|
+
// .flatMap(([seqNumStr, payloads]) =>
|
837
|
+
// payloads.map((payload) => ({ payload, seqNum: EventSequenceNumber.fromString(seqNumStr) })),
|
838
|
+
// )
|
839
|
+
// .map(({ payload, seqNum }) => [
|
840
|
+
// seqNum,
|
841
|
+
// payload._tag,
|
842
|
+
// 'newEvents',
|
843
|
+
// ...payload.newEvents.map((_) => _.toJSON()),
|
844
|
+
// 'rollbackEvents',
|
845
|
+
// ...(payload._tag === 'upstream-rebase' ? payload.rollbackEvents.map((_) => _.toJSON()) : []),
|
846
|
+
// ]),
|
847
|
+
// '\n payloadsSinceCursor',
|
848
|
+
// ...payloadsSinceCursor.map(({ payload }) => [
|
849
|
+
// payload._tag,
|
850
|
+
// 'newEvents',
|
851
|
+
// ...payload.newEvents.map((_) => _.toJSON()),
|
852
|
+
// 'rollbackEvents',
|
853
|
+
// ...(payload._tag === 'upstream-rebase' ? payload.rollbackEvents.map((_) => _.toJSON()) : []),
|
854
|
+
// ]),
|
855
|
+
// )
|
856
|
+
|
857
|
+
yield* queue.offerAll(payloadsSinceCursor)
|
858
|
+
|
859
|
+
set.add(queue)
|
860
|
+
|
861
|
+
return queue
|
862
|
+
})
|
878
863
|
|
879
864
|
const offer: PullQueueSet['offer'] = (item) =>
|
880
865
|
Effect.gen(function* () {
|
866
|
+
const seqNumStr = EventSequenceNumber.toString(item.leaderHead)
|
867
|
+
if (cachedPayloads.has(seqNumStr)) {
|
868
|
+
cachedPayloads.get(seqNumStr)!.push(item.payload)
|
869
|
+
} else {
|
870
|
+
cachedPayloads.set(seqNumStr, [item.payload])
|
871
|
+
}
|
872
|
+
|
873
|
+
// console.debug(`offering to ${set.size} queues`, item.leaderHead, JSON.stringify(item.payload, null, 2))
|
874
|
+
|
881
875
|
// Short-circuit if the payload is an empty upstream advance
|
882
876
|
if (item.payload._tag === 'upstream-advance' && item.payload.newEvents.length === 0) {
|
883
877
|
return
|
@@ -894,24 +888,6 @@ const makePullQueueSet = Effect.gen(function* () {
|
|
894
888
|
}
|
895
889
|
})
|
896
890
|
|
897
|
-
const incrementMergeCounter = (mergeCounterRef: { current: number }) =>
|
898
|
-
Effect.gen(function* () {
|
899
|
-
const { dbState } = yield* LeaderThreadCtx
|
900
|
-
mergeCounterRef.current++
|
901
|
-
dbState.execute(
|
902
|
-
sql`INSERT OR REPLACE INTO ${SystemTables.LEADER_MERGE_COUNTER_TABLE} (id, mergeCounter) VALUES (0, ${mergeCounterRef.current})`,
|
903
|
-
)
|
904
|
-
return mergeCounterRef.current
|
905
|
-
})
|
906
|
-
|
907
|
-
const getMergeCounterFromDb = (dbState: SqliteDb) =>
|
908
|
-
Effect.gen(function* () {
|
909
|
-
const result = dbState.select<{ mergeCounter: number }>(
|
910
|
-
sql`SELECT mergeCounter FROM ${SystemTables.LEADER_MERGE_COUNTER_TABLE} WHERE id = 0`,
|
911
|
-
)
|
912
|
-
return result[0]?.mergeCounter ?? 0
|
913
|
-
})
|
914
|
-
|
915
891
|
const validatePushBatch = (
|
916
892
|
batch: ReadonlyArray<LiveStoreEvent.EncodedWithMeta>,
|
917
893
|
pushHead: EventSequenceNumber.EventSequenceNumber,
|