@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
@@ -1,14 +1,13 @@
|
|
1
1
|
/// <reference lib="dom" />
|
2
2
|
import { LS_DEV, shouldNeverHappen, TRACE_VERBOSE } from '@livestore/utils'
|
3
|
-
import type
|
3
|
+
import { Option, type Runtime, type Scope } from '@livestore/utils/effect'
|
4
4
|
import { BucketQueue, Effect, FiberHandle, Queue, Schema, Stream, Subscribable } from '@livestore/utils/effect'
|
5
5
|
import * as otel from '@opentelemetry/api'
|
6
6
|
|
7
|
-
import type
|
7
|
+
import { type ClientSession, SyncError, type UnexpectedError } from '../adapter-types.js'
|
8
8
|
import * as EventSequenceNumber from '../schema/EventSequenceNumber.js'
|
9
9
|
import * as LiveStoreEvent from '../schema/LiveStoreEvent.js'
|
10
|
-
import { getEventDef, type LiveStoreSchema
|
11
|
-
import { sql } from '../util.js'
|
10
|
+
import { getEventDef, type LiveStoreSchema } from '../schema/mod.js'
|
12
11
|
import * as SyncState from './syncstate.js'
|
13
12
|
|
14
13
|
/**
|
@@ -21,6 +20,10 @@ import * as SyncState from './syncstate.js'
|
|
21
20
|
* - We might need to make the rebase behaviour configurable e.g. to let users manually trigger a rebase
|
22
21
|
*
|
23
22
|
* Longer term we should evalutate whether we can unify the ClientSessionSyncProcessor with the LeaderSyncProcessor.
|
23
|
+
*
|
24
|
+
* The session and leader sync processor are different in the following ways:
|
25
|
+
* - The leader sync processor pulls regular LiveStore events, while the session sync processor pulls SyncState.PayloadUpstream items
|
26
|
+
* - The session sync processor has no downstream nodes.
|
24
27
|
*/
|
25
28
|
export const makeClientSessionSyncProcessor = ({
|
26
29
|
schema,
|
@@ -37,11 +40,12 @@ export const makeClientSessionSyncProcessor = ({
|
|
37
40
|
clientSession: ClientSession
|
38
41
|
runtime: Runtime.Runtime<Scope.Scope>
|
39
42
|
materializeEvent: (
|
40
|
-
eventDecoded: LiveStoreEvent.
|
41
|
-
options: { otelContext: otel.Context; withChangeset: boolean },
|
43
|
+
eventDecoded: LiveStoreEvent.AnyDecoded,
|
44
|
+
options: { otelContext: otel.Context; withChangeset: boolean; materializerHashLeader: Option.Option<number> },
|
42
45
|
) => {
|
43
46
|
writeTables: Set<string>
|
44
47
|
sessionChangeset: { _tag: 'sessionChangeset'; data: Uint8Array; debug: any } | { _tag: 'no-op' } | { _tag: 'unset' }
|
48
|
+
materializerHash: Option.Option<number>
|
45
49
|
}
|
46
50
|
rollback: (changeset: Uint8Array) => void
|
47
51
|
refreshTables: (tables: Set<string>) => void
|
@@ -67,6 +71,7 @@ export const makeClientSessionSyncProcessor = ({
|
|
67
71
|
}),
|
68
72
|
}
|
69
73
|
|
74
|
+
/** Only used for debugging / observability, it's not relied upon for correctness of the sync processor. */
|
70
75
|
const syncStateUpdateQueue = Queue.unbounded<SyncState.SyncState>().pipe(Effect.runSync)
|
71
76
|
const isClientEvent = (eventEncoded: LiveStoreEvent.EncodedWithMeta) =>
|
72
77
|
getEventDef(schema, eventEncoded.name).eventDef.options.clientOnly
|
@@ -80,7 +85,10 @@ export const makeClientSessionSyncProcessor = ({
|
|
80
85
|
let baseEventSequenceNumber = syncStateRef.current.localHead
|
81
86
|
const encodedEventDefs = batch.map(({ name, args }) => {
|
82
87
|
const eventDef = getEventDef(schema, name)
|
83
|
-
const nextNumPair = EventSequenceNumber.nextPair(
|
88
|
+
const nextNumPair = EventSequenceNumber.nextPair({
|
89
|
+
seqNum: baseEventSequenceNumber,
|
90
|
+
isClient: eventDef.eventDef.options.clientOnly,
|
91
|
+
})
|
84
92
|
baseEventSequenceNumber = nextNumPair.seqNum
|
85
93
|
return new LiveStoreEvent.EncodedWithMeta(
|
86
94
|
Schema.encodeUnknownSync(eventSchema)({
|
@@ -101,7 +109,7 @@ export const makeClientSessionSyncProcessor = ({
|
|
101
109
|
})
|
102
110
|
|
103
111
|
if (mergeResult._tag === 'unexpected-error') {
|
104
|
-
return shouldNeverHappen('Unexpected error in client-session-sync-processor', mergeResult.
|
112
|
+
return shouldNeverHappen('Unexpected error in client-session-sync-processor', mergeResult.message)
|
105
113
|
}
|
106
114
|
|
107
115
|
span.addEvent('local-push', {
|
@@ -116,17 +124,28 @@ export const makeClientSessionSyncProcessor = ({
|
|
116
124
|
syncStateRef.current = mergeResult.newSyncState
|
117
125
|
syncStateUpdateQueue.offer(mergeResult.newSyncState).pipe(Effect.runSync)
|
118
126
|
|
127
|
+
// Materialize events to state
|
119
128
|
const writeTables = new Set<string>()
|
120
129
|
for (const event of mergeResult.newEvents) {
|
121
130
|
// TODO avoid encoding and decoding here again
|
122
131
|
const decodedEventDef = Schema.decodeSync(eventSchema)(event)
|
123
|
-
const
|
124
|
-
|
132
|
+
const {
|
133
|
+
writeTables: newWriteTables,
|
134
|
+
sessionChangeset,
|
135
|
+
materializerHash,
|
136
|
+
} = materializeEvent(decodedEventDef, {
|
137
|
+
otelContext,
|
138
|
+
withChangeset: true,
|
139
|
+
materializerHashLeader: Option.none(),
|
140
|
+
})
|
141
|
+
for (const table of newWriteTables) {
|
125
142
|
writeTables.add(table)
|
126
143
|
}
|
127
|
-
event.meta.sessionChangeset =
|
144
|
+
event.meta.sessionChangeset = sessionChangeset
|
145
|
+
event.meta.materializerHashSession = materializerHash
|
128
146
|
}
|
129
147
|
|
148
|
+
// Trigger push to leader
|
130
149
|
// console.debug('pushToLeader', encodedEventDefs.length, ...encodedEventDefs.map((_) => _.toJSON()))
|
131
150
|
BucketQueue.offerAll(leaderPushQueue, encodedEventDefs).pipe(Effect.runSync)
|
132
151
|
|
@@ -171,18 +190,11 @@ export const makeClientSessionSyncProcessor = ({
|
|
171
190
|
|
172
191
|
yield* FiberHandle.run(leaderPushingFiberHandle, backgroundLeaderPushing)
|
173
192
|
|
174
|
-
const getMergeCounter = () =>
|
175
|
-
clientSession.sqliteDb.select<{ mergeCounter: number }>(
|
176
|
-
sql`SELECT mergeCounter FROM ${SystemTables.LEADER_MERGE_COUNTER_TABLE} WHERE id = 0`,
|
177
|
-
)[0]?.mergeCounter ?? 0
|
178
|
-
|
179
193
|
// NOTE We need to lazily call `.pull` as we want the cursor to be updated
|
180
194
|
yield* Stream.suspend(() =>
|
181
|
-
clientSession.leaderThread.events.pull({
|
182
|
-
cursor: { mergeCounter: getMergeCounter(), eventNum: syncStateRef.current.localHead },
|
183
|
-
}),
|
195
|
+
clientSession.leaderThread.events.pull({ cursor: syncStateRef.current.upstreamHead }),
|
184
196
|
).pipe(
|
185
|
-
Stream.tap(({ payload
|
197
|
+
Stream.tap(({ payload }) =>
|
186
198
|
Effect.gen(function* () {
|
187
199
|
// yield* Effect.logDebug('ClientSessionSyncProcessor:pull', payload)
|
188
200
|
|
@@ -198,13 +210,13 @@ export const makeClientSessionSyncProcessor = ({
|
|
198
210
|
})
|
199
211
|
|
200
212
|
if (mergeResult._tag === 'unexpected-error') {
|
201
|
-
return yield*
|
213
|
+
return yield* new SyncError({ cause: mergeResult.message })
|
202
214
|
} else if (mergeResult._tag === 'reject') {
|
203
215
|
return shouldNeverHappen('Unexpected reject in client-session-sync-processor', mergeResult)
|
204
216
|
}
|
205
217
|
|
206
218
|
syncStateRef.current = mergeResult.newSyncState
|
207
|
-
syncStateUpdateQueue.offer(mergeResult.newSyncState)
|
219
|
+
yield* syncStateUpdateQueue.offer(mergeResult.newSyncState)
|
208
220
|
|
209
221
|
if (mergeResult._tag === 'rebase') {
|
210
222
|
span.addEvent('merge:pull:rebase', {
|
@@ -213,7 +225,7 @@ export const makeClientSessionSyncProcessor = ({
|
|
213
225
|
newEventsCount: mergeResult.newEvents.length,
|
214
226
|
rollbackCount: mergeResult.rollbackEvents.length,
|
215
227
|
res: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
216
|
-
|
228
|
+
rebaseGeneration: mergeResult.newSyncState.localHead.rebaseGeneration,
|
217
229
|
})
|
218
230
|
|
219
231
|
debugInfo.rebaseCount++
|
@@ -230,7 +242,6 @@ export const makeClientSessionSyncProcessor = ({
|
|
230
242
|
'merge:pull:rebase: rollback',
|
231
243
|
mergeResult.rollbackEvents.length,
|
232
244
|
...mergeResult.rollbackEvents.slice(0, 10).map((_) => _.toJSON()),
|
233
|
-
{ leaderMergeCounter },
|
234
245
|
).pipe(Effect.provide(runtime), Effect.runSync)
|
235
246
|
}
|
236
247
|
|
@@ -242,6 +253,7 @@ export const makeClientSessionSyncProcessor = ({
|
|
242
253
|
}
|
243
254
|
}
|
244
255
|
|
256
|
+
// Pushing rebased pending events to leader
|
245
257
|
yield* BucketQueue.offerAll(leaderPushQueue, mergeResult.newSyncState.pending)
|
246
258
|
} else {
|
247
259
|
span.addEvent('merge:pull:advance', {
|
@@ -249,7 +261,6 @@ export const makeClientSessionSyncProcessor = ({
|
|
249
261
|
payload: TRACE_VERBOSE ? JSON.stringify(payload) : undefined,
|
250
262
|
newEventsCount: mergeResult.newEvents.length,
|
251
263
|
res: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
252
|
-
leaderMergeCounter,
|
253
264
|
})
|
254
265
|
|
255
266
|
debugInfo.advanceCount++
|
@@ -261,12 +272,21 @@ export const makeClientSessionSyncProcessor = ({
|
|
261
272
|
for (const event of mergeResult.newEvents) {
|
262
273
|
// TODO apply changeset if available (will require tracking of write tables as well)
|
263
274
|
const decodedEventDef = Schema.decodeSync(eventSchema)(event)
|
264
|
-
const
|
265
|
-
|
275
|
+
const {
|
276
|
+
writeTables: newWriteTables,
|
277
|
+
sessionChangeset,
|
278
|
+
materializerHash,
|
279
|
+
} = materializeEvent(decodedEventDef, {
|
280
|
+
otelContext,
|
281
|
+
withChangeset: true,
|
282
|
+
materializerHashLeader: event.meta.materializerHashLeader,
|
283
|
+
})
|
284
|
+
for (const table of newWriteTables) {
|
266
285
|
writeTables.add(table)
|
267
286
|
}
|
268
287
|
|
269
|
-
event.meta.sessionChangeset =
|
288
|
+
event.meta.sessionChangeset = sessionChangeset
|
289
|
+
event.meta.materializerHashSession = materializerHash
|
270
290
|
}
|
271
291
|
|
272
292
|
refreshTables(writeTables)
|
@@ -321,6 +341,9 @@ export interface ClientSessionSyncProcessor {
|
|
321
341
|
writeTables: Set<string>
|
322
342
|
}
|
323
343
|
boot: Effect.Effect<void, UnexpectedError, Scope.Scope>
|
344
|
+
/**
|
345
|
+
* Only used for debugging / observability.
|
346
|
+
*/
|
324
347
|
syncState: Subscribable.Subscribable<SyncState.SyncState>
|
325
348
|
debug: {
|
326
349
|
print: () => void
|
@@ -14,15 +14,9 @@ export declare class IGraph<
|
|
14
14
|
}
|
15
15
|
|
16
16
|
export const DirectedGraph = class DirectedGraph extends graphology.DirectedGraph {
|
17
|
-
constructor(options?: graphologyTypes.GraphOptions) {
|
18
|
-
super(options)
|
19
|
-
}
|
20
17
|
} as typeof IGraph
|
21
18
|
|
22
19
|
export const Graph = class Graph extends graphology.Graph {
|
23
|
-
constructor(options?: graphologyTypes.GraphOptions) {
|
24
|
-
super(options)
|
25
|
-
}
|
26
20
|
} as typeof IGraph
|
27
21
|
|
28
22
|
// export const graphology = graphology_ as graphologyTypes
|
@@ -64,7 +64,7 @@ const factsSetToString = (facts: EventDefFacts, prefix: string) =>
|
|
64
64
|
export const customSerializer = {
|
65
65
|
test: (val: unknown) => Array.isArray(val),
|
66
66
|
print: (val: unknown[], _serialize: (item: unknown) => string) => {
|
67
|
-
return
|
67
|
+
return `[\n${(val as any[]).map((item) => ` ${customStringify(item)}`).join('\n')}\n]`
|
68
68
|
},
|
69
69
|
} as any
|
70
70
|
|
@@ -144,7 +144,10 @@ export const toEventNodes = (
|
|
144
144
|
|
145
145
|
const eventNodes = partialEvents.map((partialEvent) => {
|
146
146
|
const eventDef = eventDefs[partialEvent.name]!
|
147
|
-
const eventNum = EventSequenceNumber.nextPair(
|
147
|
+
const eventNum = EventSequenceNumber.nextPair({
|
148
|
+
seqNum: currentEventSequenceNumber,
|
149
|
+
isClient: eventDef.options.clientOnly,
|
150
|
+
}).seqNum
|
148
151
|
currentEventSequenceNumber = eventNum
|
149
152
|
|
150
153
|
const factsSnapshot = factsSnapshotForDag(historyDagFromNodes(nodesAcc, { skipFactsCheck: true }), undefined)
|
@@ -221,8 +224,14 @@ const getParentNum = (eventNum: EventSequenceNumber.EventSequenceNumber): EventS
|
|
221
224
|
const clientParentNum = eventNum.client - 1
|
222
225
|
|
223
226
|
if (clientParentNum < 0) {
|
224
|
-
return EventSequenceNumber.make({
|
227
|
+
return EventSequenceNumber.make({
|
228
|
+
global: globalParentNum - 1,
|
229
|
+
client: EventSequenceNumber.clientDefault,
|
230
|
+
})
|
225
231
|
}
|
226
232
|
|
227
|
-
return EventSequenceNumber.make({
|
233
|
+
return EventSequenceNumber.make({
|
234
|
+
global: globalParentNum,
|
235
|
+
client: clientParentNum,
|
236
|
+
})
|
228
237
|
}
|
package/src/sync/sync.ts
CHANGED
@@ -80,6 +80,7 @@ export type SyncBackend<TSyncMetadata = Schema.JsonValue> = {
|
|
80
80
|
|
81
81
|
export class IsOfflineError extends Schema.TaggedError<IsOfflineError>()('IsOfflineError', {}) {}
|
82
82
|
|
83
|
+
// TODO gt rid of this error in favour of SyncError
|
83
84
|
export class InvalidPushError extends Schema.TaggedError<InvalidPushError>()('InvalidPushError', {
|
84
85
|
reason: Schema.Union(
|
85
86
|
Schema.TaggedStruct('Unexpected', {
|
@@ -92,10 +93,12 @@ export class InvalidPushError extends Schema.TaggedError<InvalidPushError>()('In
|
|
92
93
|
),
|
93
94
|
}) {}
|
94
95
|
|
96
|
+
// TODO gt rid of this error in favour of SyncError
|
95
97
|
export class InvalidPullError extends Schema.TaggedError<InvalidPullError>()('InvalidPullError', {
|
96
98
|
message: Schema.String,
|
97
99
|
}) {}
|
98
100
|
|
101
|
+
// TODO gt rid of this error in favour of SyncError
|
99
102
|
export class LeaderAheadError extends Schema.TaggedError<LeaderAheadError>()('LeaderAheadError', {
|
100
103
|
minimumExpectedNum: EventSequenceNumber.EventSequenceNumber,
|
101
104
|
providedNum: EventSequenceNumber.EventSequenceNumber,
|
@@ -7,10 +7,10 @@ import * as SyncState from './syncstate.js'
|
|
7
7
|
|
8
8
|
class TestEvent extends LiveStoreEvent.EncodedWithMeta {
|
9
9
|
constructor(
|
10
|
-
seqNum: EventSequenceNumber.
|
11
|
-
parentSeqNum: EventSequenceNumber.
|
10
|
+
seqNum: EventSequenceNumber.EventSequenceNumberInput,
|
11
|
+
parentSeqNum: EventSequenceNumber.EventSequenceNumberInput,
|
12
12
|
public readonly payload: string,
|
13
|
-
public readonly
|
13
|
+
public readonly isClient: boolean,
|
14
14
|
) {
|
15
15
|
super({
|
16
16
|
seqNum: EventSequenceNumber.make(seqNum),
|
@@ -22,8 +22,8 @@ class TestEvent extends LiveStoreEvent.EncodedWithMeta {
|
|
22
22
|
})
|
23
23
|
}
|
24
24
|
|
25
|
-
rebase_ = (parentSeqNum: EventSequenceNumber.EventSequenceNumber) => {
|
26
|
-
return this.rebase(parentSeqNum, this.
|
25
|
+
rebase_ = (parentSeqNum: EventSequenceNumber.EventSequenceNumber, rebaseGeneration: number) => {
|
26
|
+
return this.rebase({ parentSeqNum, isClient: this.isClient, rebaseGeneration })
|
27
27
|
}
|
28
28
|
|
29
29
|
// Only used for Vitest printing
|
@@ -41,7 +41,7 @@ const e2_1 = new TestEvent({ global: 2, client: 1 }, e2_0.seqNum, 'a', true)
|
|
41
41
|
|
42
42
|
const isEqualEvent = LiveStoreEvent.isEqualEncoded
|
43
43
|
|
44
|
-
const isClientEvent = (event: LiveStoreEvent.EncodedWithMeta) => (event as TestEvent).
|
44
|
+
const isClientEvent = (event: LiveStoreEvent.EncodedWithMeta) => (event as TestEvent).isClient
|
45
45
|
|
46
46
|
describe('syncstate', () => {
|
47
47
|
describe('merge', () => {
|
@@ -62,8 +62,8 @@ describe('syncstate', () => {
|
|
62
62
|
upstreamHead: EventSequenceNumber.ROOT,
|
63
63
|
localHead: e2_0.seqNum,
|
64
64
|
})
|
65
|
-
const e1_0_e2_0 = e1_0.rebase_(e2_0.seqNum)
|
66
|
-
const e1_1_e2_1 = e1_1.rebase_(e1_0_e2_0.seqNum)
|
65
|
+
const e1_0_e2_0 = e1_0.rebase_(e2_0.seqNum, 0)
|
66
|
+
const e1_1_e2_1 = e1_1.rebase_(e1_0_e2_0.seqNum, 0)
|
67
67
|
const result = merge({
|
68
68
|
syncState,
|
69
69
|
payload: SyncState.PayloadUpstreamRebase.make({
|
@@ -71,7 +71,7 @@ describe('syncstate', () => {
|
|
71
71
|
newEvents: [e1_0_e2_0, e1_1_e2_1],
|
72
72
|
}),
|
73
73
|
})
|
74
|
-
const e2_0_e3_0 = e2_0.rebase_(e1_0_e2_0.seqNum)
|
74
|
+
const e2_0_e3_0 = e2_0.rebase_(e1_0_e2_0.seqNum, 1)
|
75
75
|
expectRebase(result)
|
76
76
|
expectEventArraysEqual(result.newSyncState.pending, [e2_0_e3_0])
|
77
77
|
expect(result.newSyncState.upstreamHead).toMatchObject(e1_1_e2_1.seqNum)
|
@@ -86,7 +86,7 @@ describe('syncstate', () => {
|
|
86
86
|
upstreamHead: EventSequenceNumber.ROOT,
|
87
87
|
localHead: e2_0.seqNum,
|
88
88
|
})
|
89
|
-
const e1_1_e2_0 = e1_1.rebase_(e1_0.seqNum)
|
89
|
+
const e1_1_e2_0 = e1_1.rebase_(e1_0.seqNum, 0)
|
90
90
|
const result = merge({
|
91
91
|
syncState,
|
92
92
|
payload: SyncState.PayloadUpstreamRebase.make({
|
@@ -94,7 +94,7 @@ describe('syncstate', () => {
|
|
94
94
|
rollbackEvents: [e1_1],
|
95
95
|
}),
|
96
96
|
})
|
97
|
-
const e2_0_e3_0 = e2_0.rebase_(e1_1_e2_0.seqNum)
|
97
|
+
const e2_0_e3_0 = e2_0.rebase_(e1_1_e2_0.seqNum, 1)
|
98
98
|
expectRebase(result)
|
99
99
|
expectEventArraysEqual(result.newSyncState.pending, [e2_0_e3_0])
|
100
100
|
expect(result.newSyncState.upstreamHead).toMatchObject(e1_1_e2_0.seqNum)
|
@@ -326,7 +326,7 @@ describe('syncstate', () => {
|
|
326
326
|
})
|
327
327
|
const result = merge({ syncState, payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: [e1_1] }) })
|
328
328
|
|
329
|
-
const e1_0_e1_2 = e1_0.rebase_(e1_1.seqNum)
|
329
|
+
const e1_0_e1_2 = e1_0.rebase_(e1_1.seqNum, 1)
|
330
330
|
|
331
331
|
expectRebase(result)
|
332
332
|
expectEventArraysEqual(result.newSyncState.pending, [e1_0_e1_2])
|
@@ -344,7 +344,7 @@ describe('syncstate', () => {
|
|
344
344
|
localHead: e2_0_b.seqNum,
|
345
345
|
})
|
346
346
|
const result = merge({ syncState, payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: [e2_0] }) })
|
347
|
-
const e2_0_e3_0 = e2_0_b.rebase_(e2_0.seqNum)
|
347
|
+
const e2_0_e3_0 = e2_0_b.rebase_(e2_0.seqNum, 1)
|
348
348
|
|
349
349
|
expectRebase(result)
|
350
350
|
expectEventArraysEqual(result.newSyncState.pending, [e2_0_e3_0])
|
@@ -365,7 +365,7 @@ describe('syncstate', () => {
|
|
365
365
|
payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: [e1_1, e1_2, e1_3, e2_0] }),
|
366
366
|
})
|
367
367
|
|
368
|
-
const e1_0_e3_0 = e1_0.rebase_(e2_0.seqNum)
|
368
|
+
const e1_0_e3_0 = e1_0.rebase_(e2_0.seqNum, 1)
|
369
369
|
|
370
370
|
expectRebase(result)
|
371
371
|
expectEventArraysEqual(result.newSyncState.pending, [e1_0_e3_0])
|
@@ -384,7 +384,7 @@ describe('syncstate', () => {
|
|
384
384
|
payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: [e1_0, e1_2, e1_3, e2_0] }),
|
385
385
|
})
|
386
386
|
|
387
|
-
const e1_1_e2_1 = e1_1.rebase_(e2_0.seqNum)
|
387
|
+
const e1_1_e2_1 = e1_1.rebase_(e2_0.seqNum, 1)
|
388
388
|
|
389
389
|
expectRebase(result)
|
390
390
|
expectEventArraysEqual(result.newSyncState.pending, [e1_1_e2_1])
|
@@ -405,8 +405,8 @@ describe('syncstate', () => {
|
|
405
405
|
payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: [e1_1, e1_2, e1_3, e2_0] }),
|
406
406
|
})
|
407
407
|
|
408
|
-
const e1_0_e2_1 = e1_0.rebase_(e2_0.seqNum)
|
409
|
-
const e1_1_e2_2 = e1_1.rebase_(e1_0_e2_1.seqNum)
|
408
|
+
const e1_0_e2_1 = e1_0.rebase_(e2_0.seqNum, 1)
|
409
|
+
const e1_1_e2_2 = e1_1.rebase_(e1_0_e2_1.seqNum, 1)
|
410
410
|
|
411
411
|
expectRebase(result)
|
412
412
|
expectEventArraysEqual(result.newSyncState.pending, [e1_0_e2_1, e1_1_e2_2])
|
package/src/sync/syncstate.ts
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
import { casesHandled, LS_DEV, shouldNeverHappen } from '@livestore/utils'
|
2
2
|
import { Match, ReadonlyArray, Schema } from '@livestore/utils/effect'
|
3
3
|
|
4
|
-
import { UnexpectedError } from '../adapter-types.js'
|
5
4
|
import * as EventSequenceNumber from '../schema/EventSequenceNumber.js'
|
6
5
|
import * as LiveStoreEvent from '../schema/LiveStoreEvent.js'
|
7
6
|
|
@@ -162,7 +161,7 @@ export class MergeResultReject extends Schema.Class<MergeResultReject>('MergeRes
|
|
162
161
|
|
163
162
|
export class MergeResultUnexpectedError extends Schema.Class<MergeResultUnexpectedError>('MergeResultUnexpectedError')({
|
164
163
|
_tag: Schema.Literal('unexpected-error'),
|
165
|
-
|
164
|
+
message: Schema.String,
|
166
165
|
}) {}
|
167
166
|
|
168
167
|
export class MergeResult extends Schema.Union(
|
@@ -172,15 +171,29 @@ export class MergeResult extends Schema.Union(
|
|
172
171
|
MergeResultUnexpectedError,
|
173
172
|
) {}
|
174
173
|
|
175
|
-
const
|
174
|
+
export const payloadFromMergeResult = (
|
175
|
+
mergeResult: typeof MergeResultAdvance.Type | typeof MergeResultRebase.Type,
|
176
|
+
): typeof PayloadUpstream.Type =>
|
177
|
+
Match.value(mergeResult).pipe(
|
178
|
+
Match.tag('advance', (result) => ({
|
179
|
+
_tag: 'upstream-advance' as const,
|
180
|
+
newEvents: result.newEvents,
|
181
|
+
})),
|
182
|
+
Match.tag('rebase', (result) => ({
|
183
|
+
_tag: 'upstream-rebase' as const,
|
184
|
+
newEvents: result.newEvents,
|
185
|
+
rollbackEvents: result.rollbackEvents,
|
186
|
+
})),
|
187
|
+
Match.exhaustive,
|
188
|
+
)
|
189
|
+
|
190
|
+
const unexpectedError = (message: string): MergeResultUnexpectedError => {
|
176
191
|
if (LS_DEV) {
|
192
|
+
// biome-ignore lint/suspicious/noDebugger: debug
|
177
193
|
debugger
|
178
194
|
}
|
179
195
|
|
180
|
-
return MergeResultUnexpectedError.make({
|
181
|
-
_tag: 'unexpected-error',
|
182
|
-
cause: new UnexpectedError({ cause }),
|
183
|
-
})
|
196
|
+
return MergeResultUnexpectedError.make({ _tag: 'unexpected-error', message })
|
184
197
|
}
|
185
198
|
|
186
199
|
// TODO Idea: call merge recursively through hierarchy levels
|
@@ -382,7 +395,10 @@ export const merge = ({
|
|
382
395
|
EventSequenceNumber.isGreaterThan(newEventsFirst.seqNum, syncState.localHead) === false
|
383
396
|
|
384
397
|
if (invalidEventSequenceNumber) {
|
385
|
-
const expectedMinimumId = EventSequenceNumber.nextPair(
|
398
|
+
const expectedMinimumId = EventSequenceNumber.nextPair({
|
399
|
+
seqNum: syncState.localHead,
|
400
|
+
isClient: true,
|
401
|
+
}).seqNum
|
386
402
|
return validateMergeResult(
|
387
403
|
MergeResultReject.make({
|
388
404
|
_tag: 'reject',
|
@@ -466,9 +482,14 @@ const rebaseEvents = ({
|
|
466
482
|
isClientEvent: (event: LiveStoreEvent.EncodedWithMeta) => boolean
|
467
483
|
}): ReadonlyArray<LiveStoreEvent.EncodedWithMeta> => {
|
468
484
|
let prevEventSequenceNumber = baseEventSequenceNumber
|
485
|
+
const rebaseGeneration = baseEventSequenceNumber.rebaseGeneration + 1
|
469
486
|
return events.map((event) => {
|
470
|
-
const
|
471
|
-
const newEvent = event.rebase(
|
487
|
+
const isClient = isClientEvent(event)
|
488
|
+
const newEvent = event.rebase({
|
489
|
+
parentSeqNum: prevEventSequenceNumber,
|
490
|
+
isClient,
|
491
|
+
rebaseGeneration,
|
492
|
+
})
|
472
493
|
prevEventSequenceNumber = newEvent.seqNum
|
473
494
|
return newEvent
|
474
495
|
})
|
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.
|
5
|
+
export const liveStoreVersion = '0.3.2-dev.0' as const
|
6
6
|
|
7
7
|
/**
|
8
8
|
* This version number is incremented whenever the internal storage format changes in a breaking way.
|