@livestore/common 0.3.0-dev.9 → 0.3.1-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 (479) hide show
  1. package/LICENSE +201 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/fixture.d.ts +83 -221
  4. package/dist/__tests__/fixture.d.ts.map +1 -1
  5. package/dist/__tests__/fixture.js +33 -11
  6. package/dist/__tests__/fixture.js.map +1 -1
  7. package/dist/adapter-types.d.ts +120 -64
  8. package/dist/adapter-types.d.ts.map +1 -1
  9. package/dist/adapter-types.js +39 -8
  10. package/dist/adapter-types.js.map +1 -1
  11. package/dist/bounded-collections.d.ts.map +1 -1
  12. package/dist/debug-info.d.ts +1 -1
  13. package/dist/debug-info.d.ts.map +1 -1
  14. package/dist/debug-info.js +1 -0
  15. package/dist/debug-info.js.map +1 -1
  16. package/dist/devtools/devtools-messages-client-session.d.ts +390 -0
  17. package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -0
  18. package/dist/devtools/devtools-messages-client-session.js +97 -0
  19. package/dist/devtools/devtools-messages-client-session.js.map +1 -0
  20. package/dist/devtools/devtools-messages-common.d.ts +68 -0
  21. package/dist/devtools/devtools-messages-common.d.ts.map +1 -0
  22. package/dist/devtools/devtools-messages-common.js +60 -0
  23. package/dist/devtools/devtools-messages-common.js.map +1 -0
  24. package/dist/devtools/devtools-messages-leader.d.ts +394 -0
  25. package/dist/devtools/devtools-messages-leader.d.ts.map +1 -0
  26. package/dist/devtools/devtools-messages-leader.js +147 -0
  27. package/dist/devtools/devtools-messages-leader.js.map +1 -0
  28. package/dist/devtools/devtools-messages.d.ts +3 -580
  29. package/dist/devtools/devtools-messages.d.ts.map +1 -1
  30. package/dist/devtools/devtools-messages.js +3 -174
  31. package/dist/devtools/devtools-messages.js.map +1 -1
  32. package/dist/devtools/devtools-sessioninfo.d.ts +32 -0
  33. package/dist/devtools/devtools-sessioninfo.d.ts.map +1 -0
  34. package/dist/devtools/devtools-sessioninfo.js +36 -0
  35. package/dist/devtools/devtools-sessioninfo.js.map +1 -0
  36. package/dist/devtools/mod.d.ts +55 -0
  37. package/dist/devtools/mod.d.ts.map +1 -0
  38. package/dist/devtools/mod.js +33 -0
  39. package/dist/devtools/mod.js.map +1 -0
  40. package/dist/index.d.ts +7 -9
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +7 -9
  43. package/dist/index.js.map +1 -1
  44. package/dist/leader-thread/LeaderSyncProcessor.d.ts +36 -11
  45. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
  46. package/dist/leader-thread/LeaderSyncProcessor.js +426 -252
  47. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  48. package/dist/leader-thread/connection.d.ts +34 -6
  49. package/dist/leader-thread/connection.d.ts.map +1 -1
  50. package/dist/leader-thread/connection.js +22 -7
  51. package/dist/leader-thread/connection.js.map +1 -1
  52. package/dist/leader-thread/eventlog.d.ts +27 -0
  53. package/dist/leader-thread/eventlog.d.ts.map +1 -0
  54. package/dist/leader-thread/eventlog.js +119 -0
  55. package/dist/leader-thread/eventlog.js.map +1 -0
  56. package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
  57. package/dist/leader-thread/leader-worker-devtools.js +155 -80
  58. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  59. package/dist/leader-thread/make-leader-thread-layer.d.ts +22 -9
  60. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  61. package/dist/leader-thread/make-leader-thread-layer.js +67 -45
  62. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  63. package/dist/leader-thread/materialize-event.d.ts +16 -0
  64. package/dist/leader-thread/materialize-event.d.ts.map +1 -0
  65. package/dist/leader-thread/materialize-event.js +109 -0
  66. package/dist/leader-thread/materialize-event.js.map +1 -0
  67. package/dist/leader-thread/mod.d.ts +1 -1
  68. package/dist/leader-thread/mod.d.ts.map +1 -1
  69. package/dist/leader-thread/mod.js +1 -1
  70. package/dist/leader-thread/mod.js.map +1 -1
  71. package/dist/leader-thread/recreate-db.d.ts +4 -2
  72. package/dist/leader-thread/recreate-db.d.ts.map +1 -1
  73. package/dist/leader-thread/recreate-db.js +28 -32
  74. package/dist/leader-thread/recreate-db.js.map +1 -1
  75. package/dist/leader-thread/shutdown-channel.d.ts +2 -5
  76. package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
  77. package/dist/leader-thread/shutdown-channel.js +2 -4
  78. package/dist/leader-thread/shutdown-channel.js.map +1 -1
  79. package/dist/leader-thread/types.d.ts +79 -38
  80. package/dist/leader-thread/types.d.ts.map +1 -1
  81. package/dist/leader-thread/types.js +1 -3
  82. package/dist/leader-thread/types.js.map +1 -1
  83. package/dist/make-client-session.d.ts +23 -0
  84. package/dist/make-client-session.d.ts.map +1 -0
  85. package/dist/make-client-session.js +57 -0
  86. package/dist/make-client-session.js.map +1 -0
  87. package/dist/materializer-helper.d.ts +23 -0
  88. package/dist/materializer-helper.d.ts.map +1 -0
  89. package/dist/materializer-helper.js +86 -0
  90. package/dist/materializer-helper.js.map +1 -0
  91. package/dist/otel.d.ts +2 -0
  92. package/dist/otel.d.ts.map +1 -1
  93. package/dist/otel.js +5 -0
  94. package/dist/otel.js.map +1 -1
  95. package/dist/rematerialize-from-eventlog.d.ts +14 -0
  96. package/dist/rematerialize-from-eventlog.d.ts.map +1 -0
  97. package/dist/rematerialize-from-eventlog.js +64 -0
  98. package/dist/rematerialize-from-eventlog.js.map +1 -0
  99. package/dist/schema/EventDef.d.ts +146 -0
  100. package/dist/schema/EventDef.d.ts.map +1 -0
  101. package/dist/schema/EventDef.js +58 -0
  102. package/dist/schema/EventDef.js.map +1 -0
  103. package/dist/schema/EventSequenceNumber.d.ts +57 -0
  104. package/dist/schema/EventSequenceNumber.d.ts.map +1 -0
  105. package/dist/schema/EventSequenceNumber.js +82 -0
  106. package/dist/schema/EventSequenceNumber.js.map +1 -0
  107. package/dist/schema/EventSequenceNumber.test.d.ts +2 -0
  108. package/dist/schema/EventSequenceNumber.test.d.ts.map +1 -0
  109. package/dist/schema/EventSequenceNumber.test.js +11 -0
  110. package/dist/schema/EventSequenceNumber.test.js.map +1 -0
  111. package/dist/schema/LiveStoreEvent.d.ts +257 -0
  112. package/dist/schema/LiveStoreEvent.d.ts.map +1 -0
  113. package/dist/schema/LiveStoreEvent.js +117 -0
  114. package/dist/schema/LiveStoreEvent.js.map +1 -0
  115. package/dist/schema/events.d.ts +2 -0
  116. package/dist/schema/events.d.ts.map +1 -0
  117. package/dist/schema/events.js +2 -0
  118. package/dist/schema/events.js.map +1 -0
  119. package/dist/schema/mod.d.ts +8 -6
  120. package/dist/schema/mod.d.ts.map +1 -1
  121. package/dist/schema/mod.js +8 -6
  122. package/dist/schema/mod.js.map +1 -1
  123. package/dist/schema/schema.d.ts +50 -32
  124. package/dist/schema/schema.d.ts.map +1 -1
  125. package/dist/schema/schema.js +36 -43
  126. package/dist/schema/schema.js.map +1 -1
  127. package/dist/schema/state/mod.d.ts +3 -0
  128. package/dist/schema/state/mod.d.ts.map +1 -0
  129. package/dist/schema/state/mod.js +3 -0
  130. package/dist/schema/state/mod.js.map +1 -0
  131. package/dist/schema/state/sqlite/client-document-def.d.ts +223 -0
  132. package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -0
  133. package/dist/schema/state/sqlite/client-document-def.js +170 -0
  134. package/dist/schema/state/sqlite/client-document-def.js.map +1 -0
  135. package/dist/schema/state/sqlite/client-document-def.test.d.ts +2 -0
  136. package/dist/schema/state/sqlite/client-document-def.test.d.ts.map +1 -0
  137. package/dist/schema/state/sqlite/client-document-def.test.js +201 -0
  138. package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -0
  139. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +69 -0
  140. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -0
  141. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +71 -0
  142. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -0
  143. package/dist/schema/state/sqlite/db-schema/ast/validate.d.ts +3 -0
  144. package/dist/schema/state/sqlite/db-schema/ast/validate.d.ts.map +1 -0
  145. package/dist/schema/state/sqlite/db-schema/ast/validate.js +12 -0
  146. package/dist/schema/state/sqlite/db-schema/ast/validate.js.map +1 -0
  147. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts +90 -0
  148. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts.map +1 -0
  149. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +87 -0
  150. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -0
  151. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.d.ts +2 -0
  152. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.d.ts.map +1 -0
  153. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js +29 -0
  154. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js.map +1 -0
  155. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts +90 -0
  156. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -0
  157. package/dist/schema/state/sqlite/db-schema/dsl/mod.js +41 -0
  158. package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -0
  159. package/dist/schema/state/sqlite/db-schema/hash.d.ts +2 -0
  160. package/dist/schema/state/sqlite/db-schema/hash.d.ts.map +1 -0
  161. package/dist/schema/state/sqlite/db-schema/hash.js +14 -0
  162. package/dist/schema/state/sqlite/db-schema/hash.js.map +1 -0
  163. package/dist/schema/state/sqlite/db-schema/mod.d.ts +3 -0
  164. package/dist/schema/state/sqlite/db-schema/mod.d.ts.map +1 -0
  165. package/dist/schema/state/sqlite/db-schema/mod.js +3 -0
  166. package/dist/schema/state/sqlite/db-schema/mod.js.map +1 -0
  167. package/dist/schema/state/sqlite/mod.d.ts +17 -0
  168. package/dist/schema/state/sqlite/mod.d.ts.map +1 -0
  169. package/dist/schema/state/sqlite/mod.js +41 -0
  170. package/dist/schema/state/sqlite/mod.js.map +1 -0
  171. package/dist/schema/state/sqlite/query-builder/api.d.ts +294 -0
  172. package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -0
  173. package/dist/schema/state/sqlite/query-builder/api.js +6 -0
  174. package/dist/schema/state/sqlite/query-builder/api.js.map +1 -0
  175. package/dist/schema/state/sqlite/query-builder/astToSql.d.ts +7 -0
  176. package/dist/schema/state/sqlite/query-builder/astToSql.d.ts.map +1 -0
  177. package/dist/schema/state/sqlite/query-builder/astToSql.js +190 -0
  178. package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -0
  179. package/dist/schema/state/sqlite/query-builder/impl.d.ts +7 -0
  180. package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -0
  181. package/dist/schema/state/sqlite/query-builder/impl.js +286 -0
  182. package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -0
  183. package/dist/schema/state/sqlite/query-builder/impl.test.d.ts +87 -0
  184. package/dist/schema/state/sqlite/query-builder/impl.test.d.ts.map +1 -0
  185. package/dist/schema/state/sqlite/query-builder/impl.test.js +563 -0
  186. package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -0
  187. package/dist/{query-builder → schema/state/sqlite/query-builder}/mod.d.ts +7 -0
  188. package/dist/schema/state/sqlite/query-builder/mod.d.ts.map +1 -0
  189. package/dist/{query-builder → schema/state/sqlite/query-builder}/mod.js +7 -0
  190. package/dist/schema/state/sqlite/query-builder/mod.js.map +1 -0
  191. package/dist/schema/state/sqlite/schema-helpers.d.ts.map +1 -0
  192. package/dist/schema/{schema-helpers.js → state/sqlite/schema-helpers.js} +1 -1
  193. package/dist/schema/state/sqlite/schema-helpers.js.map +1 -0
  194. package/dist/schema/state/sqlite/system-tables.d.ts +574 -0
  195. package/dist/schema/state/sqlite/system-tables.d.ts.map +1 -0
  196. package/dist/schema/state/sqlite/system-tables.js +88 -0
  197. package/dist/schema/state/sqlite/system-tables.js.map +1 -0
  198. package/dist/schema/state/sqlite/table-def.d.ts +84 -0
  199. package/dist/schema/state/sqlite/table-def.d.ts.map +1 -0
  200. package/dist/schema/state/sqlite/table-def.js +36 -0
  201. package/dist/schema/state/sqlite/table-def.js.map +1 -0
  202. package/dist/schema-management/common.d.ts +7 -7
  203. package/dist/schema-management/common.d.ts.map +1 -1
  204. package/dist/schema-management/common.js.map +1 -1
  205. package/dist/schema-management/migrations.d.ts +6 -6
  206. package/dist/schema-management/migrations.d.ts.map +1 -1
  207. package/dist/schema-management/migrations.js +27 -18
  208. package/dist/schema-management/migrations.js.map +1 -1
  209. package/dist/schema-management/validate-schema.d.ts +8 -0
  210. package/dist/schema-management/validate-schema.d.ts.map +1 -0
  211. package/dist/schema-management/validate-schema.js +39 -0
  212. package/dist/schema-management/validate-schema.js.map +1 -0
  213. package/dist/sql-queries/misc.d.ts.map +1 -1
  214. package/dist/sql-queries/sql-queries.d.ts +1 -1
  215. package/dist/sql-queries/sql-queries.d.ts.map +1 -1
  216. package/dist/sql-queries/sql-queries.js.map +1 -1
  217. package/dist/sql-queries/sql-query-builder.d.ts +1 -1
  218. package/dist/sql-queries/sql-query-builder.d.ts.map +1 -1
  219. package/dist/sql-queries/sql-query-builder.js.map +1 -1
  220. package/dist/sql-queries/types.d.ts +2 -1
  221. package/dist/sql-queries/types.d.ts.map +1 -1
  222. package/dist/sql-queries/types.js.map +1 -1
  223. package/dist/sync/ClientSessionSyncProcessor.d.ts +40 -19
  224. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  225. package/dist/sync/ClientSessionSyncProcessor.js +149 -73
  226. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  227. package/dist/sync/next/compact-events.d.ts.map +1 -1
  228. package/dist/sync/next/compact-events.js +38 -35
  229. package/dist/sync/next/compact-events.js.map +1 -1
  230. package/dist/sync/next/facts.d.ts +21 -21
  231. package/dist/sync/next/facts.d.ts.map +1 -1
  232. package/dist/sync/next/facts.js +11 -11
  233. package/dist/sync/next/facts.js.map +1 -1
  234. package/dist/sync/next/history-dag-common.d.ts +9 -7
  235. package/dist/sync/next/history-dag-common.d.ts.map +1 -1
  236. package/dist/sync/next/history-dag-common.js +10 -5
  237. package/dist/sync/next/history-dag-common.js.map +1 -1
  238. package/dist/sync/next/history-dag.d.ts +0 -2
  239. package/dist/sync/next/history-dag.d.ts.map +1 -1
  240. package/dist/sync/next/history-dag.js +16 -14
  241. package/dist/sync/next/history-dag.js.map +1 -1
  242. package/dist/sync/next/rebase-events.d.ts +10 -8
  243. package/dist/sync/next/rebase-events.d.ts.map +1 -1
  244. package/dist/sync/next/rebase-events.js +18 -10
  245. package/dist/sync/next/rebase-events.js.map +1 -1
  246. package/dist/sync/next/test/compact-events.calculator.test.js +39 -34
  247. package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
  248. package/dist/sync/next/test/compact-events.test.js +77 -77
  249. package/dist/sync/next/test/compact-events.test.js.map +1 -1
  250. package/dist/sync/next/test/{mutation-fixtures.d.ts → event-fixtures.d.ts} +35 -25
  251. package/dist/sync/next/test/event-fixtures.d.ts.map +1 -0
  252. package/dist/sync/next/test/{mutation-fixtures.js → event-fixtures.js} +83 -38
  253. package/dist/sync/next/test/event-fixtures.js.map +1 -0
  254. package/dist/sync/next/test/mod.d.ts +1 -1
  255. package/dist/sync/next/test/mod.d.ts.map +1 -1
  256. package/dist/sync/next/test/mod.js +1 -1
  257. package/dist/sync/next/test/mod.js.map +1 -1
  258. package/dist/sync/sync.d.ts +46 -21
  259. package/dist/sync/sync.d.ts.map +1 -1
  260. package/dist/sync/sync.js +10 -6
  261. package/dist/sync/sync.js.map +1 -1
  262. package/dist/sync/syncstate.d.ts +193 -84
  263. package/dist/sync/syncstate.d.ts.map +1 -1
  264. package/dist/sync/syncstate.js +305 -151
  265. package/dist/sync/syncstate.js.map +1 -1
  266. package/dist/sync/syncstate.test.js +267 -303
  267. package/dist/sync/syncstate.test.js.map +1 -1
  268. package/dist/sync/validate-push-payload.d.ts +2 -2
  269. package/dist/sync/validate-push-payload.d.ts.map +1 -1
  270. package/dist/sync/validate-push-payload.js +4 -4
  271. package/dist/sync/validate-push-payload.js.map +1 -1
  272. package/dist/util.d.ts +2 -2
  273. package/dist/util.d.ts.map +1 -1
  274. package/dist/version.d.ts +3 -3
  275. package/dist/version.js +3 -3
  276. package/package.json +11 -4
  277. package/src/__tests__/fixture.ts +36 -15
  278. package/src/adapter-types.ts +107 -68
  279. package/src/debug-info.ts +1 -0
  280. package/src/devtools/devtools-messages-client-session.ts +142 -0
  281. package/src/devtools/devtools-messages-common.ts +115 -0
  282. package/src/devtools/devtools-messages-leader.ts +191 -0
  283. package/src/devtools/devtools-messages.ts +3 -246
  284. package/src/devtools/devtools-sessioninfo.ts +101 -0
  285. package/src/devtools/mod.ts +59 -0
  286. package/src/index.ts +7 -9
  287. package/src/leader-thread/LeaderSyncProcessor.ts +664 -394
  288. package/src/leader-thread/connection.ts +54 -9
  289. package/src/leader-thread/eventlog.ts +199 -0
  290. package/src/leader-thread/leader-worker-devtools.ts +227 -104
  291. package/src/leader-thread/make-leader-thread-layer.ts +121 -72
  292. package/src/leader-thread/materialize-event.ts +173 -0
  293. package/src/leader-thread/mod.ts +1 -1
  294. package/src/leader-thread/recreate-db.ts +33 -38
  295. package/src/leader-thread/shutdown-channel.ts +2 -4
  296. package/src/leader-thread/types.ts +84 -46
  297. package/src/make-client-session.ts +136 -0
  298. package/src/materializer-helper.ts +138 -0
  299. package/src/otel.ts +8 -0
  300. package/src/rematerialize-from-eventlog.ts +117 -0
  301. package/src/schema/EventDef.ts +227 -0
  302. package/src/schema/EventSequenceNumber.test.ts +12 -0
  303. package/src/schema/EventSequenceNumber.ts +121 -0
  304. package/src/schema/LiveStoreEvent.ts +240 -0
  305. package/src/schema/events.ts +1 -0
  306. package/src/schema/mod.ts +8 -6
  307. package/src/schema/schema.ts +88 -84
  308. package/src/schema/state/mod.ts +2 -0
  309. package/src/schema/state/sqlite/client-document-def.test.ts +238 -0
  310. package/src/schema/state/sqlite/client-document-def.ts +444 -0
  311. package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +142 -0
  312. package/src/schema/state/sqlite/db-schema/ast/validate.ts +13 -0
  313. package/src/schema/state/sqlite/db-schema/dsl/__snapshots__/field-defs.test.ts.snap +206 -0
  314. package/src/schema/state/sqlite/db-schema/dsl/field-defs.test.ts +35 -0
  315. package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +242 -0
  316. package/src/schema/state/sqlite/db-schema/dsl/mod.ts +222 -0
  317. package/src/schema/state/sqlite/db-schema/hash.ts +14 -0
  318. package/src/schema/state/sqlite/db-schema/mod.ts +2 -0
  319. package/src/schema/state/sqlite/mod.ts +73 -0
  320. package/src/schema/state/sqlite/query-builder/api.ts +440 -0
  321. package/src/schema/state/sqlite/query-builder/astToSql.ts +232 -0
  322. package/src/schema/state/sqlite/query-builder/impl.test.ts +617 -0
  323. package/src/schema/state/sqlite/query-builder/impl.ts +351 -0
  324. package/src/{query-builder → schema/state/sqlite/query-builder}/mod.ts +7 -0
  325. package/src/schema/{schema-helpers.ts → state/sqlite/schema-helpers.ts} +1 -1
  326. package/src/schema/state/sqlite/system-tables.ts +117 -0
  327. package/src/schema/state/sqlite/table-def.ts +197 -0
  328. package/src/schema-management/common.ts +7 -7
  329. package/src/schema-management/migrations.ts +37 -31
  330. package/src/schema-management/validate-schema.ts +61 -0
  331. package/src/sql-queries/sql-queries.ts +1 -1
  332. package/src/sql-queries/sql-query-builder.ts +1 -2
  333. package/src/sql-queries/types.ts +3 -1
  334. package/src/sync/ClientSessionSyncProcessor.ts +218 -94
  335. package/src/sync/next/compact-events.ts +38 -35
  336. package/src/sync/next/facts.ts +43 -41
  337. package/src/sync/next/history-dag-common.ts +17 -10
  338. package/src/sync/next/history-dag.ts +16 -17
  339. package/src/sync/next/rebase-events.ts +29 -17
  340. package/src/sync/next/test/compact-events.calculator.test.ts +46 -46
  341. package/src/sync/next/test/compact-events.test.ts +79 -79
  342. package/src/sync/next/test/event-fixtures.ts +228 -0
  343. package/src/sync/next/test/mod.ts +1 -1
  344. package/src/sync/sync.ts +46 -21
  345. package/src/sync/syncstate.test.ts +312 -345
  346. package/src/sync/syncstate.ts +414 -224
  347. package/src/sync/validate-push-payload.ts +6 -6
  348. package/src/version.ts +3 -3
  349. package/dist/derived-mutations.d.ts +0 -109
  350. package/dist/derived-mutations.d.ts.map +0 -1
  351. package/dist/derived-mutations.js +0 -54
  352. package/dist/derived-mutations.js.map +0 -1
  353. package/dist/derived-mutations.test.d.ts +0 -2
  354. package/dist/derived-mutations.test.d.ts.map +0 -1
  355. package/dist/derived-mutations.test.js +0 -93
  356. package/dist/derived-mutations.test.js.map +0 -1
  357. package/dist/devtools/devtools-bridge.d.ts +0 -13
  358. package/dist/devtools/devtools-bridge.d.ts.map +0 -1
  359. package/dist/devtools/devtools-bridge.js +0 -2
  360. package/dist/devtools/devtools-bridge.js.map +0 -1
  361. package/dist/devtools/devtools-window-message.d.ts +0 -29
  362. package/dist/devtools/devtools-window-message.d.ts.map +0 -1
  363. package/dist/devtools/devtools-window-message.js +0 -33
  364. package/dist/devtools/devtools-window-message.js.map +0 -1
  365. package/dist/devtools/index.d.ts +0 -42
  366. package/dist/devtools/index.d.ts.map +0 -1
  367. package/dist/devtools/index.js +0 -48
  368. package/dist/devtools/index.js.map +0 -1
  369. package/dist/init-singleton-tables.d.ts +0 -4
  370. package/dist/init-singleton-tables.d.ts.map +0 -1
  371. package/dist/init-singleton-tables.js +0 -16
  372. package/dist/init-singleton-tables.js.map +0 -1
  373. package/dist/leader-thread/apply-mutation.d.ts +0 -11
  374. package/dist/leader-thread/apply-mutation.d.ts.map +0 -1
  375. package/dist/leader-thread/apply-mutation.js +0 -107
  376. package/dist/leader-thread/apply-mutation.js.map +0 -1
  377. package/dist/leader-thread/leader-sync-processor.d.ts +0 -47
  378. package/dist/leader-thread/leader-sync-processor.d.ts.map +0 -1
  379. package/dist/leader-thread/leader-sync-processor.js +0 -430
  380. package/dist/leader-thread/leader-sync-processor.js.map +0 -1
  381. package/dist/leader-thread/mutationlog.d.ts +0 -10
  382. package/dist/leader-thread/mutationlog.d.ts.map +0 -1
  383. package/dist/leader-thread/mutationlog.js +0 -28
  384. package/dist/leader-thread/mutationlog.js.map +0 -1
  385. package/dist/leader-thread/pull-queue-set.d.ts +0 -7
  386. package/dist/leader-thread/pull-queue-set.d.ts.map +0 -1
  387. package/dist/leader-thread/pull-queue-set.js +0 -39
  388. package/dist/leader-thread/pull-queue-set.js.map +0 -1
  389. package/dist/mutation.d.ts +0 -20
  390. package/dist/mutation.d.ts.map +0 -1
  391. package/dist/mutation.js +0 -57
  392. package/dist/mutation.js.map +0 -1
  393. package/dist/query-builder/api.d.ts +0 -190
  394. package/dist/query-builder/api.d.ts.map +0 -1
  395. package/dist/query-builder/api.js +0 -8
  396. package/dist/query-builder/api.js.map +0 -1
  397. package/dist/query-builder/impl.d.ts +0 -12
  398. package/dist/query-builder/impl.d.ts.map +0 -1
  399. package/dist/query-builder/impl.js +0 -244
  400. package/dist/query-builder/impl.js.map +0 -1
  401. package/dist/query-builder/impl.test.d.ts +0 -2
  402. package/dist/query-builder/impl.test.d.ts.map +0 -1
  403. package/dist/query-builder/impl.test.js +0 -212
  404. package/dist/query-builder/impl.test.js.map +0 -1
  405. package/dist/query-builder/mod.d.ts.map +0 -1
  406. package/dist/query-builder/mod.js.map +0 -1
  407. package/dist/query-info.d.ts +0 -38
  408. package/dist/query-info.d.ts.map +0 -1
  409. package/dist/query-info.js +0 -7
  410. package/dist/query-info.js.map +0 -1
  411. package/dist/rehydrate-from-mutationlog.d.ts +0 -14
  412. package/dist/rehydrate-from-mutationlog.d.ts.map +0 -1
  413. package/dist/rehydrate-from-mutationlog.js +0 -66
  414. package/dist/rehydrate-from-mutationlog.js.map +0 -1
  415. package/dist/schema/EventId.d.ts +0 -39
  416. package/dist/schema/EventId.d.ts.map +0 -1
  417. package/dist/schema/EventId.js +0 -38
  418. package/dist/schema/EventId.js.map +0 -1
  419. package/dist/schema/EventId.test.d.ts +0 -2
  420. package/dist/schema/EventId.test.d.ts.map +0 -1
  421. package/dist/schema/EventId.test.js +0 -11
  422. package/dist/schema/EventId.test.js.map +0 -1
  423. package/dist/schema/MutationEvent.d.ts +0 -167
  424. package/dist/schema/MutationEvent.d.ts.map +0 -1
  425. package/dist/schema/MutationEvent.js +0 -72
  426. package/dist/schema/MutationEvent.js.map +0 -1
  427. package/dist/schema/MutationEvent.test.d.ts +0 -2
  428. package/dist/schema/MutationEvent.test.d.ts.map +0 -1
  429. package/dist/schema/MutationEvent.test.js +0 -2
  430. package/dist/schema/MutationEvent.test.js.map +0 -1
  431. package/dist/schema/mutations.d.ts +0 -107
  432. package/dist/schema/mutations.d.ts.map +0 -1
  433. package/dist/schema/mutations.js +0 -42
  434. package/dist/schema/mutations.js.map +0 -1
  435. package/dist/schema/schema-helpers.d.ts.map +0 -1
  436. package/dist/schema/schema-helpers.js.map +0 -1
  437. package/dist/schema/system-tables.d.ts +0 -399
  438. package/dist/schema/system-tables.d.ts.map +0 -1
  439. package/dist/schema/system-tables.js +0 -59
  440. package/dist/schema/system-tables.js.map +0 -1
  441. package/dist/schema/table-def.d.ts +0 -156
  442. package/dist/schema/table-def.d.ts.map +0 -1
  443. package/dist/schema/table-def.js +0 -79
  444. package/dist/schema/table-def.js.map +0 -1
  445. package/dist/schema-management/validate-mutation-defs.d.ts +0 -8
  446. package/dist/schema-management/validate-mutation-defs.d.ts.map +0 -1
  447. package/dist/schema-management/validate-mutation-defs.js +0 -39
  448. package/dist/schema-management/validate-mutation-defs.js.map +0 -1
  449. package/dist/sync/client-session-sync-processor.d.ts +0 -45
  450. package/dist/sync/client-session-sync-processor.d.ts.map +0 -1
  451. package/dist/sync/client-session-sync-processor.js +0 -131
  452. package/dist/sync/client-session-sync-processor.js.map +0 -1
  453. package/dist/sync/next/test/mutation-fixtures.d.ts.map +0 -1
  454. package/dist/sync/next/test/mutation-fixtures.js.map +0 -1
  455. package/src/derived-mutations.test.ts +0 -101
  456. package/src/derived-mutations.ts +0 -170
  457. package/src/devtools/devtools-bridge.ts +0 -14
  458. package/src/devtools/devtools-window-message.ts +0 -27
  459. package/src/devtools/index.ts +0 -48
  460. package/src/init-singleton-tables.ts +0 -24
  461. package/src/leader-thread/apply-mutation.ts +0 -161
  462. package/src/leader-thread/mutationlog.ts +0 -46
  463. package/src/leader-thread/pull-queue-set.ts +0 -58
  464. package/src/mutation.ts +0 -91
  465. package/src/query-builder/api.ts +0 -289
  466. package/src/query-builder/impl.test.ts +0 -239
  467. package/src/query-builder/impl.ts +0 -285
  468. package/src/query-info.ts +0 -78
  469. package/src/rehydrate-from-mutationlog.ts +0 -119
  470. package/src/schema/EventId.test.ts +0 -12
  471. package/src/schema/EventId.ts +0 -60
  472. package/src/schema/MutationEvent.ts +0 -185
  473. package/src/schema/mutations.ts +0 -192
  474. package/src/schema/system-tables.ts +0 -105
  475. package/src/schema/table-def.ts +0 -343
  476. package/src/schema-management/validate-mutation-defs.ts +0 -63
  477. package/src/sync/next/test/mutation-fixtures.ts +0 -224
  478. package/tsconfig.json +0 -11
  479. /package/dist/schema/{schema-helpers.d.ts → state/sqlite/schema-helpers.d.ts} +0 -0
