@livestore/common 0.3.1 → 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 -161
- 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-common.d.ts.map +1 -1
- 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/leader-thread/LeaderSyncProcessor.d.ts +6 -7
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +112 -122
- 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 +32 -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.map +1 -1
- package/dist/leader-thread/materialize-event.js +7 -1
- package/dist/leader-thread/materialize-event.js.map +1 -1
- package/dist/leader-thread/mod.d.ts +1 -0
- package/dist/leader-thread/mod.d.ts.map +1 -1
- package/dist/leader-thread/mod.js +1 -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 +5 -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/rematerialize-from-eventlog.d.ts +1 -1
- package/dist/rematerialize-from-eventlog.d.ts.map +1 -1
- package/dist/rematerialize-from-eventlog.js +10 -2
- package/dist/rematerialize-from-eventlog.js.map +1 -1
- package/dist/schema/EventDef.d.ts +2 -2
- package/dist/schema/EventDef.d.ts.map +1 -1
- package/dist/schema/EventDef.js +2 -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 +25 -11
- package/dist/schema/LiveStoreEvent.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent.js +12 -4
- 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 +35 -8
- 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 -81
- 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 +67 -62
- 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 +1 -1
- 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-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 +6 -2
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +16 -13
- 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.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +7 -6
- package/src/ClientSessionLeaderThreadProxy.ts +40 -0
- package/src/adapter-types.ts +19 -166
- package/src/defs.ts +17 -0
- package/src/errors.ts +49 -0
- package/src/leader-thread/LeaderSyncProcessor.ts +141 -180
- package/src/leader-thread/eventlog.ts +78 -56
- 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 +8 -1
- package/src/leader-thread/mod.ts +1 -0
- package/src/leader-thread/recreate-db.ts +99 -91
- package/src/leader-thread/types.ts +6 -11
- package/src/make-client-session.ts +2 -2
- package/src/rematerialize-from-eventlog.ts +10 -2
- package/src/schema/EventDef.ts +5 -3
- package/src/schema/EventSequenceNumber.test.ts +120 -3
- package/src/schema/EventSequenceNumber.ts +95 -23
- package/src/schema/LiveStoreEvent.ts +20 -4
- 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 +38 -8
- 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 +1 -1
- package/src/schema-management/migrations.ts +3 -1
- package/src/sql-queries/sql-queries.ts +2 -0
- package/src/sqlite-types.ts +76 -0
- package/src/sync/ClientSessionSyncProcessor.ts +17 -20
- 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,3 +1,4 @@
|
|
1
|
+
/** biome-ignore-all lint/complexity/noArguments: using arguments is fine here */
|
1
2
|
import { casesHandled, shouldNeverHappen } from '@livestore/utils'
|
2
3
|
import { Match, Option, Predicate, Schema } from '@livestore/utils/effect'
|
3
4
|
|
@@ -36,7 +37,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
36
37
|
select: { columns },
|
37
38
|
}) as any
|
38
39
|
},
|
39
|
-
//
|
40
|
+
// biome-ignore lint/complexity/useArrowFunction: prefer function over arrow function for this case
|
40
41
|
where: function () {
|
41
42
|
if (ast._tag === 'InsertQuery') return invalidQueryBuilder('Cannot use where with insert')
|
42
43
|
if (ast._tag === 'RowQuery') return invalidQueryBuilder('Cannot use where with row')
|
@@ -136,7 +137,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
136
137
|
),
|
137
138
|
})
|
138
139
|
},
|
139
|
-
first: (
|
140
|
+
first: (behaviour) => {
|
140
141
|
assertSelectQueryBuilderAst(ast)
|
141
142
|
|
142
143
|
if (ast.limit._tag === 'Some') return invalidQueryBuilder(`.first() can't be called after .limit()`)
|
@@ -144,8 +145,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
144
145
|
return makeQueryBuilder(tableDef, {
|
145
146
|
...ast,
|
146
147
|
limit: Option.some(1),
|
147
|
-
pickFirst:
|
148
|
-
options?.fallback !== undefined && options.fallback !== 'throws' ? { fallback: options.fallback } : 'throws',
|
148
|
+
pickFirst: { _tag: 'enabled', ...(behaviour ?? { behaviour: 'undefined' }) },
|
149
149
|
})
|
150
150
|
},
|
151
151
|
// // eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
@@ -249,7 +249,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
249
249
|
return {
|
250
250
|
[QueryBuilderTypeId]: QueryBuilderTypeId,
|
251
251
|
[QueryBuilderAstSymbol]: ast,
|
252
|
-
|
252
|
+
ResultType: 'only-for-type-inference' as TResult,
|
253
253
|
asSql: () => astToSql(ast),
|
254
254
|
toString: () => {
|
255
255
|
try {
|
@@ -266,7 +266,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
|
|
266
266
|
const emptyAst = (tableDef: TableDefBase): QueryBuilderAst.SelectQuery => ({
|
267
267
|
_tag: 'SelectQuery',
|
268
268
|
columns: [],
|
269
|
-
pickFirst:
|
269
|
+
pickFirst: { _tag: 'disabled' },
|
270
270
|
select: { columns: [] },
|
271
271
|
orderBy: [],
|
272
272
|
offset: Option.none(),
|
@@ -280,28 +280,28 @@ const emptyAst = (tableDef: TableDefBase): QueryBuilderAst.SelectQuery => ({
|
|
280
280
|
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
281
281
|
function assertSelectQueryBuilderAst(ast: QueryBuilderAst): asserts ast is QueryBuilderAst.SelectQuery {
|
282
282
|
if (ast._tag !== 'SelectQuery') {
|
283
|
-
return shouldNeverHappen(
|
283
|
+
return shouldNeverHappen(`Expected SelectQuery but got ${ast._tag}`)
|
284
284
|
}
|
285
285
|
}
|
286
286
|
|
287
287
|
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
288
288
|
function assertInsertQueryBuilderAst(ast: QueryBuilderAst): asserts ast is QueryBuilderAst.InsertQuery {
|
289
289
|
if (ast._tag !== 'InsertQuery') {
|
290
|
-
return shouldNeverHappen(
|
290
|
+
return shouldNeverHappen(`Expected InsertQuery but got ${ast._tag}`)
|
291
291
|
}
|
292
292
|
}
|
293
293
|
|
294
294
|
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
295
295
|
function assertWriteQueryBuilderAst(ast: QueryBuilderAst): asserts ast is QueryBuilderAst.WriteQuery {
|
296
296
|
if (ast._tag !== 'InsertQuery' && ast._tag !== 'UpdateQuery' && ast._tag !== 'DeleteQuery') {
|
297
|
-
return shouldNeverHappen(
|
297
|
+
return shouldNeverHappen(`Expected WriteQuery but got ${ast._tag}`)
|
298
298
|
}
|
299
299
|
}
|
300
300
|
|
301
301
|
const isRowQuery = (ast: QueryBuilderAst): ast is QueryBuilderAst.RowQuery => ast._tag === 'RowQuery'
|
302
302
|
|
303
303
|
export const invalidQueryBuilder = (msg?: string) => {
|
304
|
-
return shouldNeverHappen(
|
304
|
+
return shouldNeverHappen(`Invalid query builder${msg ? `: ${msg}` : ''}`)
|
305
305
|
}
|
306
306
|
|
307
307
|
export const getResultSchema = (qb: QueryBuilder<any, any, any>): Schema.Schema<any> => {
|
@@ -309,9 +309,12 @@ export const getResultSchema = (qb: QueryBuilder<any, any, any>): Schema.Schema<
|
|
309
309
|
switch (queryAst._tag) {
|
310
310
|
case 'SelectQuery': {
|
311
311
|
const arraySchema = Schema.Array(queryAst.resultSchemaSingle)
|
312
|
-
if (queryAst.pickFirst ===
|
312
|
+
if (queryAst.pickFirst._tag === 'disabled') {
|
313
313
|
return arraySchema
|
314
|
-
} else if (queryAst.pickFirst === '
|
314
|
+
} else if (queryAst.pickFirst.behaviour === 'undefined') {
|
315
|
+
const arraySchema = Schema.Array(Schema.UndefinedOr(queryAst.resultSchemaSingle))
|
316
|
+
return arraySchema.pipe(Schema.headOrElse(() => undefined))
|
317
|
+
} else if (queryAst.pickFirst.behaviour === 'error') {
|
315
318
|
// Will throw if the array is empty
|
316
319
|
return arraySchema.pipe(Schema.headOrElse())
|
317
320
|
} else {
|
@@ -4,7 +4,7 @@ import * as EventSequenceNumber from '../../EventSequenceNumber.js'
|
|
4
4
|
import { SqliteDsl } from './db-schema/mod.js'
|
5
5
|
import { table } from './table-def.js'
|
6
6
|
|
7
|
-
///
|
7
|
+
/// State DB
|
8
8
|
|
9
9
|
export const SCHEMA_META_TABLE = '__livestore_schema'
|
10
10
|
|
@@ -46,6 +46,7 @@ export const sessionChangesetMetaTable = table({
|
|
46
46
|
// TODO bring back primary key
|
47
47
|
seqNumGlobal: SqliteDsl.integer({ schema: EventSequenceNumber.GlobalEventSequenceNumber }),
|
48
48
|
seqNumClient: SqliteDsl.integer({ schema: EventSequenceNumber.ClientEventSequenceNumber }),
|
49
|
+
seqNumRebaseGeneration: SqliteDsl.integer({}),
|
49
50
|
changeset: SqliteDsl.blob({ nullable: true }),
|
50
51
|
debug: SqliteDsl.json({ nullable: true }),
|
51
52
|
},
|
@@ -54,25 +55,7 @@ export const sessionChangesetMetaTable = table({
|
|
54
55
|
|
55
56
|
export type SessionChangesetMetaRow = typeof sessionChangesetMetaTable.Type
|
56
57
|
|
57
|
-
export const
|
58
|
-
|
59
|
-
// TODO get rid of this table in favour of client-only merge generation
|
60
|
-
export const leaderMergeCounterTable = table({
|
61
|
-
name: LEADER_MERGE_COUNTER_TABLE,
|
62
|
-
columns: {
|
63
|
-
id: SqliteDsl.integer({ primaryKey: true, schema: Schema.Literal(0) }),
|
64
|
-
mergeCounter: SqliteDsl.integer({ primaryKey: true }),
|
65
|
-
},
|
66
|
-
})
|
67
|
-
|
68
|
-
export type LeaderMergeCounterRow = typeof leaderMergeCounterTable.Type
|
69
|
-
|
70
|
-
export const stateSystemTables = [
|
71
|
-
schemaMetaTable,
|
72
|
-
schemaEventDefsMetaTable,
|
73
|
-
sessionChangesetMetaTable,
|
74
|
-
leaderMergeCounterTable,
|
75
|
-
]
|
58
|
+
export const stateSystemTables = [schemaMetaTable, schemaEventDefsMetaTable, sessionChangesetMetaTable] as const
|
76
59
|
|
77
60
|
export const isStateSystemTable = (tableName: string) => stateSystemTables.some((_) => _.sqliteDef.name === tableName)
|
78
61
|
|
@@ -86,8 +69,11 @@ export const eventlogMetaTable = table({
|
|
86
69
|
// TODO Adjust modeling so a global event never needs a client id component
|
87
70
|
seqNumGlobal: SqliteDsl.integer({ primaryKey: true, schema: EventSequenceNumber.GlobalEventSequenceNumber }),
|
88
71
|
seqNumClient: SqliteDsl.integer({ primaryKey: true, schema: EventSequenceNumber.ClientEventSequenceNumber }),
|
72
|
+
seqNumRebaseGeneration: SqliteDsl.integer({ primaryKey: true }),
|
89
73
|
parentSeqNumGlobal: SqliteDsl.integer({ schema: EventSequenceNumber.GlobalEventSequenceNumber }),
|
90
74
|
parentSeqNumClient: SqliteDsl.integer({ schema: EventSequenceNumber.ClientEventSequenceNumber }),
|
75
|
+
parentSeqNumRebaseGeneration: SqliteDsl.integer({}),
|
76
|
+
/** Event definition name */
|
91
77
|
name: SqliteDsl.text({}),
|
92
78
|
argsJson: SqliteDsl.text({ schema: Schema.parseJson(Schema.Any) }),
|
93
79
|
clientId: SqliteDsl.text({}),
|
@@ -97,7 +83,7 @@ export const eventlogMetaTable = table({
|
|
97
83
|
},
|
98
84
|
indexes: [
|
99
85
|
{ columns: ['seqNumGlobal'], name: 'idx_eventlog_seqNumGlobal' },
|
100
|
-
{ columns: ['seqNumGlobal', 'seqNumClient'], name: 'idx_eventlog_seqNum' },
|
86
|
+
{ columns: ['seqNumGlobal', 'seqNumClient', 'seqNumRebaseGeneration'], name: 'idx_eventlog_seqNum' },
|
101
87
|
],
|
102
88
|
})
|
103
89
|
|
@@ -105,6 +91,7 @@ export type EventlogMetaRow = typeof eventlogMetaTable.Type
|
|
105
91
|
|
106
92
|
export const SYNC_STATUS_TABLE = '__livestore_sync_status'
|
107
93
|
|
94
|
+
// TODO support sync backend identity (to detect if sync backend changes)
|
108
95
|
export const syncStatusTable = table({
|
109
96
|
name: SYNC_STATUS_TABLE,
|
110
97
|
columns: {
|
@@ -114,4 +101,4 @@ export const syncStatusTable = table({
|
|
114
101
|
|
115
102
|
export type SyncStatusRow = typeof syncStatusTable.Type
|
116
103
|
|
117
|
-
export const eventlogSystemTables = [eventlogMetaTable, syncStatusTable]
|
104
|
+
export const eventlogSystemTables = [eventlogMetaTable, syncStatusTable] as const
|
@@ -1,7 +1,9 @@
|
|
1
1
|
import { memoizeByStringifyArgs } from '@livestore/utils'
|
2
2
|
import { Effect, Schema as EffectSchema } from '@livestore/utils/effect'
|
3
3
|
|
4
|
-
import type {
|
4
|
+
import type { SqliteDb } from '../adapter-types.js'
|
5
|
+
import type { MigrationsReport, MigrationsReportEntry } from '../defs.js'
|
6
|
+
import type { UnexpectedError } from '../errors.js'
|
5
7
|
import type { LiveStoreSchema } from '../schema/mod.js'
|
6
8
|
import { SqliteAst, SqliteDsl } from '../schema/state/sqlite/db-schema/mod.js'
|
7
9
|
import type { SchemaEventDefsMetaRow, SchemaMetaRow } from '../schema/state/sqlite/system-tables.js'
|
@@ -106,6 +106,7 @@ export const insertRows = <TColumns extends SqliteDsl.Columns>({
|
|
106
106
|
|
107
107
|
const bindValues = valuesArray.reduce(
|
108
108
|
(acc, values, itemIndex) => ({
|
109
|
+
// biome-ignore lint/performance/noAccumulatingSpread: TODO improve this some day
|
109
110
|
...acc,
|
110
111
|
...makeBindValues({ columns, values, variablePrefix: `item_${itemIndex}_` }),
|
111
112
|
}),
|
@@ -292,6 +293,7 @@ Error: ${parseErrorStr}
|
|
292
293
|
Value:`,
|
293
294
|
value,
|
294
295
|
)
|
296
|
+
// biome-ignore lint/suspicious/noDebugger: debug
|
295
297
|
debugger
|
296
298
|
throw res.left
|
297
299
|
} else {
|
@@ -0,0 +1,76 @@
|
|
1
|
+
import { type Effect, Schema } from '@livestore/utils/effect'
|
2
|
+
import type { SqliteError, UnexpectedError } from './errors.js'
|
3
|
+
import type { EventSequenceNumber } from './schema/mod.js'
|
4
|
+
import type { QueryBuilder } from './schema/state/sqlite/query-builder/api.js'
|
5
|
+
import type { PreparedBindValues } from './util.js'
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Common interface for SQLite databases used by LiveStore to facilitate a consistent API across different platforms.
|
9
|
+
* Always assumes a synchronous SQLite build with the `bytecode` and `session` extensions enabled.
|
10
|
+
* Can be either in-memory or persisted to disk.
|
11
|
+
*/
|
12
|
+
export interface SqliteDb<TReq = any, TMetadata extends TReq = TReq> {
|
13
|
+
_tag: 'SqliteDb'
|
14
|
+
metadata: TMetadata
|
15
|
+
/** Debug information (currently not persisted and only available at runtime) */
|
16
|
+
debug: SqliteDebugInfo
|
17
|
+
prepare(queryStr: string): PreparedStatement
|
18
|
+
execute(
|
19
|
+
queryStr: string,
|
20
|
+
bindValues?: PreparedBindValues | undefined,
|
21
|
+
options?: { onRowsChanged?: (rowsChanged: number) => void },
|
22
|
+
): void
|
23
|
+
execute(queryBuilder: QueryBuilder.Any, options?: { onRowsChanged?: (rowsChanged: number) => void }): void
|
24
|
+
|
25
|
+
select<T>(queryStr: string, bindValues?: PreparedBindValues | undefined): ReadonlyArray<T>
|
26
|
+
select<T>(queryBuilder: QueryBuilder<T, any, any>): T
|
27
|
+
|
28
|
+
export(): Uint8Array
|
29
|
+
import: (data: Uint8Array | SqliteDb<TReq>) => void
|
30
|
+
close(): void
|
31
|
+
destroy(): void
|
32
|
+
session(): SqliteDbSession
|
33
|
+
makeChangeset: (data: Uint8Array) => SqliteDbChangeset
|
34
|
+
}
|
35
|
+
|
36
|
+
export type SqliteDebugInfo = { head: EventSequenceNumber.EventSequenceNumber }
|
37
|
+
|
38
|
+
// TODO refactor this helper type. It's quite cumbersome to use and should be revisited.
|
39
|
+
export type MakeSqliteDb<
|
40
|
+
TReq = { dbPointer: number; persistenceInfo: PersistenceInfo },
|
41
|
+
TInput_ extends { _tag: string } = { _tag: string },
|
42
|
+
TMetadata_ extends TReq = TReq,
|
43
|
+
R = never,
|
44
|
+
> = <
|
45
|
+
TInput extends TInput_,
|
46
|
+
TMetadata extends TMetadata_ & { _tag: TInput['_tag'] } = TMetadata_ & { _tag: TInput['_tag'] },
|
47
|
+
>(
|
48
|
+
input: TInput,
|
49
|
+
) => Effect.Effect<SqliteDb<TReq, Extract<TMetadata, { _tag: TInput['_tag'] }>>, SqliteError | UnexpectedError, R>
|
50
|
+
|
51
|
+
export interface PreparedStatement {
|
52
|
+
execute(bindValues: PreparedBindValues | undefined, options?: { onRowsChanged?: (rowsChanged: number) => void }): void
|
53
|
+
select<T>(bindValues: PreparedBindValues | undefined): ReadonlyArray<T>
|
54
|
+
finalize(): void
|
55
|
+
sql: string
|
56
|
+
}
|
57
|
+
|
58
|
+
export type SqliteDbSession = {
|
59
|
+
changeset: () => Uint8Array | undefined
|
60
|
+
finish: () => void
|
61
|
+
}
|
62
|
+
|
63
|
+
export type SqliteDbChangeset = {
|
64
|
+
// TODO combining changesets (requires changes in the SQLite WASM binding)
|
65
|
+
invert: () => SqliteDbChangeset
|
66
|
+
apply: () => void
|
67
|
+
}
|
68
|
+
|
69
|
+
export const PersistenceInfo = Schema.Struct(
|
70
|
+
{
|
71
|
+
fileName: Schema.String,
|
72
|
+
},
|
73
|
+
{ key: Schema.String, value: Schema.Any },
|
74
|
+
).annotations({ title: 'LiveStore.PersistenceInfo' })
|
75
|
+
|
76
|
+
export type PersistenceInfo<With extends {} = {}> = typeof PersistenceInfo.Type & With
|
@@ -4,11 +4,10 @@ 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,7 +40,7 @@ export const makeClientSessionSyncProcessor = ({
|
|
37
40
|
clientSession: ClientSession
|
38
41
|
runtime: Runtime.Runtime<Scope.Scope>
|
39
42
|
materializeEvent: (
|
40
|
-
eventDecoded: LiveStoreEvent.
|
43
|
+
eventDecoded: LiveStoreEvent.AnyDecoded,
|
41
44
|
options: { otelContext: otel.Context; withChangeset: boolean; materializerHashLeader: Option.Option<number> },
|
42
45
|
) => {
|
43
46
|
writeTables: Set<string>
|
@@ -82,7 +85,10 @@ export const makeClientSessionSyncProcessor = ({
|
|
82
85
|
let baseEventSequenceNumber = syncStateRef.current.localHead
|
83
86
|
const encodedEventDefs = batch.map(({ name, args }) => {
|
84
87
|
const eventDef = getEventDef(schema, name)
|
85
|
-
const nextNumPair = EventSequenceNumber.nextPair(
|
88
|
+
const nextNumPair = EventSequenceNumber.nextPair({
|
89
|
+
seqNum: baseEventSequenceNumber,
|
90
|
+
isClient: eventDef.eventDef.options.clientOnly,
|
91
|
+
})
|
86
92
|
baseEventSequenceNumber = nextNumPair.seqNum
|
87
93
|
return new LiveStoreEvent.EncodedWithMeta(
|
88
94
|
Schema.encodeUnknownSync(eventSchema)({
|
@@ -103,7 +109,7 @@ export const makeClientSessionSyncProcessor = ({
|
|
103
109
|
})
|
104
110
|
|
105
111
|
if (mergeResult._tag === 'unexpected-error') {
|
106
|
-
return shouldNeverHappen('Unexpected error in client-session-sync-processor', mergeResult.
|
112
|
+
return shouldNeverHappen('Unexpected error in client-session-sync-processor', mergeResult.message)
|
107
113
|
}
|
108
114
|
|
109
115
|
span.addEvent('local-push', {
|
@@ -184,18 +190,11 @@ export const makeClientSessionSyncProcessor = ({
|
|
184
190
|
|
185
191
|
yield* FiberHandle.run(leaderPushingFiberHandle, backgroundLeaderPushing)
|
186
192
|
|
187
|
-
const getMergeCounter = () =>
|
188
|
-
clientSession.sqliteDb.select<{ mergeCounter: number }>(
|
189
|
-
sql`SELECT mergeCounter FROM ${SystemTables.LEADER_MERGE_COUNTER_TABLE} WHERE id = 0`,
|
190
|
-
)[0]?.mergeCounter ?? 0
|
191
|
-
|
192
193
|
// NOTE We need to lazily call `.pull` as we want the cursor to be updated
|
193
194
|
yield* Stream.suspend(() =>
|
194
|
-
clientSession.leaderThread.events.pull({
|
195
|
-
cursor: { mergeCounter: getMergeCounter(), eventNum: syncStateRef.current.localHead },
|
196
|
-
}),
|
195
|
+
clientSession.leaderThread.events.pull({ cursor: syncStateRef.current.upstreamHead }),
|
197
196
|
).pipe(
|
198
|
-
Stream.tap(({ payload
|
197
|
+
Stream.tap(({ payload }) =>
|
199
198
|
Effect.gen(function* () {
|
200
199
|
// yield* Effect.logDebug('ClientSessionSyncProcessor:pull', payload)
|
201
200
|
|
@@ -211,13 +210,13 @@ export const makeClientSessionSyncProcessor = ({
|
|
211
210
|
})
|
212
211
|
|
213
212
|
if (mergeResult._tag === 'unexpected-error') {
|
214
|
-
return yield*
|
213
|
+
return yield* new SyncError({ cause: mergeResult.message })
|
215
214
|
} else if (mergeResult._tag === 'reject') {
|
216
215
|
return shouldNeverHappen('Unexpected reject in client-session-sync-processor', mergeResult)
|
217
216
|
}
|
218
217
|
|
219
218
|
syncStateRef.current = mergeResult.newSyncState
|
220
|
-
syncStateUpdateQueue.offer(mergeResult.newSyncState)
|
219
|
+
yield* syncStateUpdateQueue.offer(mergeResult.newSyncState)
|
221
220
|
|
222
221
|
if (mergeResult._tag === 'rebase') {
|
223
222
|
span.addEvent('merge:pull:rebase', {
|
@@ -226,7 +225,7 @@ export const makeClientSessionSyncProcessor = ({
|
|
226
225
|
newEventsCount: mergeResult.newEvents.length,
|
227
226
|
rollbackCount: mergeResult.rollbackEvents.length,
|
228
227
|
res: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
229
|
-
|
228
|
+
rebaseGeneration: mergeResult.newSyncState.localHead.rebaseGeneration,
|
230
229
|
})
|
231
230
|
|
232
231
|
debugInfo.rebaseCount++
|
@@ -243,7 +242,6 @@ export const makeClientSessionSyncProcessor = ({
|
|
243
242
|
'merge:pull:rebase: rollback',
|
244
243
|
mergeResult.rollbackEvents.length,
|
245
244
|
...mergeResult.rollbackEvents.slice(0, 10).map((_) => _.toJSON()),
|
246
|
-
{ leaderMergeCounter },
|
247
245
|
).pipe(Effect.provide(runtime), Effect.runSync)
|
248
246
|
}
|
249
247
|
|
@@ -263,7 +261,6 @@ export const makeClientSessionSyncProcessor = ({
|
|
263
261
|
payload: TRACE_VERBOSE ? JSON.stringify(payload) : undefined,
|
264
262
|
newEventsCount: mergeResult.newEvents.length,
|
265
263
|
res: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
|
266
|
-
leaderMergeCounter,
|
267
264
|
})
|
268
265
|
|
269
266
|
debugInfo.advanceCount++
|
@@ -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])
|