@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
@@ -5,7 +5,7 @@ import { BucketQueue, Effect, FiberHandle, Queue, Schema, Stream, Subscribable }
|
|
5
5
|
import * as otel from '@opentelemetry/api'
|
6
6
|
|
7
7
|
import type { ClientSession, UnexpectedError } from '../adapter-types.js'
|
8
|
-
import * as
|
8
|
+
import * as EventSequenceNumber from '../schema/EventSequenceNumber.js'
|
9
9
|
import * as LiveStoreEvent from '../schema/LiveStoreEvent.js'
|
10
10
|
import { getEventDef, type LiveStoreSchema, SystemTables } from '../schema/mod.js'
|
11
11
|
import { sql } from '../util.js'
|
@@ -77,16 +77,16 @@ export const makeClientSessionSyncProcessor = ({
|
|
77
77
|
const push: ClientSessionSyncProcessor['push'] = (batch, { otelContext }) => {
|
78
78
|
// TODO validate batch
|
79
79
|
|
80
|
-
let
|
80
|
+
let baseEventSequenceNumber = syncStateRef.current.localHead
|
81
81
|
const encodedEventDefs = batch.map(({ name, args }) => {
|
82
82
|
const eventDef = getEventDef(schema, name)
|
83
|
-
const
|
84
|
-
|
83
|
+
const nextNumPair = EventSequenceNumber.nextPair(baseEventSequenceNumber, eventDef.eventDef.options.clientOnly)
|
84
|
+
baseEventSequenceNumber = nextNumPair.seqNum
|
85
85
|
return new LiveStoreEvent.EncodedWithMeta(
|
86
86
|
Schema.encodeUnknownSync(eventSchema)({
|
87
87
|
name,
|
88
88
|
args,
|
89
|
-
...
|
89
|
+
...nextNumPair,
|
90
90
|
clientId: clientSession.clientId,
|
91
91
|
sessionId: clientSession.sessionId,
|
92
92
|
}),
|
@@ -179,7 +179,7 @@ export const makeClientSessionSyncProcessor = ({
|
|
179
179
|
// NOTE We need to lazily call `.pull` as we want the cursor to be updated
|
180
180
|
yield* Stream.suspend(() =>
|
181
181
|
clientSession.leaderThread.events.pull({
|
182
|
-
cursor: { mergeCounter: getMergeCounter(),
|
182
|
+
cursor: { mergeCounter: getMergeCounter(), eventNum: syncStateRef.current.localHead },
|
183
183
|
}),
|
184
184
|
).pipe(
|
185
185
|
Stream.tap(({ payload, mergeCounter: leaderMergeCounter }) =>
|
@@ -272,7 +272,7 @@ export const makeClientSessionSyncProcessor = ({
|
|
272
272
|
refreshTables(writeTables)
|
273
273
|
}).pipe(
|
274
274
|
Effect.tapCauseLogPretty,
|
275
|
-
Effect.catchAllCause((cause) =>
|
275
|
+
Effect.catchAllCause((cause) => clientSession.shutdown(cause)),
|
276
276
|
),
|
277
277
|
),
|
278
278
|
Stream.runDrain,
|
@@ -1,6 +1,6 @@
|
|
1
|
+
import { EventSequenceNumber } from '../../schema/mod.js'
|
1
2
|
import { replacesFacts } from './facts.js'
|
2
3
|
import { graphologyDag } from './graphology_.js'
|
3
|
-
import { eventIdToString } from './history-dag.js'
|
4
4
|
import type { HistoryDag } from './history-dag-common.js'
|
5
5
|
import { emptyHistoryDag } from './history-dag-common.js'
|
6
6
|
|
@@ -17,25 +17,25 @@ export const compactEvents = (inputDag: HistoryDag): { dag: HistoryDag; compacte
|
|
17
17
|
const dag = inputDag.copy()
|
18
18
|
const compactedEventCount = 0
|
19
19
|
|
20
|
-
const
|
20
|
+
const orderedEventSequenceNumberStrs = graphologyDag.topologicalSort(dag).reverse()
|
21
21
|
|
22
22
|
// drop root
|
23
|
-
|
23
|
+
orderedEventSequenceNumberStrs.pop()
|
24
24
|
|
25
|
-
for (const
|
26
|
-
if (dag.hasNode(
|
25
|
+
for (const eventNumStr of orderedEventSequenceNumberStrs) {
|
26
|
+
if (dag.hasNode(eventNumStr) === false) {
|
27
27
|
continue
|
28
28
|
}
|
29
29
|
|
30
|
-
const subDagsForEvent = Array.from(makeSubDagsForEvent(dag,
|
30
|
+
const subDagsForEvent = Array.from(makeSubDagsForEvent(dag, eventNumStr))
|
31
31
|
for (const subDag of subDagsForEvent) {
|
32
32
|
let shouldRetry = true
|
33
33
|
while (shouldRetry) {
|
34
|
-
const subDagsInHistory = findSubDagsInHistory(dag, subDag,
|
34
|
+
const subDagsInHistory = findSubDagsInHistory(dag, subDag, eventNumStr)
|
35
35
|
|
36
36
|
// console.debug(
|
37
37
|
// 'subDagsInHistory',
|
38
|
-
//
|
38
|
+
// eventNumStr,
|
39
39
|
// 'target',
|
40
40
|
// subDag.nodes(),
|
41
41
|
// 'found',
|
@@ -65,9 +65,9 @@ export const compactEvents = (inputDag: HistoryDag): { dag: HistoryDag; compacte
|
|
65
65
|
return { dag, compactedEventCount }
|
66
66
|
}
|
67
67
|
|
68
|
-
function* makeSubDagsForEvent(inputDag: HistoryDag,
|
69
|
-
/** Map from
|
70
|
-
let nextIterationEls: Map<string, string[]> = new Map([[
|
68
|
+
function* makeSubDagsForEvent(inputDag: HistoryDag, eventNumStr: string): Generator<HistoryDag> {
|
69
|
+
/** Map from eventNumStr to array of eventNumStrs that are dependencies */
|
70
|
+
let nextIterationEls: Map<string, string[]> = new Map([[eventNumStr, []]])
|
71
71
|
let previousDag: HistoryDag | undefined
|
72
72
|
|
73
73
|
while (nextIterationEls.size > 0) {
|
@@ -77,19 +77,22 @@ function* makeSubDagsForEvent(inputDag: HistoryDag, eventIdStr: string): Generat
|
|
77
77
|
const currentIterationEls = new Map(nextIterationEls)
|
78
78
|
nextIterationEls = new Map()
|
79
79
|
|
80
|
-
for (const [
|
81
|
-
const node = inputDag.getNodeAttributes(
|
82
|
-
if (subDag.hasNode(
|
83
|
-
subDag.addNode(
|
80
|
+
for (const [currentEventSequenceNumberStr, edgeTargetIdStrs] of currentIterationEls) {
|
81
|
+
const node = inputDag.getNodeAttributes(currentEventSequenceNumberStr)
|
82
|
+
if (subDag.hasNode(currentEventSequenceNumberStr) === false) {
|
83
|
+
subDag.addNode(currentEventSequenceNumberStr, { ...node })
|
84
84
|
}
|
85
85
|
for (const edgeTargetIdStr of edgeTargetIdStrs) {
|
86
|
-
subDag.addEdge(
|
86
|
+
subDag.addEdge(currentEventSequenceNumberStr, edgeTargetIdStr, { type: 'facts' })
|
87
87
|
}
|
88
88
|
|
89
|
-
for (const depEdge of inputDag.outboundEdgeEntries(
|
89
|
+
for (const depEdge of inputDag.outboundEdgeEntries(currentEventSequenceNumberStr)) {
|
90
90
|
if (depEdge.attributes.type === 'facts') {
|
91
|
-
const
|
92
|
-
nextIterationEls.set(
|
91
|
+
const depEventSequenceNumberStr = depEdge.target
|
92
|
+
nextIterationEls.set(depEventSequenceNumberStr, [
|
93
|
+
...(nextIterationEls.get(depEventSequenceNumberStr) ?? []),
|
94
|
+
currentEventSequenceNumberStr,
|
95
|
+
])
|
93
96
|
}
|
94
97
|
}
|
95
98
|
}
|
@@ -102,23 +105,23 @@ function* makeSubDagsForEvent(inputDag: HistoryDag, eventIdStr: string): Generat
|
|
102
105
|
}
|
103
106
|
|
104
107
|
/**
|
105
|
-
* Iterates over all events from root to `
|
108
|
+
* Iterates over all events from root to `upToExclEventSequenceNumberStr`
|
106
109
|
* and collects all valid sub dags that are replaced by `targetSubDag`.
|
107
110
|
*/
|
108
111
|
const findSubDagsInHistory = (
|
109
112
|
inputDag: HistoryDag,
|
110
113
|
targetSubDag: HistoryDag,
|
111
|
-
|
114
|
+
upToExclEventSequenceNumberStr: string,
|
112
115
|
): { subDags: HistoryDag[]; allOutsideDependencies: string[][] } => {
|
113
116
|
const subDags: HistoryDag[] = []
|
114
117
|
const allOutsideDependencies: string[][] = []
|
115
118
|
|
116
|
-
for (const
|
117
|
-
if (
|
119
|
+
for (const eventNumStr of graphologyDag.topologicalSort(inputDag)) {
|
120
|
+
if (eventNumStr === upToExclEventSequenceNumberStr) {
|
118
121
|
break
|
119
122
|
}
|
120
123
|
|
121
|
-
for (const subDag of makeSubDagsForEvent(inputDag,
|
124
|
+
for (const subDag of makeSubDagsForEvent(inputDag, eventNumStr)) {
|
122
125
|
// console.debug('findSubDagsInHistory', 'target', targetSubDag.nodes(), 'subDag', subDag.nodes())
|
123
126
|
if (subDag.size < targetSubDag.size) {
|
124
127
|
continue
|
@@ -152,9 +155,9 @@ const outsideDependenciesForDag = (subDag: HistoryDag, inputDag: HistoryDag) =>
|
|
152
155
|
for (const nodeIdStr of subDag.nodes()) {
|
153
156
|
for (const edgeEntry of inputDag.outboundEdgeEntries(nodeIdStr)) {
|
154
157
|
if (edgeEntry.attributes.type === 'facts') {
|
155
|
-
const
|
156
|
-
if (subDag.hasNode(
|
157
|
-
outsideDependencies.push(
|
158
|
+
const depEventSequenceNumberStr = edgeEntry.target
|
159
|
+
if (subDag.hasNode(depEventSequenceNumberStr) === false) {
|
160
|
+
outsideDependencies.push(depEventSequenceNumberStr)
|
158
161
|
}
|
159
162
|
}
|
160
163
|
}
|
@@ -201,19 +204,19 @@ const dagReplacesDag = (dagA: HistoryDag, dagB: HistoryDag): boolean => {
|
|
201
204
|
return true
|
202
205
|
}
|
203
206
|
|
204
|
-
const removeEvent = (dag: HistoryDag,
|
205
|
-
// console.debug('removing event',
|
206
|
-
const event = dag.getNodeAttributes(
|
207
|
-
const
|
208
|
-
const childEdges = dag.outboundEdgeEntries(
|
207
|
+
const removeEvent = (dag: HistoryDag, eventNumStr: string) => {
|
208
|
+
// console.debug('removing event', eventNumStr)
|
209
|
+
const event = dag.getNodeAttributes(eventNumStr)
|
210
|
+
const parentSeqNumStr = EventSequenceNumber.toString(event.parentSeqNum)
|
211
|
+
const childEdges = dag.outboundEdgeEntries(eventNumStr)
|
209
212
|
|
210
213
|
for (const childEdge of childEdges) {
|
211
214
|
if (childEdge.attributes.type === 'parent') {
|
212
215
|
const childEvent = dag.getNodeAttributes(childEdge.target)
|
213
|
-
childEvent.
|
214
|
-
dag.addEdge(
|
216
|
+
childEvent.parentSeqNum = { ...event.parentSeqNum }
|
217
|
+
dag.addEdge(parentSeqNumStr, EventSequenceNumber.toString(childEvent.seqNum), { type: 'parent' })
|
215
218
|
}
|
216
219
|
}
|
217
220
|
|
218
|
-
dag.dropNode(
|
221
|
+
dag.dropNode(eventNumStr)
|
219
222
|
}
|
package/src/sync/next/facts.ts
CHANGED
@@ -7,18 +7,18 @@ import type {
|
|
7
7
|
EventDefFactsSnapshot,
|
8
8
|
FactsCallback,
|
9
9
|
} from '../../schema/EventDef.js'
|
10
|
-
import type * as
|
10
|
+
import type * as EventSequenceNumber from '../../schema/EventSequenceNumber.js'
|
11
11
|
import { graphologyDag } from './graphology_.js'
|
12
12
|
import { EMPTY_FACT_VALUE, type HistoryDag, type HistoryDagNode } from './history-dag-common.js'
|
13
13
|
|
14
14
|
export const factsSnapshotForEvents = (
|
15
15
|
events: HistoryDagNode[],
|
16
|
-
|
16
|
+
endEventSequenceNumber: EventSequenceNumber.EventSequenceNumber,
|
17
17
|
): EventDefFactsSnapshot => {
|
18
18
|
const facts = new Map<string, any>()
|
19
19
|
|
20
20
|
for (const event of events) {
|
21
|
-
if (
|
21
|
+
if (compareEventSequenceNumbers(event.seqNum, endEventSequenceNumber) > 0) {
|
22
22
|
return facts
|
23
23
|
}
|
24
24
|
|
@@ -30,15 +30,15 @@ export const factsSnapshotForEvents = (
|
|
30
30
|
|
31
31
|
export const factsSnapshotForDag = (
|
32
32
|
dag: HistoryDag,
|
33
|
-
|
33
|
+
endEventSequenceNumber: EventSequenceNumber.EventSequenceNumber | undefined,
|
34
34
|
): EventDefFactsSnapshot => {
|
35
35
|
const facts = new Map<string, any>()
|
36
36
|
|
37
|
-
const
|
37
|
+
const orderedEventSequenceNumberStrs = graphologyDag.topologicalSort(dag)
|
38
38
|
|
39
|
-
for (let i = 0; i <
|
40
|
-
const event = dag.getNodeAttributes(
|
41
|
-
if (
|
39
|
+
for (let i = 0; i < orderedEventSequenceNumberStrs.length; i++) {
|
40
|
+
const event = dag.getNodeAttributes(orderedEventSequenceNumberStrs[i]!)
|
41
|
+
if (endEventSequenceNumber !== undefined && compareEventSequenceNumbers(event.seqNum, endEventSequenceNumber) > 0) {
|
42
42
|
return facts
|
43
43
|
}
|
44
44
|
|
@@ -226,7 +226,10 @@ export const getFactsGroupForEventArgs = ({
|
|
226
226
|
return facts
|
227
227
|
}
|
228
228
|
|
229
|
-
export const
|
229
|
+
export const compareEventSequenceNumbers = (
|
230
|
+
a: EventSequenceNumber.EventSequenceNumber,
|
231
|
+
b: EventSequenceNumber.EventSequenceNumber,
|
232
|
+
) => {
|
230
233
|
if (a.global !== b.global) {
|
231
234
|
return a.global - b.global
|
232
235
|
}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import type { EventDefFactsGroup } from '../../schema/EventDef.js'
|
2
|
-
import * as
|
2
|
+
import * as EventSequenceNumber from '../../schema/EventSequenceNumber.js'
|
3
3
|
import { graphology } from './graphology_.js'
|
4
4
|
|
5
5
|
export const connectionTypeOptions = ['parent', 'facts'] as const
|
@@ -20,11 +20,14 @@ export const emptyHistoryDag = (): HistoryDag =>
|
|
20
20
|
})
|
21
21
|
|
22
22
|
// TODO consider making `ROOT_ID` parent to itself
|
23
|
-
export const
|
23
|
+
export const rootParentNum = EventSequenceNumber.make({
|
24
|
+
global: EventSequenceNumber.ROOT.global - 1,
|
25
|
+
client: EventSequenceNumber.clientDefault,
|
26
|
+
})
|
24
27
|
|
25
28
|
export type HistoryDagNode = {
|
26
|
-
|
27
|
-
|
29
|
+
seqNum: EventSequenceNumber.EventSequenceNumber
|
30
|
+
parentSeqNum: EventSequenceNumber.EventSequenceNumber
|
28
31
|
name: string
|
29
32
|
args: any
|
30
33
|
/** Facts are being used for conflict detection and history compaction */
|
@@ -35,8 +38,8 @@ export type HistoryDagNode = {
|
|
35
38
|
}
|
36
39
|
|
37
40
|
export const rootEventNode: HistoryDagNode = {
|
38
|
-
|
39
|
-
|
41
|
+
seqNum: EventSequenceNumber.ROOT,
|
42
|
+
parentSeqNum: rootParentNum,
|
40
43
|
// unused below
|
41
44
|
name: '__Root__',
|
42
45
|
args: {},
|
@@ -1,9 +1,6 @@
|
|
1
|
-
import
|
1
|
+
import * as EventSequenceNumber from '../../schema/EventSequenceNumber.js'
|
2
2
|
import { factsToString, validateFacts } from './facts.js'
|
3
|
-
import { emptyHistoryDag, type HistoryDagNode,
|
4
|
-
|
5
|
-
export const eventIdToString = (eventId: EventId.EventId) =>
|
6
|
-
eventId.client === 0 ? eventId.global.toString() : `${eventId.global}.${eventId.client}`
|
3
|
+
import { emptyHistoryDag, type HistoryDagNode, rootParentNum } from './history-dag-common.js'
|
7
4
|
|
8
5
|
export const historyDagFromNodes = (dagNodes: HistoryDagNode[], options?: { skipFactsCheck: boolean }) => {
|
9
6
|
if (options?.skipFactsCheck !== true) {
|
@@ -21,11 +18,13 @@ export const historyDagFromNodes = (dagNodes: HistoryDagNode[], options?: { skip
|
|
21
18
|
|
22
19
|
const dag = emptyHistoryDag()
|
23
20
|
|
24
|
-
dagNodes.forEach((node) => dag.addNode(
|
21
|
+
dagNodes.forEach((node) => dag.addNode(EventSequenceNumber.toString(node.seqNum), node))
|
25
22
|
|
26
23
|
dagNodes.forEach((node) => {
|
27
|
-
if (
|
28
|
-
dag.addEdge(
|
24
|
+
if (EventSequenceNumber.toString(node.parentSeqNum) !== EventSequenceNumber.toString(rootParentNum)) {
|
25
|
+
dag.addEdge(EventSequenceNumber.toString(node.parentSeqNum), EventSequenceNumber.toString(node.seqNum), {
|
26
|
+
type: 'parent',
|
27
|
+
})
|
29
28
|
}
|
30
29
|
})
|
31
30
|
|
@@ -34,28 +33,28 @@ export const historyDagFromNodes = (dagNodes: HistoryDagNode[], options?: { skip
|
|
34
33
|
for (const factKey of factKeys) {
|
35
34
|
// Find the first ancestor node with a matching fact key (via modifySet or modifyUnset) by traversing the graph backwards via the parent edges
|
36
35
|
const depNode = (() => {
|
37
|
-
let
|
36
|
+
let currentSeqNumStr = EventSequenceNumber.toString(node.seqNum)
|
38
37
|
|
39
|
-
while (
|
40
|
-
const parentEdge = dag.inEdges(
|
38
|
+
while (currentSeqNumStr !== EventSequenceNumber.toString(rootParentNum)) {
|
39
|
+
const parentEdge = dag.inEdges(currentSeqNumStr).find((e) => dag.getEdgeAttribute(e, 'type') === 'parent')
|
41
40
|
if (!parentEdge) return null
|
42
41
|
|
43
|
-
const
|
44
|
-
const parentNode = dag.getNodeAttributes(
|
42
|
+
const parentSeqNumStr = dag.source(parentEdge)
|
43
|
+
const parentNode = dag.getNodeAttributes(parentSeqNumStr)
|
45
44
|
|
46
45
|
if (parentNode.factsGroup.modifySet.has(factKey) || parentNode.factsGroup.modifyUnset.has(factKey)) {
|
47
46
|
return parentNode
|
48
47
|
}
|
49
48
|
|
50
|
-
|
49
|
+
currentSeqNumStr = parentSeqNumStr
|
51
50
|
}
|
52
51
|
|
53
52
|
return null
|
54
53
|
})()
|
55
54
|
|
56
55
|
if (depNode) {
|
57
|
-
const depNodeIdStr =
|
58
|
-
const nodeIdStr =
|
56
|
+
const depNodeIdStr = EventSequenceNumber.toString(depNode.seqNum)
|
57
|
+
const nodeIdStr = EventSequenceNumber.toString(node.seqNum)
|
59
58
|
if (dag.edges(depNodeIdStr, nodeIdStr).filter((e) => dag.getEdgeAttributes(e).type === 'facts').length === 0) {
|
60
59
|
dag.addEdge(depNodeIdStr, nodeIdStr, { type: 'facts' })
|
61
60
|
}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import type { EventDef, EventDefFactsSnapshot } from '../../schema/EventDef.js'
|
2
|
-
import * as
|
2
|
+
import * as EventSequenceNumber from '../../schema/EventSequenceNumber.js'
|
3
3
|
import type * as LiveStoreEvent from '../../schema/LiveStoreEvent.js'
|
4
4
|
import {
|
5
5
|
applyFactGroups,
|
@@ -88,13 +88,19 @@ export const rebaseEvents = ({
|
|
88
88
|
initialSnapshot,
|
89
89
|
}),
|
90
90
|
})
|
91
|
-
const headGlobalId = newRemoteEvents.at(-1)!.
|
91
|
+
const headGlobalId = newRemoteEvents.at(-1)!.seqNum.global
|
92
92
|
|
93
93
|
return rebasedLocalEvents.map(
|
94
94
|
(event, index) =>
|
95
95
|
({
|
96
|
-
|
97
|
-
|
96
|
+
seqNum: EventSequenceNumber.make({
|
97
|
+
global: headGlobalId + index + 1,
|
98
|
+
client: EventSequenceNumber.clientDefault,
|
99
|
+
}),
|
100
|
+
parentSeqNum: EventSequenceNumber.make({
|
101
|
+
global: headGlobalId + index,
|
102
|
+
client: EventSequenceNumber.clientDefault,
|
103
|
+
}),
|
98
104
|
name: event.name,
|
99
105
|
args: event.args,
|
100
106
|
clientId,
|
@@ -5,7 +5,7 @@ import { describe, expect, it } from 'vitest'
|
|
5
5
|
import { compactEvents } from '../compact-events.js'
|
6
6
|
import { historyDagFromNodes } from '../history-dag.js'
|
7
7
|
import { customSerializer } from './compact-events.test.js'
|
8
|
-
import { toEventNodes } from './event-fixtures.js'
|
8
|
+
import { printEvent, toEventNodes } from './event-fixtures.js'
|
9
9
|
|
10
10
|
expect.addSnapshotSerializer(customSerializer)
|
11
11
|
|
@@ -15,7 +15,7 @@ const compact = (events: any[]) => {
|
|
15
15
|
|
16
16
|
return Array.from(compacted.dag.nodeEntries())
|
17
17
|
.map((_) => _.attributes)
|
18
|
-
.map(
|
18
|
+
.map(printEvent)
|
19
19
|
.slice(1)
|
20
20
|
}
|
21
21
|
|
@@ -50,8 +50,8 @@ describe('compactEvents calculator', () => {
|
|
50
50
|
|
51
51
|
expect(expected).toMatchInlineSnapshot(`
|
52
52
|
[
|
53
|
-
{
|
54
|
-
{
|
53
|
+
{ seqNum: "e1", parentSeqNum: "e0", name: "add", args: { value: 1 }, clientId: "client-id", sessionId: "session-id", facts: "" }
|
54
|
+
{ seqNum: "e2", parentSeqNum: "e1", name: "add", args: { value: 1 }, clientId: "client-id", sessionId: "session-id", facts: "" }
|
55
55
|
]
|
56
56
|
`)
|
57
57
|
})
|
@@ -64,8 +64,8 @@ describe('compactEvents calculator', () => {
|
|
64
64
|
|
65
65
|
expect(expected).toMatchInlineSnapshot(`
|
66
66
|
[
|
67
|
-
{
|
68
|
-
{
|
67
|
+
{ seqNum: "e1", parentSeqNum: "e0", name: "multiply", args: { value: 2 }, clientId: "client-id", sessionId: "session-id", facts: "?multiplyByZero -multiplyByZero" }
|
68
|
+
{ seqNum: "e2", parentSeqNum: "e1", name: "multiply", args: { value: 2 }, clientId: "client-id", sessionId: "session-id", facts: "?multiplyByZero -multiplyByZero" }
|
69
69
|
]
|
70
70
|
`)
|
71
71
|
})
|
@@ -79,7 +79,7 @@ describe('compactEvents calculator', () => {
|
|
79
79
|
|
80
80
|
expect(expected).toMatchInlineSnapshot(`
|
81
81
|
[
|
82
|
-
{
|
82
|
+
{ seqNum: "e3", parentSeqNum: "e0", name: "multiply", args: { value: 0 }, clientId: "client-id", sessionId: "session-id", facts: "+multiplyByZero" }
|
83
83
|
]
|
84
84
|
`)
|
85
85
|
})
|
@@ -94,8 +94,8 @@ describe('compactEvents calculator', () => {
|
|
94
94
|
|
95
95
|
expect(expected).toMatchInlineSnapshot(`
|
96
96
|
[
|
97
|
-
{
|
98
|
-
{
|
97
|
+
{ seqNum: "e3", parentSeqNum: "e0", name: "multiply", args: { value: 0 }, clientId: "client-id", sessionId: "session-id", facts: "+multiplyByZero" }
|
98
|
+
{ seqNum: "e4", parentSeqNum: "e3", name: "add", args: { value: 1 }, clientId: "client-id", sessionId: "session-id", facts: "" }
|
99
99
|
]
|
100
100
|
`)
|
101
101
|
})
|
@@ -111,10 +111,10 @@ describe('compactEvents calculator', () => {
|
|
111
111
|
|
112
112
|
expect(expected).toMatchInlineSnapshot(`
|
113
113
|
[
|
114
|
-
{
|
115
|
-
{
|
116
|
-
{
|
117
|
-
{
|
114
|
+
{ seqNum: "e1", parentSeqNum: "e0", name: "add", args: { value: 1 }, clientId: "client-id", sessionId: "session-id", facts: "" }
|
115
|
+
{ seqNum: "e3", parentSeqNum: "e1", name: "multiply", args: { value: 0 }, clientId: "client-id", sessionId: "session-id", facts: "+multiplyByZero" }
|
116
|
+
{ seqNum: "e4", parentSeqNum: "e3", name: "multiply", args: { value: 2 }, clientId: "client-id", sessionId: "session-id", facts: "?multiplyByZero +multiplyByZero -multiplyByZero" }
|
117
|
+
{ seqNum: "e5", parentSeqNum: "e4", name: "add", args: { value: 1 }, clientId: "client-id", sessionId: "session-id", facts: "" }
|
118
118
|
]
|
119
119
|
`)
|
120
120
|
})
|
@@ -5,7 +5,7 @@ import { compactEvents } from '../compact-events.js'
|
|
5
5
|
import { historyDagFromNodes } from '../history-dag.js'
|
6
6
|
import type { HistoryDagNode } from '../history-dag-common.js'
|
7
7
|
import { EMPTY_FACT_VALUE } from '../history-dag-common.js'
|
8
|
-
import { events as eventDefs, toEventNodes } from './event-fixtures.js'
|
8
|
+
import { events as eventDefs, printEvent, toEventNodes } from './event-fixtures.js'
|
9
9
|
|
10
10
|
const customStringify = (value: any): string => {
|
11
11
|
if (value === null) {
|
@@ -37,7 +37,7 @@ const customStringify = (value: any): string => {
|
|
37
37
|
const valStr =
|
38
38
|
key === 'facts'
|
39
39
|
? `"${factsToString(val)}"`
|
40
|
-
: (key === 'id' || key === '
|
40
|
+
: (key === 'id' || key === 'parentSeqNum') && Object.keys(val).length === 2 && val.client === 0
|
41
41
|
? val.global
|
42
42
|
: customStringify(val)
|
43
43
|
|
@@ -76,7 +76,7 @@ const compact = (events: any[]) => {
|
|
76
76
|
|
77
77
|
return Array.from(compacted.dag.nodeEntries())
|
78
78
|
.map((_) => _.attributes)
|
79
|
-
.map(
|
79
|
+
.map(printEvent)
|
80
80
|
.slice(1)
|
81
81
|
}
|
82
82
|
|
@@ -90,8 +90,8 @@ describe('compactEvents todo app', () => {
|
|
90
90
|
|
91
91
|
expect(expected).toMatchInlineSnapshot(`
|
92
92
|
[
|
93
|
-
{
|
94
|
-
{
|
93
|
+
{ seqNum: "e1", parentSeqNum: "e0", name: "createTodo", args: { id: "A", text: "buy milk" }, clientId: "client-id", sessionId: "session-id", facts: "+todo-exists-A +todo-is-writeable-A=true +todo-completed-A=false" }
|
94
|
+
{ seqNum: "e3", parentSeqNum: "e1", name: "todoCompleted", args: { id: "A" }, clientId: "client-id", sessionId: "session-id", facts: "↖todo-exists-A ↖todo-is-writeable-A=true +todo-completed-A=true" }
|
95
95
|
]
|
96
96
|
`)
|
97
97
|
})
|
@@ -106,10 +106,10 @@ describe('compactEvents todo app', () => {
|
|
106
106
|
|
107
107
|
expect(expected).toMatchInlineSnapshot(`
|
108
108
|
[
|
109
|
-
{
|
110
|
-
{
|
111
|
-
{
|
112
|
-
{
|
109
|
+
{ seqNum: "e1", parentSeqNum: "e0", name: "createTodo", args: { id: "A", text: "buy milk" }, clientId: "client-id", sessionId: "session-id", facts: "+todo-exists-A +todo-is-writeable-A=true +todo-completed-A=false" }
|
110
|
+
{ seqNum: "e2", parentSeqNum: "e1", name: "toggleTodo", args: { id: "A" }, clientId: "client-id", sessionId: "session-id", facts: "↖todo-exists-A ↖todo-is-writeable-A=true ?todo-completed-A +todo-completed-A=true" }
|
111
|
+
{ seqNum: "e3", parentSeqNum: "e2", name: "toggleTodo", args: { id: "A" }, clientId: "client-id", sessionId: "session-id", facts: "↖todo-exists-A ↖todo-is-writeable-A=true ?todo-completed-A +todo-completed-A=false" }
|
112
|
+
{ seqNum: "e4", parentSeqNum: "e3", name: "toggleTodo", args: { id: "A" }, clientId: "client-id", sessionId: "session-id", facts: "↖todo-exists-A ↖todo-is-writeable-A=true ?todo-completed-A +todo-completed-A=true" }
|
113
113
|
]
|
114
114
|
`)
|
115
115
|
})
|
@@ -126,9 +126,9 @@ describe('compactEvents todo app', () => {
|
|
126
126
|
|
127
127
|
expect(expected).toMatchInlineSnapshot(`
|
128
128
|
[
|
129
|
-
{
|
130
|
-
{
|
131
|
-
{
|
129
|
+
{ seqNum: "e1", parentSeqNum: "e0", name: "createTodo", args: { id: "A", text: "buy milk" }, clientId: "client-id", sessionId: "session-id", facts: "+todo-exists-A +todo-is-writeable-A=true +todo-completed-A=false" }
|
130
|
+
{ seqNum: "e5", parentSeqNum: "e1", name: "todoCompleted", args: { id: "A" }, clientId: "client-id", sessionId: "session-id", facts: "↖todo-exists-A ↖todo-is-writeable-A=true +todo-completed-A=true" }
|
131
|
+
{ seqNum: "e6", parentSeqNum: "e5", name: "toggleTodo", args: { id: "A" }, clientId: "client-id", sessionId: "session-id", facts: "↖todo-exists-A ↖todo-is-writeable-A=true ?todo-completed-A +todo-completed-A=false" }
|
132
132
|
]
|
133
133
|
`)
|
134
134
|
})
|
@@ -143,10 +143,10 @@ describe('compactEvents todo app', () => {
|
|
143
143
|
|
144
144
|
expect(expected).toMatchInlineSnapshot(`
|
145
145
|
[
|
146
|
-
{
|
147
|
-
{
|
148
|
-
{
|
149
|
-
{
|
146
|
+
{ seqNum: "e1", parentSeqNum: "e0", name: "createTodo", args: { id: "A", text: "buy milk" }, clientId: "client-id", sessionId: "session-id", facts: "+todo-exists-A +todo-is-writeable-A=true +todo-completed-A=false" }
|
147
|
+
{ seqNum: "e2", parentSeqNum: "e1", name: "setReadonlyTodo", args: { id: "A", readonly: false }, clientId: "client-id", sessionId: "session-id", facts: "↖todo-exists-A +todo-is-writeable-A=true" }
|
148
|
+
{ seqNum: "e3", parentSeqNum: "e2", name: "setTextTodo", args: { id: "A", text: "buy soy milk" }, clientId: "client-id", sessionId: "session-id", facts: "↖todo-exists-A ↖todo-is-writeable-A=true +todo-text-updated-A" }
|
149
|
+
{ seqNum: "e4", parentSeqNum: "e3", name: "setReadonlyTodo", args: { id: "A", readonly: true }, clientId: "client-id", sessionId: "session-id", facts: "↖todo-exists-A +todo-is-writeable-A=false" }
|
150
150
|
]
|
151
151
|
`)
|
152
152
|
})
|
@@ -162,11 +162,11 @@ describe('compactEvents todo app', () => {
|
|
162
162
|
|
163
163
|
expect(expected).toMatchInlineSnapshot(`
|
164
164
|
[
|
165
|
-
{
|
166
|
-
{
|
167
|
-
{
|
168
|
-
{
|
169
|
-
{
|
165
|
+
{ seqNum: "e1", parentSeqNum: "e0", name: "createTodo", args: { id: "A", text: "buy milk" }, clientId: "client-id", sessionId: "session-id", facts: "+todo-exists-A +todo-is-writeable-A=true +todo-completed-A=false" }
|
166
|
+
{ seqNum: "e2", parentSeqNum: "e1", name: "setReadonlyTodo", args: { id: "A", readonly: false }, clientId: "client-id", sessionId: "session-id", facts: "↖todo-exists-A +todo-is-writeable-A=true" }
|
167
|
+
{ seqNum: "e3", parentSeqNum: "e2", name: "todoCompleted", args: { id: "A" }, clientId: "client-id", sessionId: "session-id", facts: "↖todo-exists-A ↖todo-is-writeable-A=true +todo-completed-A=true" }
|
168
|
+
{ seqNum: "e4", parentSeqNum: "e3", name: "setTextTodo", args: { id: "A", text: "buy soy milk" }, clientId: "client-id", sessionId: "session-id", facts: "↖todo-exists-A ↖todo-is-writeable-A=true +todo-text-updated-A" }
|
169
|
+
{ seqNum: "e5", parentSeqNum: "e4", name: "setReadonlyTodo", args: { id: "A", readonly: true }, clientId: "client-id", sessionId: "session-id", facts: "↖todo-exists-A +todo-is-writeable-A=false" }
|
170
170
|
]
|
171
171
|
`)
|
172
172
|
})
|
@@ -200,11 +200,11 @@ describe('compactEvents todo app', () => {
|
|
200
200
|
|
201
201
|
expect(expected).toMatchInlineSnapshot(`
|
202
202
|
[
|
203
|
-
{
|
204
|
-
{
|
205
|
-
{
|
206
|
-
{
|
207
|
-
{
|
203
|
+
{ seqNum: "e1", parentSeqNum: "e0", name: "createTodo", args: { id: "A", text: "buy milk" }, clientId: "client-id", sessionId: "session-id", facts: "+todo-exists-A +todo-is-writeable-A=true +todo-completed-A=false" }
|
204
|
+
{ seqNum: "e2", parentSeqNum: "e1", name: "createTodo", args: { id: "B", text: "buy bread" }, clientId: "client-id", sessionId: "session-id", facts: "+todo-exists-B +todo-is-writeable-B=true +todo-completed-B=false" }
|
205
|
+
{ seqNum: "e3", parentSeqNum: "e2", name: "createTodo", args: { id: "C", text: "buy cheese" }, clientId: "client-id", sessionId: "session-id", facts: "+todo-exists-C +todo-is-writeable-C=true +todo-completed-C=false" }
|
206
|
+
{ seqNum: "e4", parentSeqNum: "e3", name: "todoCompleteds", args: { ids: ["A", "B", "C"] }, clientId: "client-id", sessionId: "session-id", facts: "↖todo-exists-A ↖todo-is-writeable-A=true ↖todo-exists-B ↖todo-is-writeable-B=true ↖todo-exists-C ↖todo-is-writeable-C=true +todo-completed-A=true +todo-completed-B=true +todo-completed-C=true" }
|
207
|
+
{ seqNum: "e6", parentSeqNum: "e4", name: "todoCompleted", args: { id: "A" }, clientId: "client-id", sessionId: "session-id", facts: "↖todo-exists-A ↖todo-is-writeable-A=true +todo-completed-A=true" }
|
208
208
|
]
|
209
209
|
`)
|
210
210
|
})
|
@@ -222,11 +222,11 @@ describe('compactEvents todo app', () => {
|
|
222
222
|
|
223
223
|
expect(expected).toMatchInlineSnapshot(`
|
224
224
|
[
|
225
|
-
{
|
226
|
-
{
|
227
|
-
{
|
228
|
-
{
|
229
|
-
{
|
225
|
+
{ seqNum: "e1", parentSeqNum: "e0", name: "createTodo", args: { id: "A", text: "buy milk" }, clientId: "client-id", sessionId: "session-id", facts: "+todo-exists-A +todo-is-writeable-A=true +todo-completed-A=false" }
|
226
|
+
{ seqNum: "e2", parentSeqNum: "e1", name: "createTodo", args: { id: "B", text: "buy bread" }, clientId: "client-id", sessionId: "session-id", facts: "+todo-exists-B +todo-is-writeable-B=true +todo-completed-B=false" }
|
227
|
+
{ seqNum: "e3", parentSeqNum: "e2", name: "createTodo", args: { id: "C", text: "buy cheese" }, clientId: "client-id", sessionId: "session-id", facts: "+todo-exists-C +todo-is-writeable-C=true +todo-completed-C=false" }
|
228
|
+
{ seqNum: "e5", parentSeqNum: "e3", name: "todoCompleteds", args: { ids: ["A", "B", "C"] }, clientId: "client-id", sessionId: "session-id", facts: "↖todo-exists-A ↖todo-is-writeable-A=true ↖todo-exists-B ↖todo-is-writeable-B=true ↖todo-exists-C ↖todo-is-writeable-C=true +todo-completed-A=true +todo-completed-B=true +todo-completed-C=true" }
|
229
|
+
{ seqNum: "e7", parentSeqNum: "e5", name: "todoCompleted", args: { id: "A" }, clientId: "client-id", sessionId: "session-id", facts: "↖todo-exists-A ↖todo-is-writeable-A=true +todo-completed-A=true" }
|
230
230
|
]
|
231
231
|
`)
|
232
232
|
})
|