@@ -1,223 +1,314 @@
1
- import { shouldNeverHappen } from '@livestore/utils'
2
- import { ReadonlyArray, Schema } from '@livestore/utils/effect'
1
+ import { casesHandled, LS_DEV, shouldNeverHappen } from '@livestore/utils'
2
+ import { Match, ReadonlyArray, Schema } from '@livestore/utils/effect'
3
3
 
4
- import * as EventId from '../schema/EventId.js'
5
- import * as MutationEvent from '../schema/MutationEvent.js'
4
+ import { UnexpectedError } from '../adapter-types.js'
5
+ import * as EventSequenceNumber from '../schema/EventSequenceNumber.js'
6
+ import * as LiveStoreEvent from '../schema/LiveStoreEvent.js'
6
7
 
7
8
  /**
8
9
  * SyncState represents the current sync state of a sync node relative to an upstream node.
9
10
  * Events flow from local to upstream, with each state maintaining its own event head.
10
11
  *
11
- * Event Chain Structure:
12
+ * Example:
12
13
  * ```
13
- * +-------------------------+------------------------+
14
- * | ROLLBACK TAIL | PENDING EVENTS |
15
- * +-------------------------+------------------------+
16
- * ▼ ▼
17
- * Upstream Head Local Head
18
- * Example: (0,0), (0,1), (1,0) (1,1), (1,2), (2,0)
14
+ * +------------------------+
15
+ * | PENDING EVENTS |
16
+ * +------------------------+
17
+ * ▼ ▼
18
+ * Upstream Head Local Head
19
+ * (1,0) (1,1), (1,2), (2,0)
19
20
  * ```
20
21
  *
21
- * State:
22
- * - **Pending Events**: Events awaiting acknowledgment from the upstream.
23
- * - Can be confirmed or rejected by the upstream.
24
- * - Subject to rebase if rejected.
25
- * - **Rollback Tail**: Events that are kept around temporarily for potential rollback until confirmed by upstream.
22
+ * **Pending Events**: Events awaiting acknowledgment from the upstream.
23
+ * - Can be confirmed or rejected by the upstream.
24
+ * - Subject to rebase if rejected.
26
25
  *
27
26
  * Payloads:
28
27
  * - `PayloadUpstreamRebase`: Upstream has performed a rebase, so downstream must roll back to the specified event
29
28
  * and rebase the pending events on top of the new events.
30
29
  * - `PayloadUpstreamAdvance`: Upstream has advanced, so downstream must rebase the pending events on top of the new events.
31
- * - `PayloadUpstreamTrimRollbackTail`: Upstream has advanced, so downstream can trim the rollback tail.
32
30
  * - `PayloadLocalPush`: Local push payload
33
31
  *
34
32
  * Invariants:
35
33
  * 1. **Chain Continuity**: Each event must reference its immediate parent.
36
34
  * 2. **Head Ordering**: Upstream Head ≤ Local Head.
37
- * 3. **ID Sequence**: Must follow the pattern (1,0)→(1,1)→(1,2)→(2,0).
35
+ * 3. **Event number sequence**: Must follow the pattern (1,0)→(1,1)→(1,2)→(2,0).
38
36
  *
39
- * The `updateSyncState` function processes updates to the sync state based on incoming payloads,
40
- * handling cases such as upstream rebase, advance, local push, and rollback tail trimming.
37
+ * A few further notes to help form an intuition:
38
+ * - The goal is to keep the pending events as small as possible (i.e. to have synced with the next upstream node)
39
+ * - There are 2 cases for rebasing:
40
+ * - The conflicting event only conflicts with the pending events -> only (some of) the pending events need to be rolled back
41
+ *
42
+ * The `merge` function processes updates to the sync state based on incoming payloads,
43
+ * handling cases such as upstream rebase, advance and local push.
41
44
  */
