@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.
Files changed (172) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/ClientSessionLeaderThreadProxy.d.ts +35 -0
  3. package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -0
  4. package/dist/ClientSessionLeaderThreadProxy.js +6 -0
  5. package/dist/ClientSessionLeaderThreadProxy.js.map +1 -0
  6. package/dist/adapter-types.d.ts +10 -161
  7. package/dist/adapter-types.d.ts.map +1 -1
  8. package/dist/adapter-types.js +5 -49
  9. package/dist/adapter-types.js.map +1 -1
  10. package/dist/defs.d.ts +20 -0
  11. package/dist/defs.d.ts.map +1 -0
  12. package/dist/defs.js +12 -0
  13. package/dist/defs.js.map +1 -0
  14. package/dist/devtools/devtools-messages-client-session.d.ts +23 -21
  15. package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -1
  16. package/dist/devtools/devtools-messages-common.d.ts +6 -6
  17. package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
  18. package/dist/devtools/devtools-messages-leader.d.ts +26 -24
  19. package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
  20. package/dist/errors.d.ts +50 -0
  21. package/dist/errors.d.ts.map +1 -0
  22. package/dist/errors.js +36 -0
  23. package/dist/errors.js.map +1 -0
  24. package/dist/leader-thread/LeaderSyncProcessor.d.ts +6 -7
  25. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
  26. package/dist/leader-thread/LeaderSyncProcessor.js +112 -122
  27. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  28. package/dist/leader-thread/eventlog.d.ts +17 -6
  29. package/dist/leader-thread/eventlog.d.ts.map +1 -1
  30. package/dist/leader-thread/eventlog.js +32 -17
  31. package/dist/leader-thread/eventlog.js.map +1 -1
  32. package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
  33. package/dist/leader-thread/leader-worker-devtools.js +1 -2
  34. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  35. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  36. package/dist/leader-thread/make-leader-thread-layer.js +37 -7
  37. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  38. package/dist/leader-thread/materialize-event.d.ts.map +1 -1
  39. package/dist/leader-thread/materialize-event.js +7 -1
  40. package/dist/leader-thread/materialize-event.js.map +1 -1
  41. package/dist/leader-thread/mod.d.ts +1 -0
  42. package/dist/leader-thread/mod.d.ts.map +1 -1
  43. package/dist/leader-thread/mod.js +1 -0
  44. package/dist/leader-thread/mod.js.map +1 -1
  45. package/dist/leader-thread/recreate-db.d.ts +13 -6
  46. package/dist/leader-thread/recreate-db.d.ts.map +1 -1
  47. package/dist/leader-thread/recreate-db.js +1 -3
  48. package/dist/leader-thread/recreate-db.js.map +1 -1
  49. package/dist/leader-thread/types.d.ts +5 -7
  50. package/dist/leader-thread/types.d.ts.map +1 -1
  51. package/dist/make-client-session.d.ts +1 -1
  52. package/dist/make-client-session.d.ts.map +1 -1
  53. package/dist/make-client-session.js +1 -1
  54. package/dist/make-client-session.js.map +1 -1
  55. package/dist/rematerialize-from-eventlog.d.ts +1 -1
  56. package/dist/rematerialize-from-eventlog.d.ts.map +1 -1
  57. package/dist/rematerialize-from-eventlog.js +10 -2
  58. package/dist/rematerialize-from-eventlog.js.map +1 -1
  59. package/dist/schema/EventDef.d.ts +2 -2
  60. package/dist/schema/EventDef.d.ts.map +1 -1
  61. package/dist/schema/EventDef.js +2 -2
  62. package/dist/schema/EventDef.js.map +1 -1
  63. package/dist/schema/EventSequenceNumber.d.ts +20 -2
  64. package/dist/schema/EventSequenceNumber.d.ts.map +1 -1
  65. package/dist/schema/EventSequenceNumber.js +71 -19
  66. package/dist/schema/EventSequenceNumber.js.map +1 -1
  67. package/dist/schema/EventSequenceNumber.test.js +88 -3
  68. package/dist/schema/EventSequenceNumber.test.js.map +1 -1
  69. package/dist/schema/LiveStoreEvent.d.ts +25 -11
  70. package/dist/schema/LiveStoreEvent.d.ts.map +1 -1
  71. package/dist/schema/LiveStoreEvent.js +12 -4
  72. package/dist/schema/LiveStoreEvent.js.map +1 -1
  73. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +2 -2
  74. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -1
  75. package/dist/schema/state/sqlite/db-schema/hash.js +3 -1
  76. package/dist/schema/state/sqlite/db-schema/hash.js.map +1 -1
  77. package/dist/schema/state/sqlite/mod.d.ts +1 -1
  78. package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
  79. package/dist/schema/state/sqlite/query-builder/api.d.ts +35 -8
  80. package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
  81. package/dist/schema/state/sqlite/query-builder/api.js.map +1 -1
  82. package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
  83. package/dist/schema/state/sqlite/query-builder/impl.js +16 -11
  84. package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
  85. package/dist/schema/state/sqlite/query-builder/impl.test.d.ts +1 -81
  86. package/dist/schema/state/sqlite/query-builder/impl.test.d.ts.map +1 -1
  87. package/dist/schema/state/sqlite/query-builder/impl.test.js +34 -20
  88. package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
  89. package/dist/schema/state/sqlite/system-tables.d.ts +67 -62
  90. package/dist/schema/state/sqlite/system-tables.d.ts.map +1 -1
  91. package/dist/schema/state/sqlite/system-tables.js +8 -17
  92. package/dist/schema/state/sqlite/system-tables.js.map +1 -1
  93. package/dist/schema/state/sqlite/table-def.d.ts +1 -1
  94. package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
  95. package/dist/schema-management/migrations.d.ts +3 -1
  96. package/dist/schema-management/migrations.d.ts.map +1 -1
  97. package/dist/schema-management/migrations.js.map +1 -1
  98. package/dist/sql-queries/sql-queries.d.ts.map +1 -1
  99. package/dist/sql-queries/sql-queries.js +2 -0
  100. package/dist/sql-queries/sql-queries.js.map +1 -1
  101. package/dist/sqlite-types.d.ts +72 -0
  102. package/dist/sqlite-types.d.ts.map +1 -0
  103. package/dist/sqlite-types.js +5 -0
  104. package/dist/sqlite-types.js.map +1 -0
  105. package/dist/sync/ClientSessionSyncProcessor.d.ts +6 -2
  106. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  107. package/dist/sync/ClientSessionSyncProcessor.js +16 -13
  108. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  109. package/dist/sync/next/graphology.d.ts.map +1 -1
  110. package/dist/sync/next/graphology.js +0 -6
  111. package/dist/sync/next/graphology.js.map +1 -1
  112. package/dist/sync/next/rebase-events.d.ts.map +1 -1
  113. package/dist/sync/next/rebase-events.js +1 -0
  114. package/dist/sync/next/rebase-events.js.map +1 -1
  115. package/dist/sync/next/test/compact-events.test.js +1 -1
  116. package/dist/sync/next/test/compact-events.test.js.map +1 -1
  117. package/dist/sync/next/test/event-fixtures.d.ts.map +1 -1
  118. package/dist/sync/next/test/event-fixtures.js +12 -3
  119. package/dist/sync/next/test/event-fixtures.js.map +1 -1
  120. package/dist/sync/sync.d.ts +2 -0
  121. package/dist/sync/sync.d.ts.map +1 -1
  122. package/dist/sync/sync.js +3 -0
  123. package/dist/sync/sync.js.map +1 -1
  124. package/dist/sync/syncstate.d.ts +13 -4
  125. package/dist/sync/syncstate.d.ts.map +1 -1
  126. package/dist/sync/syncstate.js +23 -10
  127. package/dist/sync/syncstate.js.map +1 -1
  128. package/dist/sync/syncstate.test.js +17 -17
  129. package/dist/sync/syncstate.test.js.map +1 -1
  130. package/dist/version.d.ts +1 -1
  131. package/dist/version.d.ts.map +1 -1
  132. package/dist/version.js +1 -1
  133. package/dist/version.js.map +1 -1
  134. package/package.json +7 -6
  135. package/src/ClientSessionLeaderThreadProxy.ts +40 -0
  136. package/src/adapter-types.ts +19 -166
  137. package/src/defs.ts +17 -0
  138. package/src/errors.ts +49 -0
  139. package/src/leader-thread/LeaderSyncProcessor.ts +141 -180
  140. package/src/leader-thread/eventlog.ts +78 -56
  141. package/src/leader-thread/leader-worker-devtools.ts +1 -2
  142. package/src/leader-thread/make-leader-thread-layer.ts +52 -8
  143. package/src/leader-thread/materialize-event.ts +8 -1
  144. package/src/leader-thread/mod.ts +1 -0
  145. package/src/leader-thread/recreate-db.ts +99 -91
  146. package/src/leader-thread/types.ts +6 -11
  147. package/src/make-client-session.ts +2 -2
  148. package/src/rematerialize-from-eventlog.ts +10 -2
  149. package/src/schema/EventDef.ts +5 -3
  150. package/src/schema/EventSequenceNumber.test.ts +120 -3
  151. package/src/schema/EventSequenceNumber.ts +95 -23
  152. package/src/schema/LiveStoreEvent.ts +20 -4
  153. package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +2 -2
  154. package/src/schema/state/sqlite/db-schema/hash.ts +3 -3
  155. package/src/schema/state/sqlite/mod.ts +1 -1
  156. package/src/schema/state/sqlite/query-builder/api.ts +38 -8
  157. package/src/schema/state/sqlite/query-builder/impl.test.ts +60 -20
  158. package/src/schema/state/sqlite/query-builder/impl.ts +15 -12
  159. package/src/schema/state/sqlite/system-tables.ts +9 -22
  160. package/src/schema/state/sqlite/table-def.ts +1 -1
  161. package/src/schema-management/migrations.ts +3 -1
  162. package/src/sql-queries/sql-queries.ts +2 -0
  163. package/src/sqlite-types.ts +76 -0
  164. package/src/sync/ClientSessionSyncProcessor.ts +17 -20
  165. package/src/sync/next/graphology.ts +0 -6
  166. package/src/sync/next/rebase-events.ts +1 -0
  167. package/src/sync/next/test/compact-events.test.ts +1 -1
  168. package/src/sync/next/test/event-fixtures.ts +12 -3
  169. package/src/sync/sync.ts +3 -0
  170. package/src/sync/syncstate.test.ts +17 -17
  171. package/src/sync/syncstate.ts +31 -10
  172. package/src/version.ts +1 -1
