@livestore/common 0.3.0-dev.46 → 0.3.0-dev.48
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/adapter-types.d.ts +11 -9
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js +5 -3
- package/dist/adapter-types.js.map +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +25 -25
- package/dist/devtools/devtools-messages-client-session.js +3 -3
- package/dist/devtools/devtools-messages-client-session.js.map +1 -1
- package/dist/devtools/devtools-messages-common.d.ts +6 -6
- package/dist/devtools/devtools-messages-leader.d.ts +30 -30
- package/dist/devtools/devtools-messages-leader.js +3 -3
- package/dist/devtools/devtools-messages-leader.js.map +1 -1
- package/dist/devtools/mod.js +1 -1
- package/dist/devtools/mod.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +27 -25
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/eventlog.d.ts +10 -10
- package/dist/leader-thread/eventlog.d.ts.map +1 -1
- package/dist/leader-thread/eventlog.js +24 -24
- package/dist/leader-thread/eventlog.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +1 -1
- package/dist/leader-thread/leader-worker-devtools.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 +19 -15
- package/dist/leader-thread/materialize-event.js.map +1 -1
- package/dist/leader-thread/types.d.ts +4 -4
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/make-client-session.d.ts +2 -1
- package/dist/make-client-session.d.ts.map +1 -1
- package/dist/make-client-session.js +11 -12
- package/dist/make-client-session.js.map +1 -1
- package/dist/materializer-helper.d.ts.map +1 -1
- package/dist/materializer-helper.js +4 -2
- package/dist/materializer-helper.js.map +1 -1
- package/dist/rematerialize-from-eventlog.js +9 -9
- package/dist/rematerialize-from-eventlog.js.map +1 -1
- package/dist/schema/EventId.d.ts +28 -28
- package/dist/schema/EventId.d.ts.map +1 -1
- package/dist/schema/EventId.js +9 -9
- package/dist/schema/EventId.js.map +1 -1
- package/dist/schema/EventId.test.js +5 -5
- package/dist/schema/EventId.test.js.map +1 -1
- package/dist/schema/EventNumber.d.ts +57 -0
- package/dist/schema/EventNumber.d.ts.map +1 -0
- package/dist/schema/EventNumber.js +82 -0
- package/dist/schema/EventNumber.js.map +1 -0
- package/dist/schema/EventNumber.test.d.ts +2 -0
- package/dist/schema/EventNumber.test.d.ts.map +1 -0
- package/dist/schema/EventNumber.test.js +11 -0
- package/dist/schema/EventNumber.test.js.map +1 -0
- package/dist/schema/EventSequenceNumber.d.ts +57 -0
- package/dist/schema/EventSequenceNumber.d.ts.map +1 -0
- package/dist/schema/EventSequenceNumber.js +82 -0
- package/dist/schema/EventSequenceNumber.js.map +1 -0
- package/dist/schema/EventSequenceNumber.test.d.ts +2 -0
- package/dist/schema/EventSequenceNumber.test.d.ts.map +1 -0
- package/dist/schema/EventSequenceNumber.test.js +11 -0
- package/dist/schema/EventSequenceNumber.test.js.map +1 -0
- package/dist/schema/LiveStoreEvent.d.ts +81 -79
- package/dist/schema/LiveStoreEvent.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent.js +31 -32
- package/dist/schema/LiveStoreEvent.js.map +1 -1
- package/dist/schema/mod.d.ts +1 -1
- package/dist/schema/mod.d.ts.map +1 -1
- package/dist/schema/mod.js +1 -1
- package/dist/schema/mod.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 +2 -2
- package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.test.d.ts +3 -3
- package/dist/schema/state/sqlite/query-builder/impl.test.js +9 -0
- package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
- package/dist/schema/state/sqlite/system-tables.d.ts +52 -52
- package/dist/schema/state/sqlite/system-tables.d.ts.map +1 -1
- package/dist/schema/state/sqlite/system-tables.js +11 -10
- package/dist/schema/state/sqlite/system-tables.js.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +7 -7
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/next/compact-events.js +38 -35
- package/dist/sync/next/compact-events.js.map +1 -1
- package/dist/sync/next/facts.d.ts +4 -4
- package/dist/sync/next/facts.d.ts.map +1 -1
- package/dist/sync/next/facts.js +8 -8
- package/dist/sync/next/facts.js.map +1 -1
- package/dist/sync/next/history-dag-common.d.ts +4 -4
- package/dist/sync/next/history-dag-common.d.ts.map +1 -1
- package/dist/sync/next/history-dag-common.js +7 -4
- package/dist/sync/next/history-dag-common.js.map +1 -1
- package/dist/sync/next/history-dag.d.ts +0 -2
- package/dist/sync/next/history-dag.d.ts.map +1 -1
- package/dist/sync/next/history-dag.js +15 -13
- package/dist/sync/next/history-dag.js.map +1 -1
- package/dist/sync/next/rebase-events.d.ts.map +1 -1
- package/dist/sync/next/rebase-events.js +10 -4
- package/dist/sync/next/rebase-events.js.map +1 -1
- package/dist/sync/next/test/compact-events.calculator.test.js +13 -13
- package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
- package/dist/sync/next/test/compact-events.test.js +31 -31
- package/dist/sync/next/test/compact-events.test.js.map +1 -1
- package/dist/sync/next/test/event-fixtures.d.ts +10 -0
- package/dist/sync/next/test/event-fixtures.d.ts.map +1 -1
- package/dist/sync/next/test/event-fixtures.js +19 -13
- package/dist/sync/next/test/event-fixtures.js.map +1 -1
- package/dist/sync/sync.d.ts +11 -11
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/sync.js +5 -5
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/syncstate.d.ts +18 -18
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +46 -47
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +110 -110
- 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 +4 -4
- package/dist/sync/validate-push-payload.js.map +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/package.json +4 -4
- package/src/adapter-types.ts +11 -7
- package/src/devtools/devtools-messages-client-session.ts +3 -3
- package/src/devtools/devtools-messages-leader.ts +3 -3
- package/src/devtools/mod.ts +1 -1
- package/src/leader-thread/LeaderSyncProcessor.ts +36 -29
- package/src/leader-thread/eventlog.ts +36 -31
- package/src/leader-thread/leader-worker-devtools.ts +1 -1
- package/src/leader-thread/materialize-event.ts +21 -17
- package/src/leader-thread/types.ts +4 -4
- package/src/make-client-session.ts +14 -11
- package/src/materializer-helper.ts +5 -2
- package/src/rematerialize-from-eventlog.ts +10 -10
- package/src/schema/EventSequenceNumber.test.ts +12 -0
- package/src/schema/EventSequenceNumber.ts +121 -0
- package/src/schema/LiveStoreEvent.ts +66 -65
- package/src/schema/mod.ts +1 -1
- package/src/schema/state/sqlite/query-builder/impl.test.ts +9 -0
- package/src/schema/state/sqlite/query-builder/impl.ts +3 -2
- package/src/schema/state/sqlite/system-tables.ts +11 -10
- package/src/sync/ClientSessionSyncProcessor.ts +7 -7
- package/src/sync/next/compact-events.ts +38 -35
- package/src/sync/next/facts.ts +12 -9
- package/src/sync/next/history-dag-common.ts +9 -6
- package/src/sync/next/history-dag.ts +15 -16
- package/src/sync/next/rebase-events.ts +10 -4
- package/src/sync/next/test/compact-events.calculator.test.ts +13 -13
- package/src/sync/next/test/compact-events.test.ts +31 -31
- package/src/sync/next/test/event-fixtures.ts +20 -13
- package/src/sync/sync.ts +7 -7
- package/src/sync/syncstate.test.ts +112 -112
- package/src/sync/syncstate.ts +58 -48
- package/src/sync/validate-push-payload.ts +5 -5
- package/src/version.ts +2 -2
- package/src/schema/EventId.test.ts +0 -12
- package/src/schema/EventId.ts +0 -106
package/src/sync/syncstate.ts
CHANGED
@@ -2,7 +2,7 @@ import { casesHandled, LS_DEV, shouldNeverHappen } from '@livestore/utils'
|
|
2
2
|
import { Match, ReadonlyArray, Schema } from '@livestore/utils/effect'
|
3
3
|
|
4
4
|
import { UnexpectedError } from '../adapter-types.js'
|
5
|
-
import * as
|
5
|
+
import * as EventSequenceNumber from '../schema/EventSequenceNumber.js'
|
6
6
|
import * as LiveStoreEvent from '../schema/LiveStoreEvent.js'
|
7
7
|
|
8
8
|
/**
|
@@ -45,14 +45,14 @@ import * as LiveStoreEvent from '../schema/LiveStoreEvent.js'
|
|
45
45
|
export class SyncState extends Schema.Class<SyncState>('SyncState')({
|
46
46
|
pending: Schema.Array(LiveStoreEvent.EncodedWithMeta),
|
47
47
|
/** What this node expects the next upstream node to have as its own local head */
|
48
|
-
upstreamHead:
|
48
|
+
upstreamHead: EventSequenceNumber.EventSequenceNumber,
|
49
49
|
/** Equivalent to `pending.at(-1)?.id` if there are pending events */
|
50
|
-
localHead:
|
50
|
+
localHead: EventSequenceNumber.EventSequenceNumber,
|
51
51
|
}) {
|
52
52
|
toJSON = (): any => ({
|
53
53
|
pending: this.pending.map((e) => e.toJSON()),
|
54
|
-
upstreamHead:
|
55
|
-
localHead:
|
54
|
+
upstreamHead: EventSequenceNumber.toString(this.upstreamHead),
|
55
|
+
localHead: EventSequenceNumber.toString(this.localHead),
|
56
56
|
})
|
57
57
|
}
|
58
58
|
|
@@ -148,13 +148,13 @@ export class MergeResultRebase extends Schema.Class<MergeResultRebase>('MergeRes
|
|
148
148
|
export class MergeResultReject extends Schema.Class<MergeResultReject>('MergeResultReject')({
|
149
149
|
_tag: Schema.Literal('reject'),
|
150
150
|
/** The minimum id that the new events must have */
|
151
|
-
expectedMinimumId:
|
151
|
+
expectedMinimumId: EventSequenceNumber.EventSequenceNumber,
|
152
152
|
mergeContext: MergeContext,
|
153
153
|
}) {
|
154
154
|
toJSON = (): any => {
|
155
155
|
return {
|
156
156
|
_tag: this._tag,
|
157
|
-
expectedMinimumId:
|
157
|
+
expectedMinimumId: EventSequenceNumber.toString(this.expectedMinimumId),
|
158
158
|
mergeContext: this.mergeContext.toJSON(),
|
159
159
|
}
|
160
160
|
}
|
@@ -185,7 +185,7 @@ const unexpectedError = (cause: unknown): MergeResultUnexpectedError => {
|
|
185
185
|
|
186
186
|
// TODO Idea: call merge recursively through hierarchy levels
|
187
187
|
/*
|
188
|
-
Idea: have a map that maps from `
|
188
|
+
Idea: have a map that maps from `globalEventSequenceNumber` to Array<ClientEvents>
|
189
189
|
The same applies to even further hierarchy levels
|
190
190
|
|
191
191
|
TODO: possibly even keep the client events in a separate table in the client leader
|
@@ -214,12 +214,12 @@ export const merge = ({
|
|
214
214
|
const rollbackEvents = [...payload.rollbackEvents, ...syncState.pending]
|
215
215
|
|
216
216
|
// Get the last new event's ID as the new upstream head
|
217
|
-
const newUpstreamHead = payload.newEvents.at(-1)?.
|
217
|
+
const newUpstreamHead = payload.newEvents.at(-1)?.seqNum ?? syncState.upstreamHead
|
218
218
|
|
219
219
|
// Rebase pending events on top of the new events
|
220
220
|
const rebasedPending = rebaseEvents({
|
221
221
|
events: syncState.pending,
|
222
|
-
|
222
|
+
baseEventSequenceNumber: newUpstreamHead,
|
223
223
|
isClientEvent,
|
224
224
|
})
|
225
225
|
|
@@ -229,7 +229,7 @@ export const merge = ({
|
|
229
229
|
newSyncState: new SyncState({
|
230
230
|
pending: rebasedPending,
|
231
231
|
upstreamHead: newUpstreamHead,
|
232
|
-
localHead: rebasedPending.at(-1)?.
|
232
|
+
localHead: rebasedPending.at(-1)?.seqNum ?? newUpstreamHead,
|
233
233
|
}),
|
234
234
|
newEvents: [...payload.newEvents, ...rebasedPending],
|
235
235
|
rollbackEvents,
|
@@ -256,26 +256,26 @@ export const merge = ({
|
|
256
256
|
)
|
257
257
|
}
|
258
258
|
|
259
|
-
// Validate that newEvents are sorted in ascending order by
|
259
|
+
// Validate that newEvents are sorted in ascending order by eventNum
|
260
260
|
for (let i = 1; i < payload.newEvents.length; i++) {
|
261
|
-
if (
|
261
|
+
if (EventSequenceNumber.isGreaterThan(payload.newEvents[i - 1]!.seqNum, payload.newEvents[i]!.seqNum)) {
|
262
262
|
return unexpectedError(
|
263
|
-
`Events must be sorted in ascending order by
|
263
|
+
`Events must be sorted in ascending order by event number. Received: [${payload.newEvents.map((e) => EventSequenceNumber.toString(e.seqNum)).join(', ')}]`,
|
264
264
|
)
|
265
265
|
}
|
266
266
|
}
|
267
267
|
|
268
268
|
// Validate that incoming events are larger than upstream head
|
269
269
|
if (
|
270
|
-
|
271
|
-
|
270
|
+
EventSequenceNumber.isGreaterThan(syncState.upstreamHead, payload.newEvents[0]!.seqNum) ||
|
271
|
+
EventSequenceNumber.isEqual(syncState.upstreamHead, payload.newEvents[0]!.seqNum)
|
272
272
|
) {
|
273
273
|
return unexpectedError(
|
274
|
-
`Incoming events must be greater than upstream head. Expected greater than: ${
|
274
|
+
`Incoming events must be greater than upstream head. Expected greater than: ${EventSequenceNumber.toString(syncState.upstreamHead)}. Received: [${payload.newEvents.map((e) => EventSequenceNumber.toString(e.seqNum)).join(', ')}]`,
|
275
275
|
)
|
276
276
|
}
|
277
277
|
|
278
|
-
const newUpstreamHead = payload.newEvents.at(-1)!.
|
278
|
+
const newUpstreamHead = payload.newEvents.at(-1)!.seqNum
|
279
279
|
|
280
280
|
const divergentPendingIndex = findDivergencePoint({
|
281
281
|
existingEvents: syncState.pending,
|
@@ -287,8 +287,12 @@ export const merge = ({
|
|
287
287
|
|
288
288
|
// No divergent pending events, thus we can just advance (some of) the pending events
|
289
289
|
if (divergentPendingIndex === -1) {
|
290
|
-
const
|
291
|
-
|
290
|
+
const pendingEventSequenceNumbers = new Set(
|
291
|
+
syncState.pending.map((e) => `${e.seqNum.global},${e.seqNum.client}`),
|
292
|
+
)
|
293
|
+
const newEvents = payload.newEvents.filter(
|
294
|
+
(e) => !pendingEventSequenceNumbers.has(`${e.seqNum.global},${e.seqNum.client}`),
|
295
|
+
)
|
292
296
|
|
293
297
|
// In the case where the incoming events are a subset of the pending events,
|
294
298
|
// we need to split the pending events into two groups:
|
@@ -318,7 +322,8 @@ export const merge = ({
|
|
318
322
|
newSyncState: new SyncState({
|
319
323
|
pending: pendingRemaining,
|
320
324
|
upstreamHead: newUpstreamHead,
|
321
|
-
localHead:
|
325
|
+
localHead:
|
326
|
+
pendingRemaining.at(-1)?.seqNum ?? EventSequenceNumber.max(syncState.localHead, newUpstreamHead),
|
322
327
|
}),
|
323
328
|
newEvents,
|
324
329
|
confirmedEvents: pendingMatching,
|
@@ -329,7 +334,7 @@ export const merge = ({
|
|
329
334
|
const divergentPending = syncState.pending.slice(divergentPendingIndex)
|
330
335
|
const rebasedPending = rebaseEvents({
|
331
336
|
events: divergentPending,
|
332
|
-
|
337
|
+
baseEventSequenceNumber: newUpstreamHead,
|
333
338
|
isClientEvent,
|
334
339
|
})
|
335
340
|
|
@@ -347,7 +352,7 @@ export const merge = ({
|
|
347
352
|
newSyncState: new SyncState({
|
348
353
|
pending: rebasedPending,
|
349
354
|
upstreamHead: newUpstreamHead,
|
350
|
-
localHead: rebasedPending.at(-1)!.
|
355
|
+
localHead: rebasedPending.at(-1)!.seqNum,
|
351
356
|
}),
|
352
357
|
newEvents: [...payload.newEvents.slice(divergentNewEventsIndex), ...rebasedPending],
|
353
358
|
rollbackEvents: divergentPending,
|
@@ -373,10 +378,11 @@ export const merge = ({
|
|
373
378
|
}
|
374
379
|
|
375
380
|
const newEventsFirst = payload.newEvents.at(0)!
|
376
|
-
const
|
381
|
+
const invalidEventSequenceNumber =
|
382
|
+
EventSequenceNumber.isGreaterThan(newEventsFirst.seqNum, syncState.localHead) === false
|
377
383
|
|
378
|
-
if (
|
379
|
-
const expectedMinimumId =
|
384
|
+
if (invalidEventSequenceNumber) {
|
385
|
+
const expectedMinimumId = EventSequenceNumber.nextPair(syncState.localHead, true).seqNum
|
380
386
|
return validateMergeResult(
|
381
387
|
MergeResultReject.make({
|
382
388
|
_tag: 'reject',
|
@@ -391,7 +397,7 @@ export const merge = ({
|
|
391
397
|
newSyncState: new SyncState({
|
392
398
|
pending: [...syncState.pending, ...payload.newEvents],
|
393
399
|
upstreamHead: syncState.upstreamHead,
|
394
|
-
localHead: payload.newEvents.at(-1)!.
|
400
|
+
localHead: payload.newEvents.at(-1)!.seqNum,
|
395
401
|
}),
|
396
402
|
newEvents: payload.newEvents,
|
397
403
|
confirmedEvents: [],
|
@@ -436,9 +442,11 @@ export const findDivergencePoint = ({
|
|
436
442
|
|
437
443
|
if (divergencePointWithoutClientEvents === -1) return -1
|
438
444
|
|
439
|
-
const
|
445
|
+
const divergencePointEventSequenceNumber = existingEvents[divergencePointWithoutClientEvents]!.seqNum
|
440
446
|
// Now find the divergence point in the original array
|
441
|
-
return existingEvents.findIndex((event) =>
|
447
|
+
return existingEvents.findIndex((event) =>
|
448
|
+
EventSequenceNumber.isEqual(event.seqNum, divergencePointEventSequenceNumber),
|
449
|
+
)
|
442
450
|
}
|
443
451
|
|
444
452
|
return existingEvents.findIndex((existingEvent, index) => {
|
@@ -450,18 +458,18 @@ export const findDivergencePoint = ({
|
|
450
458
|
|
451
459
|
const rebaseEvents = ({
|
452
460
|
events,
|
453
|
-
|
461
|
+
baseEventSequenceNumber,
|
454
462
|
isClientEvent,
|
455
463
|
}: {
|
456
464
|
events: ReadonlyArray<LiveStoreEvent.EncodedWithMeta>
|
457
|
-
|
465
|
+
baseEventSequenceNumber: EventSequenceNumber.EventSequenceNumber
|
458
466
|
isClientEvent: (event: LiveStoreEvent.EncodedWithMeta) => boolean
|
459
467
|
}): ReadonlyArray<LiveStoreEvent.EncodedWithMeta> => {
|
460
|
-
let
|
468
|
+
let prevEventSequenceNumber = baseEventSequenceNumber
|
461
469
|
return events.map((event) => {
|
462
470
|
const isLocal = isClientEvent(event)
|
463
|
-
const newEvent = event.rebase(
|
464
|
-
|
471
|
+
const newEvent = event.rebase(prevEventSequenceNumber, isLocal)
|
472
|
+
prevEventSequenceNumber = newEvent.seqNum
|
465
473
|
return newEvent
|
466
474
|
})
|
467
475
|
}
|
@@ -477,9 +485,9 @@ const _flattenMergeResults = (_updateResults: ReadonlyArray<MergeResult>) => {}
|
|
477
485
|
|
478
486
|
const validatePayload = (payload: typeof Payload.Type) => {
|
479
487
|
for (let i = 1; i < payload.newEvents.length; i++) {
|
480
|
-
if (
|
488
|
+
if (EventSequenceNumber.isGreaterThanOrEqual(payload.newEvents[i - 1]!.seqNum, payload.newEvents[i]!.seqNum)) {
|
481
489
|
return unexpectedError(
|
482
|
-
`Events must be ordered in monotonically ascending order by
|
490
|
+
`Events must be ordered in monotonically ascending order by eventNum. Received: [${payload.newEvents.map((e) => EventSequenceNumber.toString(e.seqNum)).join(', ')}]`,
|
483
491
|
)
|
484
492
|
}
|
485
493
|
}
|
@@ -491,9 +499,9 @@ const validateSyncState = (syncState: SyncState) => {
|
|
491
499
|
const nextEvent = syncState.pending[i + 1]
|
492
500
|
if (nextEvent === undefined) break // Reached end of chain
|
493
501
|
|
494
|
-
if (
|
502
|
+
if (EventSequenceNumber.isGreaterThanOrEqual(event.seqNum, nextEvent.seqNum)) {
|
495
503
|
shouldNeverHappen(
|
496
|
-
`Events must be ordered in monotonically ascending order by
|
504
|
+
`Events must be ordered in monotonically ascending order by eventNum. Received: [${syncState.pending.map((e) => EventSequenceNumber.toString(e.seqNum)).join(', ')}]`,
|
497
505
|
{
|
498
506
|
event,
|
499
507
|
nextEvent,
|
@@ -502,11 +510,11 @@ const validateSyncState = (syncState: SyncState) => {
|
|
502
510
|
}
|
503
511
|
|
504
512
|
// If the global id has increased, then the client id must be 0
|
505
|
-
const globalIdHasIncreased = nextEvent.
|
513
|
+
const globalIdHasIncreased = nextEvent.seqNum.global > event.seqNum.global
|
506
514
|
if (globalIdHasIncreased) {
|
507
|
-
if (nextEvent.
|
515
|
+
if (nextEvent.seqNum.client !== 0) {
|
508
516
|
shouldNeverHappen(
|
509
|
-
`New global events must point to clientId 0 in the
|
517
|
+
`New global events must point to clientId 0 in the parentSeqNum. Received: (${EventSequenceNumber.toString(nextEvent.seqNum)})`,
|
510
518
|
syncState.pending,
|
511
519
|
{
|
512
520
|
event,
|
@@ -515,9 +523,9 @@ const validateSyncState = (syncState: SyncState) => {
|
|
515
523
|
)
|
516
524
|
}
|
517
525
|
} else {
|
518
|
-
// Otherwise, the
|
519
|
-
if (
|
520
|
-
shouldNeverHappen('Events must be linked in a continuous chain via the
|
526
|
+
// Otherwise, the parentSeqNum must be the same as the previous event's id
|
527
|
+
if (EventSequenceNumber.isEqual(nextEvent.parentSeqNum, event.seqNum) === false) {
|
528
|
+
shouldNeverHappen('Events must be linked in a continuous chain via the parentSeqNum', syncState.pending, {
|
521
529
|
event,
|
522
530
|
nextEvent,
|
523
531
|
})
|
@@ -532,7 +540,7 @@ const validateMergeResult = (mergeResult: typeof MergeResult.Type) => {
|
|
532
540
|
validateSyncState(mergeResult.newSyncState)
|
533
541
|
|
534
542
|
// Ensure local head is always greater than or equal to upstream head
|
535
|
-
if (
|
543
|
+
if (EventSequenceNumber.isGreaterThan(mergeResult.newSyncState.upstreamHead, mergeResult.newSyncState.localHead)) {
|
536
544
|
shouldNeverHappen('Local head must be greater than or equal to upstream head', {
|
537
545
|
localHead: mergeResult.newSyncState.localHead,
|
538
546
|
upstreamHead: mergeResult.newSyncState.upstreamHead,
|
@@ -541,8 +549,10 @@ const validateMergeResult = (mergeResult: typeof MergeResult.Type) => {
|
|
541
549
|
|
542
550
|
// Ensure new local head is greater than or equal to the previous local head
|
543
551
|
if (
|
544
|
-
|
545
|
-
|
552
|
+
EventSequenceNumber.isGreaterThanOrEqual(
|
553
|
+
mergeResult.newSyncState.localHead,
|
554
|
+
mergeResult.mergeContext.syncState.localHead,
|
555
|
+
) === false
|
546
556
|
) {
|
547
557
|
shouldNeverHappen('New local head must be greater than or equal to the previous local head', {
|
548
558
|
localHead: mergeResult.newSyncState.localHead,
|
@@ -552,7 +562,7 @@ const validateMergeResult = (mergeResult: typeof MergeResult.Type) => {
|
|
552
562
|
|
553
563
|
// Ensure new upstream head is greater than or equal to the previous upstream head
|
554
564
|
if (
|
555
|
-
|
565
|
+
EventSequenceNumber.isGreaterThanOrEqual(
|
556
566
|
mergeResult.newSyncState.upstreamHead,
|
557
567
|
mergeResult.mergeContext.syncState.upstreamHead,
|
558
568
|
) === false
|
@@ -1,20 +1,20 @@
|
|
1
1
|
import { Effect } from '@livestore/utils/effect'
|
2
2
|
|
3
|
-
import type {
|
3
|
+
import type { EventSequenceNumber, LiveStoreEvent } from '../schema/mod.js'
|
4
4
|
import { InvalidPushError } from './sync.js'
|
5
5
|
|
6
6
|
// TODO proper batch validation
|
7
7
|
export const validatePushPayload = (
|
8
8
|
batch: ReadonlyArray<LiveStoreEvent.AnyEncodedGlobal>,
|
9
|
-
|
9
|
+
currentEventSequenceNumber: EventSequenceNumber.GlobalEventSequenceNumber,
|
10
10
|
) =>
|
11
11
|
Effect.gen(function* () {
|
12
|
-
if (batch[0]!.
|
12
|
+
if (batch[0]!.seqNum <= currentEventSequenceNumber) {
|
13
13
|
return yield* InvalidPushError.make({
|
14
14
|
reason: {
|
15
15
|
_tag: 'ServerAhead',
|
16
|
-
|
17
|
-
|
16
|
+
minimumExpectedNum: currentEventSequenceNumber + 1,
|
17
|
+
providedNum: batch[0]!.seqNum,
|
18
18
|
},
|
19
19
|
})
|
20
20
|
}
|
package/src/version.ts
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
// import packageJson from '../package.json' with { type: 'json' }
|
3
3
|
// export const liveStoreVersion = packageJson.version
|
4
4
|
|
5
|
-
export const liveStoreVersion = '0.3.0-dev.
|
5
|
+
export const liveStoreVersion = '0.3.0-dev.48' as const
|
6
6
|
|
7
7
|
/**
|
8
8
|
* This version number is incremented whenever the internal storage format changes in a breaking way.
|
@@ -11,4 +11,4 @@ export const liveStoreVersion = '0.3.0-dev.46' as const
|
|
11
11
|
* While LiveStore is in alpha, this might happen more frequently.
|
12
12
|
* In the future, LiveStore will provide a migration path for older database files to avoid the impression of data loss.
|
13
13
|
*/
|
14
|
-
export const liveStoreStorageFormatVersion =
|
14
|
+
export const liveStoreStorageFormatVersion = 4
|
@@ -1,12 +0,0 @@
|
|
1
|
-
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
2
|
-
import { expect } from 'vitest'
|
3
|
-
|
4
|
-
import { EventId } from './mod.js'
|
5
|
-
|
6
|
-
Vitest.describe('EventId', () => {
|
7
|
-
Vitest.test('nextPair', () => {
|
8
|
-
const e_0_0 = EventId.make({ global: 0, client: 0 })
|
9
|
-
expect(EventId.nextPair(e_0_0, false).id).toStrictEqual({ global: 1, client: 0 })
|
10
|
-
expect(EventId.nextPair(e_0_0, true).id).toStrictEqual({ global: 0, client: 1 })
|
11
|
-
})
|
12
|
-
})
|
package/src/schema/EventId.ts
DELETED
@@ -1,106 +0,0 @@
|
|
1
|
-
import { Brand, Schema } from '@livestore/utils/effect'
|
2
|
-
|
3
|
-
export type ClientEventId = Brand.Branded<number, 'ClientEventId'>
|
4
|
-
export const localEventId = Brand.nominal<ClientEventId>()
|
5
|
-
export const ClientEventId = Schema.fromBrand(localEventId)(Schema.Int)
|
6
|
-
|
7
|
-
export type GlobalEventId = Brand.Branded<number, 'GlobalEventId'>
|
8
|
-
export const globalEventId = Brand.nominal<GlobalEventId>()
|
9
|
-
export const GlobalEventId = Schema.fromBrand(globalEventId)(Schema.Int)
|
10
|
-
|
11
|
-
export const clientDefault = 0 as any as ClientEventId
|
12
|
-
|
13
|
-
/**
|
14
|
-
* LiveStore event id value consisting of a globally unique event sequence number
|
15
|
-
* and a client sequence number.
|
16
|
-
*
|
17
|
-
* The client sequence number is only used for clientOnly events and starts from 0 for each global sequence number.
|
18
|
-
*/
|
19
|
-
export type EventId = { global: GlobalEventId; client: ClientEventId }
|
20
|
-
|
21
|
-
// export const EventSequenceNumber = Schema.Struct({})
|
22
|
-
// export const EventNumber = Schema.Struct({})
|
23
|
-
// export const ClientEventNumber = Schema.Struct({})
|
24
|
-
// export const GlobalEventNumber = Schema.Struct({})
|
25
|
-
|
26
|
-
/**
|
27
|
-
* NOTE: Client mutation events with a non-0 client id, won't be synced to the sync backend.
|
28
|
-
*/
|
29
|
-
export const EventId = Schema.Struct({
|
30
|
-
global: GlobalEventId,
|
31
|
-
/** Only increments for clientOnly events */
|
32
|
-
client: ClientEventId,
|
33
|
-
|
34
|
-
// TODO also provide a way to see "confirmation level" of event (e.g. confirmed by leader/sync backend)
|
35
|
-
|
36
|
-
// TODO: actually add this field
|
37
|
-
// Client only
|
38
|
-
// generation: Schema.Number.pipe(Schema.optional),
|
39
|
-
}).annotations({ title: 'LiveStore.EventId' })
|
40
|
-
|
41
|
-
/**
|
42
|
-
* Compare two event ids i.e. checks if the first event id is less than the second.
|
43
|
-
*/
|
44
|
-
export const compare = (a: EventId, b: EventId) => {
|
45
|
-
if (a.global !== b.global) {
|
46
|
-
return a.global - b.global
|
47
|
-
}
|
48
|
-
return a.client - b.client
|
49
|
-
}
|
50
|
-
|
51
|
-
/**
|
52
|
-
* Convert an event id to a string representation.
|
53
|
-
*/
|
54
|
-
export const toString = (id: EventId) => (id.client === 0 ? `e${id.global}` : `e${id.global}+${id.client}`)
|
55
|
-
|
56
|
-
/**
|
57
|
-
* Convert a string representation of an event id to an event id.
|
58
|
-
*/
|
59
|
-
export const fromString = (str: string): EventId => {
|
60
|
-
const [global, client] = str.slice(1, -1).split(',').map(Number)
|
61
|
-
if (global === undefined || client === undefined) {
|
62
|
-
throw new Error('Invalid event id string')
|
63
|
-
}
|
64
|
-
return { global, client } as EventId
|
65
|
-
}
|
66
|
-
|
67
|
-
export const isEqual = (a: EventId, b: EventId) => a.global === b.global && a.client === b.client
|
68
|
-
|
69
|
-
export type EventIdPair = { id: EventId; parentId: EventId }
|
70
|
-
|
71
|
-
export const ROOT = { global: 0 as any as GlobalEventId, client: clientDefault } satisfies EventId
|
72
|
-
|
73
|
-
export const isGreaterThan = (a: EventId, b: EventId) => {
|
74
|
-
return a.global > b.global || (a.global === b.global && a.client > b.client)
|
75
|
-
}
|
76
|
-
|
77
|
-
export const isGreaterThanOrEqual = (a: EventId, b: EventId) => {
|
78
|
-
return a.global > b.global || (a.global === b.global && a.client >= b.client)
|
79
|
-
}
|
80
|
-
|
81
|
-
export const max = (a: EventId, b: EventId) => {
|
82
|
-
return a.global > b.global || (a.global === b.global && a.client > b.client) ? a : b
|
83
|
-
}
|
84
|
-
|
85
|
-
export const diff = (a: EventId, b: EventId) => {
|
86
|
-
return {
|
87
|
-
global: a.global - b.global,
|
88
|
-
client: a.client - b.client,
|
89
|
-
}
|
90
|
-
}
|
91
|
-
|
92
|
-
export const make = (id: EventId | typeof EventId.Encoded): EventId => {
|
93
|
-
return Schema.is(EventId)(id) ? id : Schema.decodeSync(EventId)(id)
|
94
|
-
}
|
95
|
-
|
96
|
-
export const nextPair = (id: EventId, isLocal: boolean): EventIdPair => {
|
97
|
-
if (isLocal) {
|
98
|
-
return { id: { global: id.global, client: (id.client + 1) as any as ClientEventId }, parentId: id }
|
99
|
-
}
|
100
|
-
|
101
|
-
return {
|
102
|
-
id: { global: (id.global + 1) as any as GlobalEventId, client: clientDefault },
|
103
|
-
// NOTE we always point to `client: 0` for non-clientOnly events
|
104
|
-
parentId: { global: id.global, client: clientDefault },
|
105
|
-
}
|
106
|
-
}
|