42
45
  export class SyncState extends Schema.Class<SyncState>('SyncState')({
43
- pending: Schema.Array(MutationEvent.EncodedWithMeta),
44
- rollbackTail: Schema.Array(MutationEvent.EncodedWithMeta),
45
- upstreamHead: EventId.EventId,
46
- localHead: EventId.EventId,
46
+ pending: Schema.Array(LiveStoreEvent.EncodedWithMeta),
47
+ /** What this node expects the next upstream node to have as its own local head */
48
+ upstreamHead: EventSequenceNumber.EventSequenceNumber,
49
+ /** Equivalent to `pending.at(-1)?.id` if there are pending events */
50
+ localHead: EventSequenceNumber.EventSequenceNumber,
47
51
  }) {
48
- toJSON = (): any => {
49
- return {
50
- pending: this.pending.map((e) => e.toJSON()),
51
- rollbackTail: this.rollbackTail.map((e) => e.toJSON()),
52
- upstreamHead: `(${this.upstreamHead.global},${this.upstreamHead.local})`,
53
- localHead: `(${this.localHead.global},${this.localHead.local})`,
54
- }
55
- }
52
+ toJSON = (): any => ({
53
+ pending: this.pending.map((e) => e.toJSON()),
54
+ upstreamHead: EventSequenceNumber.toString(this.upstreamHead),
55
+ localHead: EventSequenceNumber.toString(this.localHead),
56
+ })
56
57
  }