@@ -14,7 +14,7 @@ import {
14
14
  import { migrateTable } from '../schema-management/migrations.js'
15
15
  import { insertRow, updateRows } from '../sql-queries/sql-queries.js'
16
16
  import type { PreparedBindValues } from '../util.js'
17
- import { prepareBindValues, sql } from '../util.js'
17
+ import { sql } from '../util.js'
18
18
  import { execSql } from './connection.js'
19
19
  import type { InitialSyncInfo } from './types.js'
20
20
  import { LeaderThreadCtx } from './types.js'
@@ -40,68 +40,78 @@ export const initEventlogDb = (dbEventlog: SqliteDb) =>
40
40
  )
41
41
  })
42
42
 
43
- /** Exclusive of the "since event" */
44
- export const getEventsSince = (
45
- since: EventSequenceNumber.EventSequenceNumber,
46
- ): Effect.Effect<ReadonlyArray<LiveStoreEvent.EncodedWithMeta>, never, LeaderThreadCtx> =>
47
- Effect.gen(function* () {
48
- const { dbEventlog, dbState } = yield* LeaderThreadCtx
49
-
50
- const query = eventlogMetaTable.where('seqNumGlobal', '>=', since.global).asSql()
51
- const pendingEventsRaw = dbEventlog.select(query.query, prepareBindValues(query.bindValues, query.query))
52
- const pendingEvents = Schema.decodeUnknownSync(eventlogMetaTable.rowSchema.pipe(Schema.Array))(pendingEventsRaw)
53
-
54
- const sessionChangesetRows = sessionChangesetMetaTable.where('seqNumGlobal', '>=', since.global).asSql()
55
- const sessionChangesetRowsRaw = dbState.select(
56
- sessionChangesetRows.query,
57
- prepareBindValues(sessionChangesetRows.bindValues, sessionChangesetRows.query),
58
- )
59
- const sessionChangesetRowsDecoded = Schema.decodeUnknownSync(
60
- sessionChangesetMetaTable.rowSchema.pipe(Schema.Array),
61
- )(sessionChangesetRowsRaw)
62
-
63
- return pendingEvents
64
- .map((eventlogEvent) => {
65
- const sessionChangeset = sessionChangesetRowsDecoded.find(
66
- (readModelEvent) =>
67
- readModelEvent.seqNumGlobal === eventlogEvent.seqNumGlobal &&
68
- readModelEvent.seqNumClient === eventlogEvent.seqNumClient,
69
- )
70
- return LiveStoreEvent.EncodedWithMeta.make({
71
- name: eventlogEvent.name,
72
- args: eventlogEvent.argsJson,
73
- seqNum: { global: eventlogEvent.seqNumGlobal, client: eventlogEvent.seqNumClient },
74
- parentSeqNum: { global: eventlogEvent.parentSeqNumGlobal, client: eventlogEvent.parentSeqNumClient },
75
- clientId: eventlogEvent.clientId,
76
- sessionId: eventlogEvent.sessionId,
77
- meta: {
78
- sessionChangeset:
79
- sessionChangeset && sessionChangeset.changeset !== null
80
- ? {
81
- _tag: 'sessionChangeset' as const,
82
- data: sessionChangeset.changeset,
83
- debug: sessionChangeset.debug,
84
- }
85
- : { _tag: 'unset' as const },
86
- syncMetadata: eventlogEvent.syncMetadataJson,
87
- materializerHashLeader: Option.none(),
88
- materializerHashSession: Option.none(),
89
- },
90
- })
43
+ /**
44
+ * Exclusive of the "since event"
45
+ * Also queries the state db in order to get the SQLite session changeset data.
46
+ */
47
+ export const getEventsSince = ({
48
+ dbEventlog,
49
+ dbState,
50
+ since,
51
+ }: {
52
+ dbEventlog: SqliteDb
53
+ dbState: SqliteDb
54
+ since: EventSequenceNumber.EventSequenceNumber
55
+ }): ReadonlyArray<LiveStoreEvent.EncodedWithMeta> => {
56
+ const pendingEvents = dbEventlog.select(eventlogMetaTable.where('seqNumGlobal', '>=', since.global))
57
+
58
+ const sessionChangesetRowsDecoded = dbState.select(
59
+ sessionChangesetMetaTable.where('seqNumGlobal', '>=', since.global),
60
+ )
61
+
62
+ return pendingEvents
63
+ .map((eventlogEvent) => {
64
+ const sessionChangeset = sessionChangesetRowsDecoded.find(
65
+ (readModelEvent) =>
66
+ readModelEvent.seqNumGlobal === eventlogEvent.seqNumGlobal &&
67
+ readModelEvent.seqNumClient === eventlogEvent.seqNumClient,
68
+ )
69
+ return LiveStoreEvent.EncodedWithMeta.make({
70
+ name: eventlogEvent.name,
71
+ args: eventlogEvent.argsJson,
72
+ seqNum: {
73
+ global: eventlogEvent.seqNumGlobal,
74
+ client: eventlogEvent.seqNumClient,
75
+ rebaseGeneration: eventlogEvent.seqNumRebaseGeneration,
76
+ },
77
+ parentSeqNum: {
78
+ global: eventlogEvent.parentSeqNumGlobal,
79
+ client: eventlogEvent.parentSeqNumClient,
80
+ rebaseGeneration: eventlogEvent.parentSeqNumRebaseGeneration,
81
+ },
82
+ clientId: eventlogEvent.clientId,
83
+ sessionId: eventlogEvent.sessionId,
84
+ meta: {
85
+ sessionChangeset:
86
+ sessionChangeset && sessionChangeset.changeset !== null
87
+ ? {
88
+ _tag: 'sessionChangeset' as const,
89
+ data: sessionChangeset.changeset,
90
+ debug: sessionChangeset.debug,
91
+ }
92
+ : { _tag: 'unset' as const },
93
+ syncMetadata: eventlogEvent.syncMetadataJson,
94
+ materializerHashLeader: Option.none(),
95
+ materializerHashSession: Option.none(),
96
+ },
91
97
  })
92
- .filter((_) => EventSequenceNumber.compare(_.seqNum, since) > 0)
93
- .sort((a, b) => EventSequenceNumber.compare(a.seqNum, b.seqNum))
94
- })
98
+ })
99
+ .filter((_) => EventSequenceNumber.compare(_.seqNum, since) > 0)
100
+ .sort((a, b) => EventSequenceNumber.compare(a.seqNum, b.seqNum))
101
+ }
95
102
 
96
103
  export const getClientHeadFromDb = (dbEventlog: SqliteDb): EventSequenceNumber.EventSequenceNumber => {
97
104
  const res = dbEventlog.select<{
98
105
  seqNumGlobal: EventSequenceNumber.GlobalEventSequenceNumber
99
106
  seqNumClient: EventSequenceNumber.ClientEventSequenceNumber
107
+ seqNumRebaseGeneration: number
100
108
  }>(
101
- sql`select seqNumGlobal, seqNumClient from ${EVENTLOG_META_TABLE} order by seqNumGlobal DESC, seqNumClient DESC limit 1`,
109
+ sql`select seqNumGlobal, seqNumClient, seqNumRebaseGeneration from ${EVENTLOG_META_TABLE} order by seqNumGlobal DESC, seqNumClient DESC limit 1`,
102
110
  )[0]
103
111
 
104
- return res ? { global: res.seqNumGlobal, client: res.seqNumClient } : EventSequenceNumber.ROOT
112
+ return res
113
+ ? { global: res.seqNumGlobal, client: res.seqNumClient, rebaseGeneration: res.seqNumRebaseGeneration }
114
+ : EventSequenceNumber.ROOT
105
115
  }
106
116
 
107
117
  export const getBackendHeadFromDb = (dbEventlog: SqliteDb): EventSequenceNumber.GlobalEventSequenceNumber =>
@@ -145,8 +155,10 @@ export const insertIntoEventlog = (
145
155
  values: {
146
156
  seqNumGlobal: eventEncoded.seqNum.global,
147
157
  seqNumClient: eventEncoded.seqNum.client,
158
+ seqNumRebaseGeneration: eventEncoded.seqNum.rebaseGeneration,
148
159
  parentSeqNumGlobal: eventEncoded.parentSeqNum.global,
149
160
  parentSeqNumClient: eventEncoded.parentSeqNum.client,
161
+ parentSeqNumRebaseGeneration: eventEncoded.parentSeqNum.rebaseGeneration,
150
162
  name: eventEncoded.name,
151
163
  argsJson: eventEncoded.args ?? {},
152
164
  clientId,
@@ -156,6 +168,8 @@ export const insertIntoEventlog = (
156
168
  },
157
169
  }),
158
170
  )
171
+
172
+ dbEventlog.debug.head = eventEncoded.seqNum
159
173
  })
