@livestore/common 0.3.0-dev.4 → 0.3.0-dev.41

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