57
58
 
59
+ /**
60
+ * This payload propagates a rebase from the upstream node
61
+ */
58
62
  export class PayloadUpstreamRebase extends Schema.TaggedStruct('upstream-rebase', {
59
- /** Rollback until this event in the rollback tail (inclusive). Starting from the end of the rollback tail. */
60
- rollbackUntil: EventId.EventId,
61
- newEvents: Schema.Array(MutationEvent.EncodedWithMeta),
62
- /** Trim rollback tail up to this event (inclusive). */
63
- trimRollbackUntil: Schema.optional(EventId.EventId),
63
+ /** Events which need to be rolled back */
64
+ rollbackEvents: Schema.Array(LiveStoreEvent.EncodedWithMeta),
65
+ /** Events which need to be applied after the rollback (already rebased by the upstream node) */
66
+ newEvents: Schema.Array(LiveStoreEvent.EncodedWithMeta),
64
67
  }) {}
65
68
 
66
69
  export class PayloadUpstreamAdvance extends Schema.TaggedStruct('upstream-advance', {
67
- newEvents: Schema.Array(MutationEvent.EncodedWithMeta),
68
- /** Trim rollback tail up to this event (inclusive). */
69
- trimRollbackUntil: Schema.optional(EventId.EventId),
70
+ newEvents: Schema.Array(LiveStoreEvent.EncodedWithMeta),
70
71
  }) {}