160
174
 
161
175
  export const updateSyncMetadata = (items: ReadonlyArray<LiveStoreEvent.EncodedWithMeta>) =>
@@ -178,7 +192,11 @@ export const updateSyncMetadata = (items: ReadonlyArray<LiveStoreEvent.EncodedWi
178
192
  }
179
193
  })
180
194
 
181
- export const getSyncBackendCursorInfo = (remoteHead: EventSequenceNumber.GlobalEventSequenceNumber) =>
195
+ export const getSyncBackendCursorInfo = ({
196
+ remoteHead,
197
+ }: {
198
+ remoteHead: EventSequenceNumber.GlobalEventSequenceNumber
199
+ }) =>
182
200
  Effect.gen(function* () {
183
201
  const { dbEventlog } = yield* LeaderThreadCtx
184
202
 
@@ -195,7 +213,11 @@ export const getSyncBackendCursorInfo = (remoteHead: EventSequenceNumber.GlobalE
195
213
  ).pipe(Effect.andThen(Schema.decode(EventlogQuerySchema)), Effect.map(Option.flatten), Effect.orDie)
196
214
 
197
215
  return Option.some({
198
- cursor: { global: remoteHead, client: EventSequenceNumber.clientDefault },
216
+ cursor: {
217
+ global: remoteHead,
218
+ client: EventSequenceNumber.clientDefault,
219
+ rebaseGeneration: EventSequenceNumber.rebaseGenerationDefault,
220
+ },
199
221
  metadata: syncMetadataOption,
200
222
  }) satisfies InitialSyncInfo
201
223
  }).pipe(Effect.withSpan('@livestore/common:eventlog:getSyncBackendCursorInfo', { attributes: { remoteHead } }))