71
72
 
72
73
  export class PayloadLocalPush extends Schema.TaggedStruct('local-push', {
73
- newEvents: Schema.Array(MutationEvent.EncodedWithMeta),
74
+ newEvents: Schema.Array(LiveStoreEvent.EncodedWithMeta),
74
75
  }) {}
75
76
 
76
77
  export class Payload extends Schema.Union(PayloadUpstreamRebase, PayloadUpstreamAdvance, PayloadLocalPush) {}
77
78
 
78
- export const PayloadUpstream = Schema.Union(PayloadUpstreamRebase, PayloadUpstreamAdvance)
79
+ export class PayloadUpstream extends Schema.Union(PayloadUpstreamRebase, PayloadUpstreamAdvance) {}
79
80
 
80
- export type PayloadUpstream = typeof PayloadUpstream.Type
81
+ /** Only used for debugging purposes */
82
+ export class MergeContext extends Schema.Class<MergeContext>('MergeContext')({
83
+ payload: Payload,
84
+ syncState: SyncState,
85
+ }) {
86
+ toJSON = (): any => {
87
+ const payload = Match.value(this.payload).pipe(
88
+ Match.tag('local-push', () => ({
89
+ _tag: 'local-push',
90
+ newEvents: this.payload.newEvents.map((e) => e.toJSON()),
91
+ })),
92
+ Match.tag('upstream-advance', () => ({
93
+ _tag: 'upstream-advance',
94
+ newEvents: this.payload.newEvents.map((e) => e.toJSON()),
95
+ })),
96
+ Match.tag('upstream-rebase', (payload) => ({
97
+ _tag: 'upstream-rebase',
98
+ newEvents: payload.newEvents.map((e) => e.toJSON()),
99
+ rollbackEvents: payload.rollbackEvents.map((e) => e.toJSON()),
100
+ })),
101
+ Match.exhaustive,
102
+ )
103
+ return {
104
+ payload,
105
+ syncState: this.syncState.toJSON(),
106
+ }
107
+ }
108
+ }
81
109
 
82
- export type UpdateResultAdvance = {
83
- _tag: 'advance'
84
- newSyncState: SyncState
85
- previousSyncState: SyncState
86
- /** Events which weren't pending before the update */
87
- newEvents: ReadonlyArray<MutationEvent.EncodedWithMeta>
110
+ export class MergeResultAdvance extends Schema.Class<MergeResultAdvance>('MergeResultAdvance')({
111
+ _tag: Schema.Literal('advance'),
112
+ newSyncState: SyncState,
113
+ newEvents: Schema.Array(LiveStoreEvent.EncodedWithMeta),
114
+ /** Events which were previously pending but are now confirmed */
115
+ confirmedEvents: Schema.Array(LiveStoreEvent.EncodedWithMeta),
116
+ mergeContext: MergeContext,
117
+ }) {
118
+ toJSON = (): any => {
119
+ return {
120
+ _tag: this._tag,
121
+ newSyncState: this.newSyncState.toJSON(),
122
+ newEvents: this.newEvents.map((e) => e.toJSON()),
123
+ confirmedEvents: this.confirmedEvents.map((e) => e.toJSON()),
124
+ mergeContext: this.mergeContext.toJSON(),
125
+ }
126
+ }
88
127
  }
89
128
 
90
- export type UpdateResultRebase = {
91
- _tag: 'rebase'
92
- newSyncState: SyncState
93
- previousSyncState: SyncState
94
- /** Events which weren't pending before the update */
95
- newEvents: ReadonlyArray<MutationEvent.EncodedWithMeta>
96
- eventsToRollback: ReadonlyArray<MutationEvent.EncodedWithMeta>
129
+ export class MergeResultRebase extends Schema.Class<MergeResultRebase>('MergeResultRebase')({
130
+ _tag: Schema.Literal('rebase'),
131
+ newSyncState: SyncState,
132
+ newEvents: Schema.Array(LiveStoreEvent.EncodedWithMeta),
133
+ /** Events which need to be rolled back */
134
+ rollbackEvents: Schema.Array(LiveStoreEvent.EncodedWithMeta),
135
+ mergeContext: MergeContext,
136
+ }) {
137
+ toJSON = (): any => {
138
+ return {
139
+ _tag: this._tag,
140
+ newSyncState: this.newSyncState.toJSON(),
141
+ newEvents: this.newEvents.map((e) => e.toJSON()),
142
+ rollbackEvents: this.rollbackEvents.map((e) => e.toJSON()),
143
+ mergeContext: this.mergeContext.toJSON(),
144
+ }
145
+ }
97
146
  }
98
147
 
99
- export type UpdateResultReject = {
100
- _tag: 'reject'
101
- previousSyncState: SyncState
148
+ export class MergeResultReject extends Schema.Class<MergeResultReject>('MergeResultReject')({
149
+ _tag: Schema.Literal('reject'),
102
150
  /** The minimum id that the new events must have */
103
- expectedMinimumId: EventId.EventId
151
+ expectedMinimumId: EventSequenceNumber.EventSequenceNumber,
152
+ mergeContext: MergeContext,
153
+ }) {
154
+ toJSON = (): any => {
155
+ return {
156
+ _tag: this._tag,
157
+ expectedMinimumId: EventSequenceNumber.toString(this.expectedMinimumId),
158
+ mergeContext: this.mergeContext.toJSON(),
159
+ }
160
+ }
104
161
  }
105
162
 
106
- export type UpdateResult = UpdateResultAdvance | UpdateResultRebase | UpdateResultReject
163
+ export class MergeResultUnexpectedError extends Schema.Class<MergeResultUnexpectedError>('MergeResultUnexpectedError')({
164
+ _tag: Schema.Literal('unexpected-error'),
165
+ cause: UnexpectedError,
166
+ }) {}
167
+
168
+ export class MergeResult extends Schema.Union(
169
+ MergeResultAdvance,
170
+ MergeResultRebase,
171
+ MergeResultReject,
172
+ MergeResultUnexpectedError,
173
+ ) {}
174
+
175
+ const unexpectedError = (cause: unknown): MergeResultUnexpectedError => {
176
+ if (LS_DEV) {
177
+ debugger
178
+ }
179
+
180
+ return MergeResultUnexpectedError.make({
181
+ _tag: 'unexpected-error',
182
+ cause: new UnexpectedError({ cause }),
183
+ })
184
+ }
107
185
 
108
- export const updateSyncState = ({
186
+ // TODO Idea: call merge recursively through hierarchy levels
187
+ /*
188
+ Idea: have a map that maps from `globalEventSequenceNumber` to Array<ClientEvents>
189
+ The same applies to even further hierarchy levels
190
+
191
+ TODO: possibly even keep the client events in a separate table in the client leader
192
+ */
193
+ export const merge = ({
109
194
  syncState,
110
195
  payload,
111
- isLocalEvent,
196
+ isClientEvent,
112
197
  isEqualEvent,
113
- ignoreLocalEvents = false,
198
+ ignoreClientEvents = false,
114
199
  }: {
115
200
  syncState: SyncState
116
201
  payload: typeof Payload.Type
117
- isLocalEvent: (event: MutationEvent.EncodedWithMeta) => boolean
118
- isEqualEvent: (a: MutationEvent.EncodedWithMeta, b: MutationEvent.EncodedWithMeta) => boolean
119
- /** This is used in the leader which should ignore local events when receiving an upstream-advance payload */
120
- ignoreLocalEvents?: boolean
121
- }): UpdateResult => {
122
- const trimRollbackTail = (
123
- rollbackTail: ReadonlyArray<MutationEvent.EncodedWithMeta>,
124
- ): ReadonlyArray<MutationEvent.EncodedWithMeta> => {
125
- const trimRollbackUntil = payload._tag === 'local-push' ? undefined : payload.trimRollbackUntil
126
- if (trimRollbackUntil === undefined) return rollbackTail
127
- const index = rollbackTail.findIndex((event) => EventId.isEqual(event.id, trimRollbackUntil))
128
- if (index === -1) return []
129
- return rollbackTail.slice(index + 1)
130
- }
202
+ isClientEvent: (event: LiveStoreEvent.EncodedWithMeta) => boolean
203
+ isEqualEvent: (a: LiveStoreEvent.EncodedWithMeta, b: LiveStoreEvent.EncodedWithMeta) => boolean
204
+ /** This is used in the leader which should ignore client events when receiving an upstream-advance payload */
205
+ ignoreClientEvents?: boolean
206
+ }): typeof MergeResult.Type => {
207
+ validateSyncState(syncState)
208
+ validatePayload(payload)
209
+
210
+ const mergeContext = MergeContext.make({ payload, syncState })
131
211
 
132
212
  switch (payload._tag) {
133
213
  case 'upstream-rebase': {
134
- // Find the index of the rollback event in the rollback tail
135
- const rollbackIndex = syncState.rollbackTail.findIndex((event) =>
136
- EventId.isEqual(event.id, payload.rollbackUntil),
137
- )
138
- if (rollbackIndex === -1) {
139
- return shouldNeverHappen(
140
- `Rollback event not found in rollback tail. Rollback until: [${payload.rollbackUntil.global},${payload.rollbackUntil.local}]. Rollback tail: [${syncState.rollbackTail.map((e) => e.toString()).join(', ')}]`,
141
- )
142
- }
143
-
144
- const eventsToRollback = [...syncState.rollbackTail.slice(rollbackIndex), ...syncState.pending]
214
+ const rollbackEvents = [...payload.rollbackEvents, ...syncState.pending]
145
215
 
146
216
  // Get the last new event's ID as the new upstream head
147
- const newUpstreamHead = payload.newEvents.at(-1)?.id ?? syncState.upstreamHead
217
+ const newUpstreamHead = payload.newEvents.at(-1)?.seqNum ?? syncState.upstreamHead
148
218
 
149
219
  // Rebase pending events on top of the new events
150
220
  const rebasedPending = rebaseEvents({
151
221
  events: syncState.pending,
152
- baseEventId: newUpstreamHead,
153
- isLocalEvent,
222
+ baseEventSequenceNumber: newUpstreamHead,
223
+ isClientEvent,
154
224
  })
155
225
 
156
- return {
157
- _tag: 'rebase',
158
- newSyncState: new SyncState({
159
- pending: rebasedPending,
160
- rollbackTail: trimRollbackTail([...syncState.rollbackTail.slice(0, rollbackIndex), ...payload.newEvents]),
161
- upstreamHead: newUpstreamHead,
162
- localHead: rebasedPending.at(-1)?.id ?? newUpstreamHead,
226
+ return validateMergeResult(
227
+ MergeResultRebase.make({
228
+ _tag: 'rebase',
229
+ newSyncState: new SyncState({
230
+ pending: rebasedPending,
231
+ upstreamHead: newUpstreamHead,
232
+ localHead: rebasedPending.at(-1)?.seqNum ?? newUpstreamHead,
233
+ }),
234
+ newEvents: [...payload.newEvents, ...rebasedPending],
235
+ rollbackEvents,
236
+ mergeContext,
163
237
  }),
164
- previousSyncState: syncState,
165
- newEvents: payload.newEvents,
166
- eventsToRollback,
167
- }
238
+ )
168
239
  }
169
240
 
241
+ // #region upstream-advance
170
242
  case 'upstream-advance': {
171
243
  if (payload.newEvents.length === 0) {
172
- return {
173
- _tag: 'advance',
174
- newSyncState: new SyncState({
175
- pending: syncState.pending,
176
- rollbackTail: trimRollbackTail(syncState.rollbackTail),
177
- upstreamHead: syncState.upstreamHead,
178
- localHead: syncState.localHead,
244
+ return validateMergeResult(
245
+ MergeResultAdvance.make({
246
+ _tag: 'advance',
247
+ newSyncState: new SyncState({
248
+ pending: syncState.pending,
249
+ upstreamHead: syncState.upstreamHead,
250
+ localHead: syncState.localHead,
251
+ }),
252
+ newEvents: [],
253
+ confirmedEvents: [],
254
+ mergeContext: mergeContext,
179
255
  }),
180
- previousSyncState: syncState,
181
- newEvents: [],
182
- }
256
+ )
183
257
  }
184
258
 
185
- // Validate that newEvents are sorted in ascending order by eventId
259
+ // Validate that newEvents are sorted in ascending order by eventNum
186
260
  for (let i = 1; i < payload.newEvents.length; i++) {
187
- if (EventId.isGreaterThan(payload.newEvents[i - 1]!.id, payload.newEvents[i]!.id)) {
188
- return shouldNeverHappen('Events must be sorted in ascending order by eventId')
261
+ if (EventSequenceNumber.isGreaterThan(payload.newEvents[i - 1]!.seqNum, payload.newEvents[i]!.seqNum)) {
262
+ return unexpectedError(
263
+ `Events must be sorted in ascending order by event number. Received: [${payload.newEvents.map((e) => EventSequenceNumber.toString(e.seqNum)).join(', ')}]`,
264
+ )
189
265
  }
190
266
  }
191
267
 
192
- const newUpstreamHead = payload.newEvents.at(-1)!.id
268
+ // Validate that incoming events are larger than upstream head
269
+ if (
270
+ EventSequenceNumber.isGreaterThan(syncState.upstreamHead, payload.newEvents[0]!.seqNum) ||
271
+ EventSequenceNumber.isEqual(syncState.upstreamHead, payload.newEvents[0]!.seqNum)
272
+ ) {
273
+ return unexpectedError(
274
+ `Incoming events must be greater than upstream head. Expected greater than: ${EventSequenceNumber.toString(syncState.upstreamHead)}. Received: [${payload.newEvents.map((e) => EventSequenceNumber.toString(e.seqNum)).join(', ')}]`,
275
+ )
276
+ }
277
+
278
+ const newUpstreamHead = payload.newEvents.at(-1)!.seqNum
193
279
 
194
280
  const divergentPendingIndex = findDivergencePoint({
195
281
  existingEvents: syncState.pending,
196
282
  incomingEvents: payload.newEvents,
197
283
  isEqualEvent,
198
- isLocalEvent,
199
- ignoreLocalEvents,
284
+ isClientEvent,
285
+ ignoreClientEvents,
200
286
  })
201
287
 
288
+ // No divergent pending events, thus we can just advance (some of) the pending events
202
289
  if (divergentPendingIndex === -1) {
203
- const pendingEventIds = new Set(syncState.pending.map((e) => `${e.id.global},${e.id.local}`))
204
- const newEvents = payload.newEvents.filter((e) => !pendingEventIds.has(`${e.id.global},${e.id.local}`))
290
+ const pendingEventSequenceNumbers = new Set(
291
+ syncState.pending.map((e) => `${e.seqNum.global},${e.seqNum.client}`),
292
+ )
293
+ const newEvents = payload.newEvents.filter(
294
+ (e) => !pendingEventSequenceNumbers.has(`${e.seqNum.global},${e.seqNum.client}`),
295
+ )
205
296
 
206
297
  // In the case where the incoming events are a subset of the pending events,
207
298
  // we need to split the pending events into two groups:
208
299
  // - pendingMatching: The pending events up to point where they match the incoming events
209
300
  // - pendingRemaining: The pending events after the point where they match the incoming events
210
- // The `localIndexOffset` is used to account for the local events that are being ignored
211
- let localIndexOffset = 0
301
+ // The `clientIndexOffset` is used to account for the client events that are being ignored
302
+ let clientIndexOffset = 0
212
303
  const [pendingMatching, pendingRemaining] = ReadonlyArray.splitWhere(
213
304
  syncState.pending,
214
305
  (pendingEvent, index) => {
215
- if (ignoreLocalEvents && isLocalEvent(pendingEvent)) {
216
- localIndexOffset++
306
+ if (ignoreClientEvents && isClientEvent(pendingEvent)) {
307
+ clientIndexOffset++
217
308
  return false
218
309
  }
219
310
 
220
- const newEvent = payload.newEvents.at(index - localIndexOffset)
311
+ const newEvent = payload.newEvents.at(index - clientIndexOffset)
221
312
  if (!newEvent) {
222
313
  return true
223
314
  }
@@ -225,105 +316,100 @@ export const updateSyncState = ({
225
316
  },
226
317
  )
227
318
 
228
- const seenEventIds = new Set<string>()
229
- const pendingAndNewEvents = [...pendingMatching, ...payload.newEvents].filter((event) => {
230
- const eventIdStr = `${event.id.global},${event.id.local}`
231
- if (seenEventIds.has(eventIdStr)) {
232
- return false
233
- }
234
- seenEventIds.add(eventIdStr)
235
- return true
236
- })
237
-
238
- return {
239
- _tag: 'advance',
240
- newSyncState: new SyncState({
241
- pending: pendingRemaining,
242
- rollbackTail: trimRollbackTail([...syncState.rollbackTail, ...pendingAndNewEvents]),
243
- upstreamHead: newUpstreamHead,
244
- localHead: pendingRemaining.at(-1)?.id ?? newUpstreamHead,
319
+ return validateMergeResult(
320
+ MergeResultAdvance.make({
321
+ _tag: 'advance',
322
+ newSyncState: new SyncState({
323
+ pending: pendingRemaining,
324
+ upstreamHead: newUpstreamHead,
325
+ localHead:
326
+ pendingRemaining.at(-1)?.seqNum ?? EventSequenceNumber.max(syncState.localHead, newUpstreamHead),
327
+ }),
328
+ newEvents,
329
+ confirmedEvents: pendingMatching,
330
+ mergeContext: mergeContext,
245
331
  }),
246
- previousSyncState: syncState,
247
- newEvents,
248
- }
332
+ )
249
333
  } else {
250
334
  const divergentPending = syncState.pending.slice(divergentPendingIndex)
251
335
  const rebasedPending = rebaseEvents({
252
336
  events: divergentPending,
253
- baseEventId: newUpstreamHead,
254
- isLocalEvent,
337
+ baseEventSequenceNumber: newUpstreamHead,
338
+ isClientEvent,
255
339
  })
256
340
 
257
341
  const divergentNewEventsIndex = findDivergencePoint({
258
342
  existingEvents: payload.newEvents,
259
343
  incomingEvents: syncState.pending,
260
344
  isEqualEvent,
261
- isLocalEvent,
262
- ignoreLocalEvents,
345
+ isClientEvent,
346
+ ignoreClientEvents,
263
347
  })
264
348
 
265
- return {
266
- _tag: 'rebase',
267
- newSyncState: new SyncState({
268
- pending: rebasedPending,
269
- rollbackTail: trimRollbackTail([...syncState.rollbackTail, ...payload.newEvents]),
270
- upstreamHead: newUpstreamHead,
271
- localHead: rebasedPending.at(-1)!.id,
349
+ return validateMergeResult(
350
+ MergeResultRebase.make({
351
+ _tag: 'rebase',
352
+ newSyncState: new SyncState({
353
+ pending: rebasedPending,
354
+ upstreamHead: newUpstreamHead,
355
+ localHead: rebasedPending.at(-1)!.seqNum,
356
+ }),
357
+ newEvents: [...payload.newEvents.slice(divergentNewEventsIndex), ...rebasedPending],
358
+ rollbackEvents: divergentPending,
359
+ mergeContext,
272
360
  }),
273
- previousSyncState: syncState,
274
- newEvents: [...payload.newEvents.slice(divergentNewEventsIndex), ...rebasedPending],
275
- eventsToRollback: [...syncState.rollbackTail, ...divergentPending],
276
- }
361
+ )
277
362
  }
278
363
  }
364
+ // #endregion
279
365
 
366
+ // This is the same as what's running in the sync backend
280
367
  case 'local-push': {
281
368
  if (payload.newEvents.length === 0) {
282
- return { _tag: 'advance', newSyncState: syncState, previousSyncState: syncState, newEvents: [] }
369
+ return validateMergeResult(
370
+ MergeResultAdvance.make({
371
+ _tag: 'advance',
372
+ newSyncState: syncState,
373
+ newEvents: [],
374
+ confirmedEvents: [],
375
+ mergeContext: mergeContext,
376
+ }),
377
+ )
283
378
  }
284
379
 
285
380
  const newEventsFirst = payload.newEvents.at(0)!
286
- const invalidEventId = EventId.isGreaterThan(newEventsFirst.id, syncState.localHead) === false
287
-
288
- if (invalidEventId) {
289
- const expectedMinimumId = EventId.nextPair(syncState.localHead, true).id
290
- return { _tag: 'reject', previousSyncState: syncState, expectedMinimumId }
381
+ const invalidEventSequenceNumber =
382
+ EventSequenceNumber.isGreaterThan(newEventsFirst.seqNum, syncState.localHead) === false
383
+
384
+ if (invalidEventSequenceNumber) {
385
+ const expectedMinimumId = EventSequenceNumber.nextPair(syncState.localHead, true).seqNum
386
+ return validateMergeResult(
387
+ MergeResultReject.make({
388
+ _tag: 'reject',
389
+ expectedMinimumId,
390
+ mergeContext,
391
+ }),
392
+ )
291
393
  } else {
292
- return {
293
- _tag: 'advance',
294
- newSyncState: new SyncState({
295
- pending: [...syncState.pending, ...payload.newEvents],
296
- rollbackTail: syncState.rollbackTail,
297
- upstreamHead: syncState.upstreamHead,
298
- localHead: payload.newEvents.at(-1)!.id,
394
+ return validateMergeResult(
395
+ MergeResultAdvance.make({
396
+ _tag: 'advance',
397
+ newSyncState: new SyncState({
398
+ pending: [...syncState.pending, ...payload.newEvents],
399
+ upstreamHead: syncState.upstreamHead,
400
+ localHead: payload.newEvents.at(-1)!.seqNum,
401
+ }),
402
+ newEvents: payload.newEvents,
403
+ confirmedEvents: [],
404
+ mergeContext: mergeContext,
299
405
  }),
300
- previousSyncState: syncState,
301
- newEvents: payload.newEvents,
302
- }
406
+ )
303
407
  }
304
408
  }
305
409
 
306
- // case 'upstream-trim-rollback-tail': {
307
- // // Find the index of the new rollback start in the rollback tail
308
- // const startIndex = syncState.rollbackTail.findIndex((event) => eventIdsEqual(event.id, payload.trimRollbackUntil))
309
- // if (startIndex === -1) {
310
- // return shouldNeverHappen('New rollback start event not found in rollback tail')
311
- // }
312
-
313
- // // Keep only the events from the start index onwards
314
- // const newRollbackTail = syncState.rollbackTail.slice(startIndex)
315
-
316
- // return {
317
- // _tag: 'advance',
318
- // syncState: {
319
- // pending: syncState.pending,
320
- // rollbackTail: newRollbackTail,
321
- // upstreamHead: syncState.upstreamHead,
322
- // localHead: syncState.localHead,
323
- // },
324
- // newEvents: [],
325
- // }
326
- // }
410
+ default: {
411
+ casesHandled(payload)
412
+ }
327
413
  }
328
414
  }
329
415
 
@@ -331,34 +417,36 @@ export const updateSyncState = ({
331
417
  * Gets the index relative to `existingEvents` where the divergence point is
332
418
  * by comparing each event in `existingEvents` to the corresponding event in `incomingEvents`
333
419
  */
334
- const findDivergencePoint = ({
420
+ export const findDivergencePoint = ({
335
421
  existingEvents,
336
422
  incomingEvents,
337
423
  isEqualEvent,
338
- isLocalEvent,
339
- ignoreLocalEvents,
424
+ isClientEvent,
425
+ ignoreClientEvents,
340
426
  }: {
341
- existingEvents: ReadonlyArray<MutationEvent.EncodedWithMeta>
342
- incomingEvents: ReadonlyArray<MutationEvent.EncodedWithMeta>
343
- isEqualEvent: (a: MutationEvent.EncodedWithMeta, b: MutationEvent.EncodedWithMeta) => boolean
344
- isLocalEvent: (event: MutationEvent.EncodedWithMeta) => boolean
345
- ignoreLocalEvents: boolean
427
+ existingEvents: ReadonlyArray<LiveStoreEvent.EncodedWithMeta>
428
+ incomingEvents: ReadonlyArray<LiveStoreEvent.EncodedWithMeta>
429
+ isEqualEvent: (a: LiveStoreEvent.EncodedWithMeta, b: LiveStoreEvent.EncodedWithMeta) => boolean
430
+ isClientEvent: (event: LiveStoreEvent.EncodedWithMeta) => boolean
431
+ ignoreClientEvents: boolean
346
432
  }): number => {
347
- if (ignoreLocalEvents) {
348
- const filteredExistingEvents = existingEvents.filter((event) => !isLocalEvent(event))
349
- const divergencePointWithoutLocalEvents = findDivergencePoint({
433
+ if (ignoreClientEvents) {
434
+ const filteredExistingEvents = existingEvents.filter((event) => !isClientEvent(event))
435
+ const divergencePointWithoutClientEvents = findDivergencePoint({
350
436
  existingEvents: filteredExistingEvents,
351
437
  incomingEvents,
352
438
  isEqualEvent,
353
- isLocalEvent,
354
- ignoreLocalEvents: false,
439
+ isClientEvent,
440
+ ignoreClientEvents: false,
355
441
  })
356
442
 
357
- if (divergencePointWithoutLocalEvents === -1) return -1
443
+ if (divergencePointWithoutClientEvents === -1) return -1
358
444
 
359
- const divergencePointEventId = existingEvents[divergencePointWithoutLocalEvents]!.id
445
+ const divergencePointEventSequenceNumber = existingEvents[divergencePointWithoutClientEvents]!.seqNum
360
446
  // Now find the divergence point in the original array
361
- return existingEvents.findIndex((event) => EventId.isEqual(event.id, divergencePointEventId))
447
+ return existingEvents.findIndex((event) =>
448
+ EventSequenceNumber.isEqual(event.seqNum, divergencePointEventSequenceNumber),
449
+ )
362
450
  }
363
451
 
364
452
  return existingEvents.findIndex((existingEvent, index) => {
@@ -370,18 +458,120 @@ const findDivergencePoint = ({
370
458
 
371
459
  const rebaseEvents = ({
372
460
  events,
373
- baseEventId,
374
- isLocalEvent,
461
+ baseEventSequenceNumber,
462
+ isClientEvent,
375
463
  }: {
376
- events: ReadonlyArray<MutationEvent.EncodedWithMeta>
377
- baseEventId: EventId.EventId
378
- isLocalEvent: (event: MutationEvent.EncodedWithMeta) => boolean
379
- }): ReadonlyArray<MutationEvent.EncodedWithMeta> => {
380
- let prevEventId = baseEventId
464
+ events: ReadonlyArray<LiveStoreEvent.EncodedWithMeta>
465
+ baseEventSequenceNumber: EventSequenceNumber.EventSequenceNumber
466
+ isClientEvent: (event: LiveStoreEvent.EncodedWithMeta) => boolean
467
+ }): ReadonlyArray<LiveStoreEvent.EncodedWithMeta> => {
468
+ let prevEventSequenceNumber = baseEventSequenceNumber
381
469
  return events.map((event) => {
382
- const isLocal = isLocalEvent(event)
383
- const newEvent = event.rebase(prevEventId, isLocal)
384
- prevEventId = newEvent.id
470
+ const isLocal = isClientEvent(event)
471
+ const newEvent = event.rebase(prevEventSequenceNumber, isLocal)
472
+ prevEventSequenceNumber = newEvent.seqNum
385
473
  return newEvent
386
474
  })
387
475
  }
476
+
477
+ /**
478
+ * TODO: Implement this
479
+ *
480
+ * In certain scenarios e.g. when the client session has a queue of upstream update results,
481
+ * it could make sense to "flatten" update results into a single update result which the client session
482
+ * can process more efficiently which avoids push-threshing
483
+ */
484
+ const _flattenMergeResults = (_updateResults: ReadonlyArray<MergeResult>) => {}
485
+
486
+ const validatePayload = (payload: typeof Payload.Type) => {
487
+ for (let i = 1; i < payload.newEvents.length; i++) {
488
+ if (EventSequenceNumber.isGreaterThanOrEqual(payload.newEvents[i - 1]!.seqNum, payload.newEvents[i]!.seqNum)) {
489
+ return unexpectedError(
490
+ `Events must be ordered in monotonically ascending order by eventNum. Received: [${payload.newEvents.map((e) => EventSequenceNumber.toString(e.seqNum)).join(', ')}]`,
491
+ )
492
+ }
493
+ }
494
+ }
495
+
496
+ const validateSyncState = (syncState: SyncState) => {
497
+ for (let i = 0; i < syncState.pending.length; i++) {
498
+ const event = syncState.pending[i]!
499
+ const nextEvent = syncState.pending[i + 1]
500
+ if (nextEvent === undefined) break // Reached end of chain
501
+
502
+ if (EventSequenceNumber.isGreaterThanOrEqual(event.seqNum, nextEvent.seqNum)) {
503
+ shouldNeverHappen(
504
+ `Events must be ordered in monotonically ascending order by eventNum. Received: [${syncState.pending.map((e) => EventSequenceNumber.toString(e.seqNum)).join(', ')}]`,
505
+ {
506
+ event,
507
+ nextEvent,
508
+ },
509
+ )
510
+ }
511
+
512
+ // If the global id has increased, then the client id must be 0
513
+ const globalIdHasIncreased = nextEvent.seqNum.global > event.seqNum.global
514
+ if (globalIdHasIncreased) {
515
+ if (nextEvent.seqNum.client !== 0) {
516
+ shouldNeverHappen(
517
+ `New global events must point to clientId 0 in the parentSeqNum. Received: (${EventSequenceNumber.toString(nextEvent.seqNum)})`,
518
+ syncState.pending,
519
+ {
520
+ event,
521
+ nextEvent,
522
+ },
523
+ )
524
+ }
525
+ } else {
526
+ // Otherwise, the parentSeqNum must be the same as the previous event's id
527
+ if (EventSequenceNumber.isEqual(nextEvent.parentSeqNum, event.seqNum) === false) {
528
+ shouldNeverHappen('Events must be linked in a continuous chain via the parentSeqNum', syncState.pending, {
529
+ event,
530
+ nextEvent,
531
+ })
532
+ }
533
+ }
534
+ }
535
+ }
536
+
537
+ const validateMergeResult = (mergeResult: typeof MergeResult.Type) => {
538
+ if (mergeResult._tag === 'unexpected-error' || mergeResult._tag === 'reject') return mergeResult
539
+
540
+ validateSyncState(mergeResult.newSyncState)
541
+
542
+ // Ensure local head is always greater than or equal to upstream head
543
+ if (EventSequenceNumber.isGreaterThan(mergeResult.newSyncState.upstreamHead, mergeResult.newSyncState.localHead)) {
544
+ shouldNeverHappen('Local head must be greater than or equal to upstream head', {
545
+ localHead: mergeResult.newSyncState.localHead,
546
+ upstreamHead: mergeResult.newSyncState.upstreamHead,
547
+ })
548
+ }
549
+
550
+ // Ensure new local head is greater than or equal to the previous local head
551
+ if (
552
+ EventSequenceNumber.isGreaterThanOrEqual(
553
+ mergeResult.newSyncState.localHead,
554
+ mergeResult.mergeContext.syncState.localHead,
555
+ ) === false
556
+ ) {
557
+ shouldNeverHappen('New local head must be greater than or equal to the previous local head', {
558
+ localHead: mergeResult.newSyncState.localHead,
559
+ previousLocalHead: mergeResult.mergeContext.syncState.localHead,
560
+ })
561
+ }
562
+
563
+ // Ensure new upstream head is greater than or equal to the previous upstream head
564
+ if (
565
+ EventSequenceNumber.isGreaterThanOrEqual(
566
+ mergeResult.newSyncState.upstreamHead,
567
+ mergeResult.mergeContext.syncState.upstreamHead,
568
+ ) === false
569
+ ) {
570
+ shouldNeverHappen('New upstream head must be greater than or equal to the previous upstream head', {
571
+ upstreamHead: mergeResult.newSyncState.upstreamHead,
572
+ previousUpstreamHead: mergeResult.mergeContext.syncState.upstreamHead,
573
+ })
574
+ }
575
+
576
+ return mergeResult
577
+ }