@@ -48,9 +48,8 @@ export const bootDevtools = (options: DevtoolsOptions) =>
48
48
  )
49
49
 
50
50
  const syncState = yield* syncProcessor.syncState
51
- const mergeCounter = syncProcessor.getMergeCounter()
52
51
 
53
- yield* syncProcessor.pull({ cursor: { mergeCounter, eventNum: syncState.localHead } }).pipe(
52
+ yield* syncProcessor.pull({ cursor: syncState.localHead }).pipe(
54
53
  Stream.tap(({ payload }) => sendMessage(Devtools.Leader.SyncPull.make({ payload, liveStoreVersion }))),
55
54
  Stream.runDrain,
56
55
  Effect.forkScoped,
@@ -1,16 +1,18 @@
1
+ import { shouldNeverHappen } from '@livestore/utils'
1
2
  import type { HttpClient, Schema, Scope } from '@livestore/utils/effect'
2
3
  import { Deferred, Effect, Layer, Queue, SubscriptionRef } from '@livestore/utils/effect'
3
4
 
4
- import type { BootStatus, MakeSqliteDb, SqliteError } from '../adapter-types.js'
5
+ import type { BootStatus, MakeSqliteDb, SqliteDb, SqliteError } from '../adapter-types.js'
5
6
  import { UnexpectedError } from '../adapter-types.js'
6
7
  import type * as Devtools from '../devtools/mod.js'
7
8
  import type { LiveStoreSchema } from '../schema/mod.js'
8
- import { LiveStoreEvent } from '../schema/mod.js'
9
+ import { EventSequenceNumber, LiveStoreEvent } from '../schema/mod.js'
9
10
  import type { InvalidPullError, IsOfflineError, SyncOptions } from '../sync/sync.js'
11
+ import { SyncState } from '../sync/syncstate.js'
10
12
  import { sql } from '../util.js'
11
13
  import * as Eventlog from './eventlog.js'
12
- import { bootDevtools } from './leader-worker-devtools.js'
13
14
  import { makeLeaderSyncProcessor } from './LeaderSyncProcessor.js'
15
+ import { bootDevtools } from './leader-worker-devtools.js'
14
16
  import { makeMaterializeEvent } from './materialize-event.js'
15
17
  import { recreateDb } from './recreate-db.js'
16
18
  import type { ShutdownChannel } from './shutdown-channel.js'
@@ -89,10 +91,8 @@ export const makeLeaderThreadLayer = ({
89
91
 
90
92
  const syncProcessor = yield* makeLeaderSyncProcessor({
91
93
  schema,
92
- dbEventlogMissing,
93
- dbEventlog,
94
94
  dbState,
95
- dbStateMissing,
95
+ initialSyncState: getInitialSyncState({ dbEventlog, dbState, dbEventlogMissing }),
96
96
  initialBlockingSyncContext,
97
97
  onError: syncOptions?.onSyncError ?? 'ignore',
98
98
  params: {
@@ -158,6 +158,48 @@ export const makeLeaderThreadLayer = ({
158
158
  Layer.unwrapScoped,
159
159
  )
160
160
 
161
+ const getInitialSyncState = ({
162
+ dbEventlog,
163
+ dbState,
164
+ dbEventlogMissing,
165
+ }: {
166
+ dbEventlog: SqliteDb
167
+ dbState: SqliteDb
168
+ dbEventlogMissing: boolean
169
+ }) => {
170
+ const initialBackendHead = dbEventlogMissing
171
+ ? EventSequenceNumber.ROOT.global
172
+ : Eventlog.getBackendHeadFromDb(dbEventlog)
173
+
174
+ const initialLocalHead = dbEventlogMissing ? EventSequenceNumber.ROOT : Eventlog.getClientHeadFromDb(dbEventlog)
175
+
176
+ if (initialBackendHead > initialLocalHead.global) {
177
+ return shouldNeverHappen(
178
+ `During boot the backend head (${initialBackendHead}) should never be greater than the local head (${initialLocalHead.global})`,
179
+ )
180
+ }
181
+
182
+ return SyncState.make({
183
+ localHead: initialLocalHead,
184
+ upstreamHead: {
185
+ global: initialBackendHead,
186
+ client: EventSequenceNumber.clientDefault,
187
+ rebaseGeneration: EventSequenceNumber.rebaseGenerationDefault,
188
+ },
189
+ pending: dbEventlogMissing
190
+ ? []
191
+ : Eventlog.getEventsSince({
192
+ dbEventlog,
193
+ dbState,
194
+ since: {
195
+ global: initialBackendHead,
196
+ client: EventSequenceNumber.clientDefault,
197
+ rebaseGeneration: initialLocalHead.rebaseGeneration,
198
+ },
199
+ }),
200
+ })
201
+ }
202
+
161
203
  const makeInitialBlockingSyncContext = ({
162
204
  initialSyncOptions,
163
205
  bootStatusQueue,
@@ -223,11 +265,13 @@ const bootLeaderThread = ({
223
265
  LeaderThreadCtx | Scope.Scope | HttpClient.HttpClient
224
266
  > =>
225
267
  Effect.gen(function* () {
226
- const { dbEventlog, bootStatusQueue, syncProcessor } = yield* LeaderThreadCtx
268
+ const { dbEventlog, bootStatusQueue, syncProcessor, schema, materializeEvent, dbState } = yield* LeaderThreadCtx
227
269
 
228
270
  yield* Eventlog.initEventlogDb(dbEventlog)
229
271
 
230
- const { migrationsReport } = dbStateMissing ? yield* recreateDb : { migrationsReport: { migrations: [] } }
272
+ const { migrationsReport } = dbStateMissing
273
+ ? yield* recreateDb({ dbState, dbEventlog, schema, bootStatusQueue, materializeEvent })
274
+ : { migrationsReport: { migrations: [] } }
231
275
 
232
276
  // NOTE the sync processor depends on the dbs being initialized properly
233
277
  const { initialLeaderHead } = yield* syncProcessor.boot
@@ -74,6 +74,8 @@ export const makeMaterializeEvent = ({
74
74
  yield* execSqlPrepared(dbState, statementSql, bindValues)
75
75
  }
76
76
 
77
+ dbState.debug.head = eventEncoded.seqNum
78
+
77
79
  const changeset = session.changeset()
78
80
  session.finish()
79
81
 
@@ -86,6 +88,7 @@ export const makeMaterializeEvent = ({
86
88
  values: {
87
89
  seqNumGlobal: eventEncoded.seqNum.global,
88
90
  seqNumClient: eventEncoded.seqNum.client,
91
+ seqNumRebaseGeneration: eventEncoded.seqNum.rebaseGeneration,
89
92
  // NOTE the changeset will be empty (i.e. null) for no-op events
90
93
  changeset: changeset ?? null,
91
94
  debug: LS_DEV ? execArgsArr : null,
@@ -149,7 +152,11 @@ export const rollback = ({
149
152
  sql`SELECT * FROM ${SystemTables.SESSION_CHANGESET_META_TABLE} WHERE (seqNumGlobal, seqNumClient) IN (${eventNumsToRollback.map((id) => `(${id.global}, ${id.client})`).join(', ')})`,
150
153
  )
151
154
  .map((_) => ({
152
- seqNum: { global: _.seqNumGlobal, client: _.seqNumClient },
155
+ seqNum: {
156
+ global: _.seqNumGlobal,
157
+ client: _.seqNumClient,
158
+ rebaseGeneration: -1, // unused in this code path
159
+ },
153
160
  changeset: _.changeset,
154
161
  debug: _.debug,
155
162
  }))
@@ -5,3 +5,4 @@ export * from './leader-worker-devtools.js'
5
5
  export * from './make-leader-thread-layer.js'
6
6
  export * as Eventlog from './eventlog.js'
7
7
  export * from './materialize-event.js'
8
+ export * from './recreate-db.js'
@@ -1,108 +1,116 @@
1
1
  import { casesHandled } from '@livestore/utils'
2
- import type { HttpClient } from '@livestore/utils/effect'
3
2
  import { Effect, Queue } from '@livestore/utils/effect'
4
3
 
5
- import type { InvalidPullError, IsOfflineError, MigrationHooks, MigrationsReport, SqliteError } from '../index.js'
4
+ import type { MigrationsReport } from '../defs.js'
5
+ import type { BootStatus, MigrationHooks, SqliteDb, SqliteError } from '../index.js'
6
6
  import { migrateDb, rematerializeFromEventlog, UnexpectedError } from '../index.js'
7
+ import type { LiveStoreSchema } from '../schema/mod.js'
7
8
  import { configureConnection } from './connection.js'
8
- import { LeaderThreadCtx } from './types.js'
9
-
10
- export const recreateDb: Effect.Effect<
11
- { migrationsReport: MigrationsReport },
12
- UnexpectedError | SqliteError | IsOfflineError | InvalidPullError,
13
- LeaderThreadCtx | HttpClient.HttpClient
14
- > = Effect.gen(function* () {
15
- const { dbState, dbEventlog, schema, bootStatusQueue, materializeEvent } = yield* LeaderThreadCtx
16
-
17
- const migrationOptions = schema.state.sqlite.migrations
18
- let migrationsReport: MigrationsReport
19
-
20
- yield* Effect.addFinalizer(
21
- Effect.fn('recreateDb:finalizer')(function* (ex) {
22
- if (ex._tag === 'Failure') dbState.destroy()
23
- }),
24
- )
25
-
26
- // NOTE to speed up the operations below, we're creating a temporary in-memory database
27
- // and later we'll overwrite the persisted database with the new data
28
- // TODO bring back this optimization
29
- // const tmpDb = yield* makeSqliteDb({ _tag: 'in-memory' })
30
- const tmpDb = dbState
31
- yield* configureConnection(tmpDb, { foreignKeys: true })
32
-
33
- const initDb = (hooks: Partial<MigrationHooks> | undefined) =>
34
- Effect.gen(function* () {
35
- yield* Effect.tryAll(() => hooks?.init?.(tmpDb)).pipe(UnexpectedError.mapToUnexpectedError)
36
-
37
- const migrationsReport = yield* migrateDb({
38
- db: tmpDb,
39
- schema,
40
- onProgress: ({ done, total }) =>
41
- Queue.offer(bootStatusQueue, { stage: 'migrating', progress: { done, total } }),
9
+ import type { MaterializeEvent } from './types.js'
10
+
11
+ export const recreateDb = ({
12
+ dbState,
13
+ dbEventlog,
14
+ schema,
15
+ bootStatusQueue,
16
+ materializeEvent,
17
+ }: {
18
+ dbState: SqliteDb
19
+ dbEventlog: SqliteDb
20
+ schema: LiveStoreSchema
21
+ bootStatusQueue: Queue.Queue<BootStatus>
22
+ materializeEvent: MaterializeEvent
23
+ }): Effect.Effect<{ migrationsReport: MigrationsReport }, UnexpectedError | SqliteError> =>
24
+ Effect.gen(function* () {
25
+ const migrationOptions = schema.state.sqlite.migrations
26
+ let migrationsReport: MigrationsReport
27
+
28
+ yield* Effect.addFinalizer(
29
+ Effect.fn('recreateDb:finalizer')(function* (ex) {
30
+ if (ex._tag === 'Failure') dbState.destroy()
31
+ }),
32
+ )
33
+
34
+ // NOTE to speed up the operations below, we're creating a temporary in-memory database
35
+ // and later we'll overwrite the persisted database with the new data
36
+ // TODO bring back this optimization
37
+ // const tmpDb = yield* makeSqliteDb({ _tag: 'in-memory' })
38
+ const tmpDb = dbState
39
+ yield* configureConnection(tmpDb, { foreignKeys: true })
40
+
41
+ const initDb = (hooks: Partial<MigrationHooks> | undefined) =>
42
+ Effect.gen(function* () {
43
+ yield* Effect.tryAll(() => hooks?.init?.(tmpDb)).pipe(UnexpectedError.mapToUnexpectedError)
44
+
45
+ const migrationsReport = yield* migrateDb({
46
+ db: tmpDb,
47
+ schema,
48
+ onProgress: ({ done, total }) =>
49
+ Queue.offer(bootStatusQueue, { stage: 'migrating', progress: { done, total } }),
50
+ })
51
+
52
+ yield* Effect.tryAll(() => hooks?.pre?.(tmpDb)).pipe(UnexpectedError.mapToUnexpectedError)
53
+
54
+ return { migrationsReport, tmpDb }
42
55
  })
43
56
 
44
- yield* Effect.tryAll(() => hooks?.pre?.(tmpDb)).pipe(UnexpectedError.mapToUnexpectedError)
57
+ switch (migrationOptions.strategy) {
58
+ case 'auto': {
59
+ const hooks = migrationOptions.hooks
60
+ const initResult = yield* initDb(hooks)
45
61
 
46
- return { migrationsReport, tmpDb }
47
- })
62
+ migrationsReport = initResult.migrationsReport
48
63
 
49
- switch (migrationOptions.strategy) {
50
- case 'auto': {
51
- const hooks = migrationOptions.hooks
52
- const initResult = yield* initDb(hooks)
64
+ yield* rematerializeFromEventlog({
65
+ // db: initResult.tmpDb,
66
+ dbEventlog,
67
+ schema,
68
+ materializeEvent,
69
+ onProgress: ({ done, total }) =>
70
+ Queue.offer(bootStatusQueue, { stage: 'rehydrating', progress: { done, total } }),
71
+ })
53
72
 
54
- migrationsReport = initResult.migrationsReport
73
+ yield* Effect.tryAll(() => hooks?.post?.(initResult.tmpDb)).pipe(UnexpectedError.mapToUnexpectedError)
55
74
 
56
- yield* rematerializeFromEventlog({
57
- // db: initResult.tmpDb,
58
- dbEventlog,
59
- schema,
60
- materializeEvent,
61
- onProgress: ({ done, total }) =>
62
- Queue.offer(bootStatusQueue, { stage: 'rehydrating', progress: { done, total } }),
63
- })
75
+ break
76
+ }
77
+ case 'manual': {
78
+ const oldDbData = dbState.export()
64
79
 
65
- yield* Effect.tryAll(() => hooks?.post?.(initResult.tmpDb)).pipe(UnexpectedError.mapToUnexpectedError)
66
-
67
- break
68
- }
69
- case 'manual': {
70
- const oldDbData = dbState.export()
80
+ migrationsReport = { migrations: [] }
71
81
 
72
- migrationsReport = { migrations: [] }
82
+ const newDbData = yield* Effect.tryAll(() => migrationOptions.migrate(oldDbData)).pipe(
83
+ UnexpectedError.mapToUnexpectedError,
84
+ )
73
85
 
74
- const newDbData = yield* Effect.tryAll(() => migrationOptions.migrate(oldDbData)).pipe(
75
- UnexpectedError.mapToUnexpectedError,
76
- )
86
+ tmpDb.import(newDbData)
77
87
 
78
- tmpDb.import(newDbData)
88
+ // TODO validate schema
79
89
 
80
- // TODO validate schema
81
-
82
- break
83
- }
84
- default: {
85
- casesHandled(migrationOptions)
90
+ break
91
+ }
92
+ default: {
93
+ casesHandled(migrationOptions)
94
+ }
86
95
  }
87
- }
88
-
89
- // TODO bring back
90
- // Import the temporary in-memory database into the persistent database
91
- // yield* Effect.sync(() => db.import(tmpDb)).pipe(
92
- // Effect.withSpan('@livestore/common:leader-thread:recreateDb:import'),
93
- // )
94
-
95
- // TODO maybe bring back re-using this initial snapshot to avoid calling `.export()` again
96
- // We've disabled this for now as it made the code too complex, as we often run syncing right after
97
- // so the snapshot is no longer up to date
98
- // const snapshotFromTmpDb = tmpDb.export()
99
-
100
- // TODO bring back
101
- // tmpDb.close()
102
-
103
- return { migrationsReport }
104
- }).pipe(
105
- Effect.scoped, // NOTE we're closing the scope here so finalizers are called when the effect is done
106
- Effect.withSpan('@livestore/common:leader-thread:recreateDb'),
107
- Effect.withPerformanceMeasure('@livestore/common:leader-thread:recreateDb'),
108
- )
96
+
97
+ // TODO bring back
98
+ // Import the temporary in-memory database into the persistent database
99
+ // yield* Effect.sync(() => db.import(tmpDb)).pipe(
100
+ // Effect.withSpan('@livestore/common:leader-thread:recreateDb:import'),
101
+ // )
102
+
103
+ // TODO maybe bring back re-using this initial snapshot to avoid calling `.export()` again
104
+ // We've disabled this for now as it made the code too complex, as we often run syncing right after
105
+ // so the snapshot is no longer up to date
106
+ // const snapshotFromTmpDb = tmpDb.export()
107
+
108
+ // TODO bring back
109
+ // tmpDb.close()
110
+
111
+ return { migrationsReport }
112
+ }).pipe(
113
+ Effect.scoped, // NOTE we're closing the scope here so finalizers are called when the effect is done
114
+ Effect.withSpan('@livestore/common:leader-thread:recreateDb'),
115
+ Effect.withPerformanceMeasure('@livestore/common:leader-thread:recreateDb'),
116
+ )
@@ -12,13 +12,13 @@ import type {
12
12
  import { Context, Schema } from '@livestore/utils/effect'
13
13
  import type { MeshNode } from '@livestore/webmesh'
14
14
 
15
- import type { LeaderPullCursor, SqliteError } from '../adapter-types.js'
15
+ import type { MigrationsReport } from '../defs.js'
16
+ import type { SqliteError } from '../errors.js'
16
17
  import type {
17
18
  BootStatus,
18
19
  Devtools,
19
20
  LeaderAheadError,
20
21
  MakeSqliteDb,
21
- MigrationsReport,
22
22
  PersistenceInfo,
23
23
  SqliteDb,
24
24
  SyncBackend,
@@ -136,16 +136,12 @@ export type InitialBlockingSyncContext = {
136
136
  export interface LeaderSyncProcessor {
137
137
  /** Used by client sessions to subscribe to upstream sync state changes */
138
138
  pull: (args: {
139
- cursor: LeaderPullCursor
140
- }) => Stream.Stream<{ payload: typeof SyncState.PayloadUpstream.Type; mergeCounter: number }, UnexpectedError>
139
+ cursor: EventSequenceNumber.EventSequenceNumber
140
+ }) => Stream.Stream<{ payload: typeof SyncState.PayloadUpstream.Type }, UnexpectedError>
141
141
  /** The `pullQueue` API can be used instead of `pull` when more convenient */
142
142
  pullQueue: (args: {
143
- cursor: LeaderPullCursor
144
- }) => Effect.Effect<
145
- Queue.Queue<{ payload: typeof SyncState.PayloadUpstream.Type; mergeCounter: number }>,
146
- UnexpectedError,
147
- Scope.Scope
148
- >
143
+ cursor: EventSequenceNumber.EventSequenceNumber
144
+ }) => Effect.Effect<Queue.Queue<{ payload: typeof SyncState.PayloadUpstream.Type }>, UnexpectedError, Scope.Scope>
149
145
 
150
146
  /** Used by client sessions to push events to the leader thread */
151
147
  push: (
@@ -173,5 +169,4 @@ export interface LeaderSyncProcessor {
173
169
  LeaderThreadCtx | Scope.Scope | HttpClient.HttpClient
174
170
  >
175
171
  syncState: Subscribable.Subscribable<SyncState.SyncState>
176
- getMergeCounter: () => number
177
172
  }
@@ -39,7 +39,7 @@ export const makeClientSession = <R>({
39
39
  sessionId: string
40
40
  isLeader: boolean
41
41
  lockStatus: SubscriptionRef.SubscriptionRef<LockStatus>
42
- leaderThread: ClientSessionLeaderThreadProxy
42
+ leaderThread: ClientSessionLeaderThreadProxy.ClientSessionLeaderThreadProxy
43
43
  sqliteDb: SqliteDb
44
44
  connectWebmeshNode: (args: {
45
45
  webmeshNode: Webmesh.MeshNode
@@ -133,4 +133,4 @@ export const makeClientSession = <R>({
133
133
  shutdown,
134
134
  debugInstanceId,
135
135
  } satisfies ClientSession
136
- })
136
+ }).pipe(Effect.withSpan('@livestore/common:make-client-session'))
@@ -56,8 +56,16 @@ This likely means the schema has changed in an incompatible way.
56
56
  )
57
57
 
58
58
  const eventEncoded = LiveStoreEvent.EncodedWithMeta.make({
59
- seqNum: { global: row.seqNumGlobal, client: row.seqNumClient },
60
- parentSeqNum: { global: row.parentSeqNumGlobal, client: row.parentSeqNumClient },
59
+ seqNum: {
60
+ global: row.seqNumGlobal,
61
+ client: row.seqNumClient,
62
+ rebaseGeneration: row.seqNumRebaseGeneration,
63
+ },
64
+ parentSeqNum: {
65
+ global: row.parentSeqNumGlobal,
66
+ client: row.parentSeqNumClient,
67
+ rebaseGeneration: row.parentSeqNumRebaseGeneration,
68
+ },
61
69
  name: row.name,
62
70
  args,
63
71
  clientId: row.clientId,
@@ -28,7 +28,9 @@ export type EventDef<TName extends string, TType, TEncoded = TType, TDerived ext
28
28
  }
29
29
 
30
30
  /** Helper function to construct a partial event */
31
- (args: TType): {
31
+ (
32
+ args: TType,
33
+ ): {
32
34
  name: TName
33
35
  args: TType
34
36
  }
@@ -194,14 +196,14 @@ export type Materializer<TEventDef extends EventDef.AnyWithoutFn = EventDef.AnyW
194
196
  ) => SingleOrReadonlyArray<MaterializerResult>
195
197
 
196
198
  export const defineMaterializer = <TEventDef extends EventDef.AnyWithoutFn>(
197
- eventDef: TEventDef,
199
+ _eventDef: TEventDef,
198
200
  materializer: Materializer<TEventDef>,
199
201
  ): Materializer<TEventDef> => {
200
202
  return materializer
201
203
  }
202
204
 
203
205
  export const materializers = <TInputRecord extends Record<string, EventDef.AnyWithoutFn>>(
204
- eventDefRecord: TInputRecord,
206
+ _eventDefRecord: TInputRecord,
205
207
  handlers: {
206
208
  [TEventName in TInputRecord[keyof TInputRecord]['name'] as Extract<
207
209
  TInputRecord[keyof TInputRecord],