@livestore/common 0.3.0-dev.9 → 0.3.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 (480) 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 +120 -64
  7. package/dist/adapter-types.d.ts.map +1 -1
  8. package/dist/adapter-types.js +39 -8
  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 -580
  28. package/dist/devtools/devtools-messages.d.ts.map +1 -1
  29. package/dist/devtools/devtools-messages.js +3 -174
  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 -9
  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 +36 -11
  44. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
  45. package/dist/leader-thread/LeaderSyncProcessor.js +426 -252
  46. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  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.map +1 -1
  56. package/dist/leader-thread/leader-worker-devtools.js +155 -80
  57. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  58. package/dist/leader-thread/make-leader-thread-layer.d.ts +22 -9
  59. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  60. package/dist/leader-thread/make-leader-thread-layer.js +67 -45
  61. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  62. package/dist/leader-thread/materialize-event.d.ts +16 -0
  63. package/dist/leader-thread/materialize-event.d.ts.map +1 -0
  64. package/dist/leader-thread/materialize-event.js +109 -0
  65. package/dist/leader-thread/materialize-event.js.map +1 -0
  66. package/dist/leader-thread/mod.d.ts +1 -1
  67. package/dist/leader-thread/mod.d.ts.map +1 -1
  68. package/dist/leader-thread/mod.js +1 -1
  69. package/dist/leader-thread/mod.js.map +1 -1
  70. package/dist/leader-thread/recreate-db.d.ts +4 -2
  71. package/dist/leader-thread/recreate-db.d.ts.map +1 -1
  72. package/dist/leader-thread/recreate-db.js +28 -32
  73. package/dist/leader-thread/recreate-db.js.map +1 -1
  74. package/dist/leader-thread/shutdown-channel.d.ts +2 -5
  75. package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
  76. package/dist/leader-thread/shutdown-channel.js +2 -4
  77. package/dist/leader-thread/shutdown-channel.js.map +1 -1
  78. package/dist/leader-thread/types.d.ts +79 -38
  79. package/dist/leader-thread/types.d.ts.map +1 -1
  80. package/dist/leader-thread/types.js +1 -3
  81. package/dist/leader-thread/types.js.map +1 -1
  82. package/dist/make-client-session.d.ts +23 -0
  83. package/dist/make-client-session.d.ts.map +1 -0
  84. package/dist/make-client-session.js +57 -0
  85. package/dist/make-client-session.js.map +1 -0
  86. package/dist/materializer-helper.d.ts +23 -0
  87. package/dist/materializer-helper.d.ts.map +1 -0
  88. package/dist/materializer-helper.js +86 -0
  89. package/dist/materializer-helper.js.map +1 -0
  90. package/dist/otel.d.ts +2 -0
  91. package/dist/otel.d.ts.map +1 -1
  92. package/dist/otel.js +5 -0
  93. package/dist/otel.js.map +1 -1
  94. package/dist/rematerialize-from-eventlog.d.ts +14 -0
  95. package/dist/rematerialize-from-eventlog.d.ts.map +1 -0
  96. package/dist/rematerialize-from-eventlog.js +64 -0
  97. package/dist/rematerialize-from-eventlog.js.map +1 -0
  98. package/dist/schema/EventDef.d.ts +146 -0
  99. package/dist/schema/EventDef.d.ts.map +1 -0
  100. package/dist/schema/EventDef.js +58 -0
  101. package/dist/schema/EventDef.js.map +1 -0
  102. package/dist/schema/EventSequenceNumber.d.ts +57 -0
  103. package/dist/schema/EventSequenceNumber.d.ts.map +1 -0
  104. package/dist/schema/EventSequenceNumber.js +82 -0
  105. package/dist/schema/EventSequenceNumber.js.map +1 -0
  106. package/dist/schema/EventSequenceNumber.test.d.ts +2 -0
  107. package/dist/schema/EventSequenceNumber.test.d.ts.map +1 -0
  108. package/dist/schema/EventSequenceNumber.test.js +11 -0
  109. package/dist/schema/EventSequenceNumber.test.js.map +1 -0
  110. package/dist/schema/LiveStoreEvent.d.ts +257 -0
  111. package/dist/schema/LiveStoreEvent.d.ts.map +1 -0
  112. package/dist/schema/LiveStoreEvent.js +117 -0
  113. package/dist/schema/LiveStoreEvent.js.map +1 -0
  114. package/dist/schema/events.d.ts +2 -0
  115. package/dist/schema/events.d.ts.map +1 -0
  116. package/dist/schema/events.js +2 -0
  117. package/dist/schema/events.js.map +1 -0
  118. package/dist/schema/mod.d.ts +8 -6
  119. package/dist/schema/mod.d.ts.map +1 -1
  120. package/dist/schema/mod.js +8 -6
  121. package/dist/schema/mod.js.map +1 -1
  122. package/dist/schema/schema.d.ts +50 -32
  123. package/dist/schema/schema.d.ts.map +1 -1
  124. package/dist/schema/schema.js +36 -43
  125. package/dist/schema/schema.js.map +1 -1
  126. package/dist/schema/state/mod.d.ts +3 -0
  127. package/dist/schema/state/mod.d.ts.map +1 -0
  128. package/dist/schema/state/mod.js +3 -0
  129. package/dist/schema/state/mod.js.map +1 -0
  130. package/dist/schema/state/sqlite/client-document-def.d.ts +223 -0
  131. package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -0
  132. package/dist/schema/state/sqlite/client-document-def.js +170 -0
  133. package/dist/schema/state/sqlite/client-document-def.js.map +1 -0
  134. package/dist/schema/state/sqlite/client-document-def.test.d.ts +2 -0
  135. package/dist/schema/state/sqlite/client-document-def.test.d.ts.map +1 -0
  136. package/dist/schema/state/sqlite/client-document-def.test.js +201 -0
  137. package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -0
  138. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +69 -0
  139. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -0
  140. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +71 -0
  141. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -0
  142. package/dist/schema/state/sqlite/db-schema/ast/validate.d.ts +3 -0
  143. package/dist/schema/state/sqlite/db-schema/ast/validate.d.ts.map +1 -0
  144. package/dist/schema/state/sqlite/db-schema/ast/validate.js +12 -0
  145. package/dist/schema/state/sqlite/db-schema/ast/validate.js.map +1 -0
  146. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts +90 -0
  147. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts.map +1 -0
  148. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +87 -0
  149. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -0
  150. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.d.ts +2 -0
  151. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.d.ts.map +1 -0
  152. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js +29 -0
  153. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js.map +1 -0
  154. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts +90 -0
  155. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -0
  156. package/dist/schema/state/sqlite/db-schema/dsl/mod.js +41 -0
  157. package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -0
  158. package/dist/schema/state/sqlite/db-schema/hash.d.ts +2 -0
  159. package/dist/schema/state/sqlite/db-schema/hash.d.ts.map +1 -0
  160. package/dist/schema/state/sqlite/db-schema/hash.js +14 -0
  161. package/dist/schema/state/sqlite/db-schema/hash.js.map +1 -0
  162. package/dist/schema/state/sqlite/db-schema/mod.d.ts +3 -0
  163. package/dist/schema/state/sqlite/db-schema/mod.d.ts.map +1 -0
  164. package/dist/schema/state/sqlite/db-schema/mod.js +3 -0
  165. package/dist/schema/state/sqlite/db-schema/mod.js.map +1 -0
  166. package/dist/schema/state/sqlite/mod.d.ts +17 -0
  167. package/dist/schema/state/sqlite/mod.d.ts.map +1 -0
  168. package/dist/schema/state/sqlite/mod.js +41 -0
  169. package/dist/schema/state/sqlite/mod.js.map +1 -0
  170. package/dist/schema/state/sqlite/query-builder/api.d.ts +294 -0
  171. package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -0
  172. package/dist/schema/state/sqlite/query-builder/api.js +6 -0
  173. package/dist/schema/state/sqlite/query-builder/api.js.map +1 -0
  174. package/dist/schema/state/sqlite/query-builder/astToSql.d.ts +7 -0
  175. package/dist/schema/state/sqlite/query-builder/astToSql.d.ts.map +1 -0
  176. package/dist/schema/state/sqlite/query-builder/astToSql.js +190 -0
  177. package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -0
  178. package/dist/schema/state/sqlite/query-builder/impl.d.ts +7 -0
  179. package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -0
  180. package/dist/schema/state/sqlite/query-builder/impl.js +286 -0
  181. package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -0
  182. package/dist/schema/state/sqlite/query-builder/impl.test.d.ts +87 -0
  183. package/dist/schema/state/sqlite/query-builder/impl.test.d.ts.map +1 -0
  184. package/dist/schema/state/sqlite/query-builder/impl.test.js +563 -0
  185. package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -0
  186. package/dist/{query-builder → schema/state/sqlite/query-builder}/mod.d.ts +7 -0
  187. package/dist/schema/state/sqlite/query-builder/mod.d.ts.map +1 -0
  188. package/dist/{query-builder → schema/state/sqlite/query-builder}/mod.js +7 -0
  189. package/dist/schema/state/sqlite/query-builder/mod.js.map +1 -0
  190. package/dist/schema/state/sqlite/schema-helpers.d.ts.map +1 -0
  191. package/dist/schema/{schema-helpers.js → state/sqlite/schema-helpers.js} +1 -1
  192. package/dist/schema/state/sqlite/schema-helpers.js.map +1 -0
  193. package/dist/schema/state/sqlite/system-tables.d.ts +574 -0
  194. package/dist/schema/state/sqlite/system-tables.d.ts.map +1 -0
  195. package/dist/schema/state/sqlite/system-tables.js +88 -0
  196. package/dist/schema/state/sqlite/system-tables.js.map +1 -0
  197. package/dist/schema/state/sqlite/table-def.d.ts +84 -0
  198. package/dist/schema/state/sqlite/table-def.d.ts.map +1 -0
  199. package/dist/schema/state/sqlite/table-def.js +36 -0
  200. package/dist/schema/state/sqlite/table-def.js.map +1 -0
  201. package/dist/schema-management/common.d.ts +7 -7
  202. package/dist/schema-management/common.d.ts.map +1 -1
  203. package/dist/schema-management/common.js.map +1 -1
  204. package/dist/schema-management/migrations.d.ts +6 -6
  205. package/dist/schema-management/migrations.d.ts.map +1 -1
  206. package/dist/schema-management/migrations.js +27 -18
  207. package/dist/schema-management/migrations.js.map +1 -1
  208. package/dist/schema-management/validate-schema.d.ts +8 -0
  209. package/dist/schema-management/validate-schema.d.ts.map +1 -0
  210. package/dist/schema-management/validate-schema.js +39 -0
  211. package/dist/schema-management/validate-schema.js.map +1 -0
  212. package/dist/sql-queries/misc.d.ts.map +1 -1
  213. package/dist/sql-queries/sql-queries.d.ts +1 -1
  214. package/dist/sql-queries/sql-queries.d.ts.map +1 -1
  215. package/dist/sql-queries/sql-queries.js.map +1 -1
  216. package/dist/sql-queries/sql-query-builder.d.ts +1 -1
  217. package/dist/sql-queries/sql-query-builder.d.ts.map +1 -1
  218. package/dist/sql-queries/sql-query-builder.js.map +1 -1
  219. package/dist/sql-queries/types.d.ts +2 -1
  220. package/dist/sql-queries/types.d.ts.map +1 -1
  221. package/dist/sql-queries/types.js.map +1 -1
  222. package/dist/sync/ClientSessionSyncProcessor.d.ts +40 -19
  223. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  224. package/dist/sync/ClientSessionSyncProcessor.js +149 -73
  225. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  226. package/dist/sync/next/compact-events.d.ts.map +1 -1
  227. package/dist/sync/next/compact-events.js +38 -35
  228. package/dist/sync/next/compact-events.js.map +1 -1
  229. package/dist/sync/next/facts.d.ts +21 -21
  230. package/dist/sync/next/facts.d.ts.map +1 -1
  231. package/dist/sync/next/facts.js +11 -11
  232. package/dist/sync/next/facts.js.map +1 -1
  233. package/dist/sync/next/history-dag-common.d.ts +9 -7
  234. package/dist/sync/next/history-dag-common.d.ts.map +1 -1
  235. package/dist/sync/next/history-dag-common.js +10 -5
  236. package/dist/sync/next/history-dag-common.js.map +1 -1
  237. package/dist/sync/next/history-dag.d.ts +0 -2
  238. package/dist/sync/next/history-dag.d.ts.map +1 -1
  239. package/dist/sync/next/history-dag.js +16 -14
  240. package/dist/sync/next/history-dag.js.map +1 -1
  241. package/dist/sync/next/rebase-events.d.ts +10 -8
  242. package/dist/sync/next/rebase-events.d.ts.map +1 -1
  243. package/dist/sync/next/rebase-events.js +18 -10
  244. package/dist/sync/next/rebase-events.js.map +1 -1
  245. package/dist/sync/next/test/compact-events.calculator.test.js +39 -34
  246. package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
  247. package/dist/sync/next/test/compact-events.test.js +77 -77
  248. package/dist/sync/next/test/compact-events.test.js.map +1 -1
  249. package/dist/sync/next/test/{mutation-fixtures.d.ts → event-fixtures.d.ts} +35 -25
  250. package/dist/sync/next/test/event-fixtures.d.ts.map +1 -0
  251. package/dist/sync/next/test/{mutation-fixtures.js → event-fixtures.js} +81 -38
  252. package/dist/sync/next/test/event-fixtures.js.map +1 -0
  253. package/dist/sync/next/test/mod.d.ts +1 -1
  254. package/dist/sync/next/test/mod.d.ts.map +1 -1
  255. package/dist/sync/next/test/mod.js +1 -1
  256. package/dist/sync/next/test/mod.js.map +1 -1
  257. package/dist/sync/sync.d.ts +46 -21
  258. package/dist/sync/sync.d.ts.map +1 -1
  259. package/dist/sync/sync.js +10 -6
  260. package/dist/sync/sync.js.map +1 -1
  261. package/dist/sync/syncstate.d.ts +193 -84
  262. package/dist/sync/syncstate.d.ts.map +1 -1
  263. package/dist/sync/syncstate.js +305 -151
  264. package/dist/sync/syncstate.js.map +1 -1
  265. package/dist/sync/syncstate.test.js +267 -303
  266. package/dist/sync/syncstate.test.js.map +1 -1
  267. package/dist/sync/validate-push-payload.d.ts +2 -2
  268. package/dist/sync/validate-push-payload.d.ts.map +1 -1
  269. package/dist/sync/validate-push-payload.js +4 -4
  270. package/dist/sync/validate-push-payload.js.map +1 -1
  271. package/dist/util.d.ts +2 -2
  272. package/dist/util.d.ts.map +1 -1
  273. package/dist/version.d.ts +2 -2
  274. package/dist/version.d.ts.map +1 -1
  275. package/dist/version.js +2 -2
  276. package/dist/version.js.map +1 -1
  277. package/package.json +10 -4
  278. package/src/__tests__/fixture.ts +36 -15
  279. package/src/adapter-types.ts +107 -68
  280. package/src/debug-info.ts +1 -0
  281. package/src/devtools/devtools-messages-client-session.ts +142 -0
  282. package/src/devtools/devtools-messages-common.ts +115 -0
  283. package/src/devtools/devtools-messages-leader.ts +191 -0
  284. package/src/devtools/devtools-messages.ts +3 -246
  285. package/src/devtools/devtools-sessioninfo.ts +101 -0
  286. package/src/devtools/mod.ts +59 -0
  287. package/src/index.ts +7 -9
  288. package/src/leader-thread/LeaderSyncProcessor.ts +664 -394
  289. package/src/leader-thread/connection.ts +54 -9
  290. package/src/leader-thread/eventlog.ts +199 -0
  291. package/src/leader-thread/leader-worker-devtools.ts +227 -104
  292. package/src/leader-thread/make-leader-thread-layer.ts +121 -72
  293. package/src/leader-thread/materialize-event.ts +173 -0
  294. package/src/leader-thread/mod.ts +1 -1
  295. package/src/leader-thread/recreate-db.ts +33 -38
  296. package/src/leader-thread/shutdown-channel.ts +2 -4
  297. package/src/leader-thread/types.ts +84 -46
  298. package/src/make-client-session.ts +136 -0
  299. package/src/materializer-helper.ts +138 -0
  300. package/src/otel.ts +8 -0
  301. package/src/rematerialize-from-eventlog.ts +117 -0
  302. package/src/schema/EventDef.ts +227 -0
  303. package/src/schema/EventSequenceNumber.test.ts +12 -0
  304. package/src/schema/EventSequenceNumber.ts +121 -0
  305. package/src/schema/LiveStoreEvent.ts +240 -0
  306. package/src/schema/events.ts +1 -0
  307. package/src/schema/mod.ts +8 -6
  308. package/src/schema/schema.ts +88 -84
  309. package/src/schema/state/mod.ts +2 -0
  310. package/src/schema/state/sqlite/client-document-def.test.ts +238 -0
  311. package/src/schema/state/sqlite/client-document-def.ts +444 -0
  312. package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +142 -0
  313. package/src/schema/state/sqlite/db-schema/ast/validate.ts +13 -0
  314. package/src/schema/state/sqlite/db-schema/dsl/__snapshots__/field-defs.test.ts.snap +206 -0
  315. package/src/schema/state/sqlite/db-schema/dsl/field-defs.test.ts +35 -0
  316. package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +242 -0
  317. package/src/schema/state/sqlite/db-schema/dsl/mod.ts +222 -0
  318. package/src/schema/state/sqlite/db-schema/hash.ts +14 -0
  319. package/src/schema/state/sqlite/db-schema/mod.ts +2 -0
  320. package/src/schema/state/sqlite/mod.ts +73 -0
  321. package/src/schema/state/sqlite/query-builder/api.ts +440 -0
  322. package/src/schema/state/sqlite/query-builder/astToSql.ts +232 -0
  323. package/src/schema/state/sqlite/query-builder/impl.test.ts +617 -0
  324. package/src/schema/state/sqlite/query-builder/impl.ts +351 -0
  325. package/src/{query-builder → schema/state/sqlite/query-builder}/mod.ts +7 -0
  326. package/src/schema/{schema-helpers.ts → state/sqlite/schema-helpers.ts} +1 -1
  327. package/src/schema/state/sqlite/system-tables.ts +117 -0
  328. package/src/schema/state/sqlite/table-def.ts +197 -0
  329. package/src/schema-management/common.ts +7 -7
  330. package/src/schema-management/migrations.ts +37 -31
  331. package/src/schema-management/validate-schema.ts +61 -0
  332. package/src/sql-queries/sql-queries.ts +1 -1
  333. package/src/sql-queries/sql-query-builder.ts +1 -2
  334. package/src/sql-queries/types.ts +3 -1
  335. package/src/sync/ClientSessionSyncProcessor.ts +218 -94
  336. package/src/sync/next/compact-events.ts +38 -35
  337. package/src/sync/next/facts.ts +43 -41
  338. package/src/sync/next/history-dag-common.ts +17 -10
  339. package/src/sync/next/history-dag.ts +16 -17
  340. package/src/sync/next/rebase-events.ts +29 -17
  341. package/src/sync/next/test/compact-events.calculator.test.ts +46 -46
  342. package/src/sync/next/test/compact-events.test.ts +79 -79
  343. package/src/sync/next/test/event-fixtures.ts +226 -0
  344. package/src/sync/next/test/mod.ts +1 -1
  345. package/src/sync/sync.ts +46 -21
  346. package/src/sync/syncstate.test.ts +312 -345
  347. package/src/sync/syncstate.ts +414 -224
  348. package/src/sync/validate-push-payload.ts +6 -6
  349. package/src/version.ts +2 -2
  350. package/dist/derived-mutations.d.ts +0 -109
  351. package/dist/derived-mutations.d.ts.map +0 -1
  352. package/dist/derived-mutations.js +0 -54
  353. package/dist/derived-mutations.js.map +0 -1
  354. package/dist/derived-mutations.test.d.ts +0 -2
  355. package/dist/derived-mutations.test.d.ts.map +0 -1
  356. package/dist/derived-mutations.test.js +0 -93
  357. package/dist/derived-mutations.test.js.map +0 -1
  358. package/dist/devtools/devtools-bridge.d.ts +0 -13
  359. package/dist/devtools/devtools-bridge.d.ts.map +0 -1
  360. package/dist/devtools/devtools-bridge.js +0 -2
  361. package/dist/devtools/devtools-bridge.js.map +0 -1
  362. package/dist/devtools/devtools-window-message.d.ts +0 -29
  363. package/dist/devtools/devtools-window-message.d.ts.map +0 -1
  364. package/dist/devtools/devtools-window-message.js +0 -33
  365. package/dist/devtools/devtools-window-message.js.map +0 -1
  366. package/dist/devtools/index.d.ts +0 -42
  367. package/dist/devtools/index.d.ts.map +0 -1
  368. package/dist/devtools/index.js +0 -48
  369. package/dist/devtools/index.js.map +0 -1
  370. package/dist/init-singleton-tables.d.ts +0 -4
  371. package/dist/init-singleton-tables.d.ts.map +0 -1
  372. package/dist/init-singleton-tables.js +0 -16
  373. package/dist/init-singleton-tables.js.map +0 -1
  374. package/dist/leader-thread/apply-mutation.d.ts +0 -11
  375. package/dist/leader-thread/apply-mutation.d.ts.map +0 -1
  376. package/dist/leader-thread/apply-mutation.js +0 -107
  377. package/dist/leader-thread/apply-mutation.js.map +0 -1
  378. package/dist/leader-thread/leader-sync-processor.d.ts +0 -47
  379. package/dist/leader-thread/leader-sync-processor.d.ts.map +0 -1
  380. package/dist/leader-thread/leader-sync-processor.js +0 -430
  381. package/dist/leader-thread/leader-sync-processor.js.map +0 -1
  382. package/dist/leader-thread/mutationlog.d.ts +0 -10
  383. package/dist/leader-thread/mutationlog.d.ts.map +0 -1
  384. package/dist/leader-thread/mutationlog.js +0 -28
  385. package/dist/leader-thread/mutationlog.js.map +0 -1
  386. package/dist/leader-thread/pull-queue-set.d.ts +0 -7
  387. package/dist/leader-thread/pull-queue-set.d.ts.map +0 -1
  388. package/dist/leader-thread/pull-queue-set.js +0 -39
  389. package/dist/leader-thread/pull-queue-set.js.map +0 -1
  390. package/dist/mutation.d.ts +0 -20
  391. package/dist/mutation.d.ts.map +0 -1
  392. package/dist/mutation.js +0 -57
  393. package/dist/mutation.js.map +0 -1
  394. package/dist/query-builder/api.d.ts +0 -190
  395. package/dist/query-builder/api.d.ts.map +0 -1
  396. package/dist/query-builder/api.js +0 -8
  397. package/dist/query-builder/api.js.map +0 -1
  398. package/dist/query-builder/impl.d.ts +0 -12
  399. package/dist/query-builder/impl.d.ts.map +0 -1
  400. package/dist/query-builder/impl.js +0 -244
  401. package/dist/query-builder/impl.js.map +0 -1
  402. package/dist/query-builder/impl.test.d.ts +0 -2
  403. package/dist/query-builder/impl.test.d.ts.map +0 -1
  404. package/dist/query-builder/impl.test.js +0 -212
  405. package/dist/query-builder/impl.test.js.map +0 -1
  406. package/dist/query-builder/mod.d.ts.map +0 -1
  407. package/dist/query-builder/mod.js.map +0 -1
  408. package/dist/query-info.d.ts +0 -38
  409. package/dist/query-info.d.ts.map +0 -1
  410. package/dist/query-info.js +0 -7
  411. package/dist/query-info.js.map +0 -1
  412. package/dist/rehydrate-from-mutationlog.d.ts +0 -14
  413. package/dist/rehydrate-from-mutationlog.d.ts.map +0 -1
  414. package/dist/rehydrate-from-mutationlog.js +0 -66
  415. package/dist/rehydrate-from-mutationlog.js.map +0 -1
  416. package/dist/schema/EventId.d.ts +0 -39
  417. package/dist/schema/EventId.d.ts.map +0 -1
  418. package/dist/schema/EventId.js +0 -38
  419. package/dist/schema/EventId.js.map +0 -1
  420. package/dist/schema/EventId.test.d.ts +0 -2
  421. package/dist/schema/EventId.test.d.ts.map +0 -1
  422. package/dist/schema/EventId.test.js +0 -11
  423. package/dist/schema/EventId.test.js.map +0 -1
  424. package/dist/schema/MutationEvent.d.ts +0 -167
  425. package/dist/schema/MutationEvent.d.ts.map +0 -1
  426. package/dist/schema/MutationEvent.js +0 -72
  427. package/dist/schema/MutationEvent.js.map +0 -1
  428. package/dist/schema/MutationEvent.test.d.ts +0 -2
  429. package/dist/schema/MutationEvent.test.d.ts.map +0 -1
  430. package/dist/schema/MutationEvent.test.js +0 -2
  431. package/dist/schema/MutationEvent.test.js.map +0 -1
  432. package/dist/schema/mutations.d.ts +0 -107
  433. package/dist/schema/mutations.d.ts.map +0 -1
  434. package/dist/schema/mutations.js +0 -42
  435. package/dist/schema/mutations.js.map +0 -1
  436. package/dist/schema/schema-helpers.d.ts.map +0 -1
  437. package/dist/schema/schema-helpers.js.map +0 -1
  438. package/dist/schema/system-tables.d.ts +0 -399
  439. package/dist/schema/system-tables.d.ts.map +0 -1
  440. package/dist/schema/system-tables.js +0 -59
  441. package/dist/schema/system-tables.js.map +0 -1
  442. package/dist/schema/table-def.d.ts +0 -156
  443. package/dist/schema/table-def.d.ts.map +0 -1
  444. package/dist/schema/table-def.js +0 -79
  445. package/dist/schema/table-def.js.map +0 -1
  446. package/dist/schema-management/validate-mutation-defs.d.ts +0 -8
  447. package/dist/schema-management/validate-mutation-defs.d.ts.map +0 -1
  448. package/dist/schema-management/validate-mutation-defs.js +0 -39
  449. package/dist/schema-management/validate-mutation-defs.js.map +0 -1
  450. package/dist/sync/client-session-sync-processor.d.ts +0 -45
  451. package/dist/sync/client-session-sync-processor.d.ts.map +0 -1
  452. package/dist/sync/client-session-sync-processor.js +0 -131
  453. package/dist/sync/client-session-sync-processor.js.map +0 -1
  454. package/dist/sync/next/test/mutation-fixtures.d.ts.map +0 -1
  455. package/dist/sync/next/test/mutation-fixtures.js.map +0 -1
  456. package/src/derived-mutations.test.ts +0 -101
  457. package/src/derived-mutations.ts +0 -170
  458. package/src/devtools/devtools-bridge.ts +0 -14
  459. package/src/devtools/devtools-window-message.ts +0 -27
  460. package/src/devtools/index.ts +0 -48
  461. package/src/init-singleton-tables.ts +0 -24
  462. package/src/leader-thread/apply-mutation.ts +0 -161
  463. package/src/leader-thread/mutationlog.ts +0 -46
  464. package/src/leader-thread/pull-queue-set.ts +0 -58
  465. package/src/mutation.ts +0 -91
  466. package/src/query-builder/api.ts +0 -289
  467. package/src/query-builder/impl.test.ts +0 -239
  468. package/src/query-builder/impl.ts +0 -285
  469. package/src/query-info.ts +0 -78
  470. package/src/rehydrate-from-mutationlog.ts +0 -119
  471. package/src/schema/EventId.test.ts +0 -12
  472. package/src/schema/EventId.ts +0 -60
  473. package/src/schema/MutationEvent.ts +0 -185
  474. package/src/schema/mutations.ts +0 -192
  475. package/src/schema/system-tables.ts +0 -105
  476. package/src/schema/table-def.ts +0 -343
  477. package/src/schema-management/validate-mutation-defs.ts +0 -63
  478. package/src/sync/next/test/mutation-fixtures.ts +0 -224
  479. package/tsconfig.json +0 -11
  480. /package/dist/schema/{schema-helpers.d.ts → state/sqlite/schema-helpers.d.ts} +0 -0
@@ -1,270 +1,406 @@
1
- import { isNotUndefined, shouldNeverHappen, TRACE_VERBOSE } from '@livestore/utils';
2
- import { BucketQueue, Deferred, Effect, Exit, FiberHandle, Option, OtelTracer, ReadonlyArray, Schema, Stream, Subscribable, SubscriptionRef, } from '@livestore/utils/effect';
1
+ import { casesHandled, isNotUndefined, LS_DEV, shouldNeverHappen, TRACE_VERBOSE } from '@livestore/utils';
2
+ import { BucketQueue, Deferred, Effect, Exit, FiberHandle, OtelTracer, Queue, ReadonlyArray, Stream, Subscribable, SubscriptionRef, } from '@livestore/utils/effect';
3
3
  import { UnexpectedError } from '../adapter-types.js';
4
- import { EventId, MUTATION_LOG_META_TABLE, MutationEvent, mutationLogMetaTable, SESSION_CHANGESET_META_TABLE, } from '../schema/mod.js';
5
- import { updateRows } from '../sql-queries/index.js';
6
- import { InvalidPushError } from '../sync/sync.js';
4
+ import { EventSequenceNumber, getEventDef, LiveStoreEvent, SystemTables } from '../schema/mod.js';
5
+ import { LeaderAheadError } from '../sync/sync.js';
7
6
  import * as SyncState from '../sync/syncstate.js';
8
7
  import { sql } from '../util.js';
9
- import { makeApplyMutation } from './apply-mutation.js';
10
- import { execSql } from './connection.js';
11
- import { getBackendHeadFromDb, getLocalHeadFromDb, getMutationEventsSince, updateBackendHead } from './mutationlog.js';
8
+ import * as Eventlog from './eventlog.js';
9
+ import { rollback } from './materialize-event.js';
12
10
  import { LeaderThreadCtx } from './types.js';
13
11
  /**
14
- * The LeaderSyncProcessor manages synchronization of mutations between
12
+ * The LeaderSyncProcessor manages synchronization of events between
15
13
  * the local state and the sync backend, ensuring efficient and orderly processing.
16
14
  *
17
15
  * In the LeaderSyncProcessor, pulling always has precedence over pushing.
18
16
  *
19
17
  * Responsibilities:
20
- * - Queueing incoming local mutations in a localPushMailbox.
21
- * - Broadcasting mutations to client sessions via pull queues.
22
- * - Pushing mutations to the sync backend.
18
+ * - Queueing incoming local events in a localPushesQueue.
19
+ * - Broadcasting events to client sessions via pull queues.
20
+ * - Pushing events to the sync backend.
23
21
  *
24
22
  * Notes:
25
23
  *
26
24
  * local push processing:
27
- * - localPushMailbox:
25
+ * - localPushesQueue:
28
26
  * - Maintains events in ascending order.
29
27
  * - Uses `Deferred` objects to resolve/reject events based on application success.
30
- * - Processes events from the mailbox, applying mutations in batches.
28
+ * - Processes events from the queue, applying events in batches.
31
29
  * - Controlled by a `Latch` to manage execution flow.
32
30
  * - The latch closes on pull receipt and re-opens post-pull completion.
33
31
  * - Processes up to `maxBatchSize` events per cycle.
34
32
  *
33
+ * Currently we're advancing the db read model and eventlog in lockstep, but we could also decouple this in the future
34
+ *
35
+ * Tricky concurrency scenarios:
36
+ * - Queued local push batches becoming invalid due to a prior local push item being rejected.
37
+ * Solution: Introduce a generation number for local push batches which is used to filter out old batches items in case of rejection.
38
+ *
35
39
  */
36
- export const makeLeaderSyncProcessor = ({ schema, dbMissing, dbLog, initialBlockingSyncContext, }) => Effect.gen(function* () {
37
- const syncBackendQueue = yield* BucketQueue.make();
40
+ export const makeLeaderSyncProcessor = ({ schema, dbEventlogMissing, dbEventlog, dbState, dbStateMissing, initialBlockingSyncContext, onError, params, testing, }) => Effect.gen(function* () {
41
+ const syncBackendPushQueue = yield* BucketQueue.make();
42
+ const localPushBatchSize = params.localPushBatchSize ?? 10;
43
+ const backendPushBatchSize = params.backendPushBatchSize ?? 50;
38
44
  const syncStateSref = yield* SubscriptionRef.make(undefined);
39
- const isLocalEvent = (mutationEventEncoded) => {
40
- const mutationDef = schema.mutations.get(mutationEventEncoded.mutation);
41
- return mutationDef.options.localOnly;
45
+ const isClientEvent = (eventEncoded) => {
46
+ const { eventDef } = getEventDef(schema, eventEncoded.name);
47
+ return eventDef.options.clientOnly;
48
+ };
49
+ const connectedClientSessionPullQueues = yield* makePullQueueSet;
50
+ /**
51
+ * Tracks generations of queued local push events.
52
+ * If a local-push batch is rejected, all subsequent push queue items with the same generation are also rejected,
53
+ * even if they would be valid on their own.
54
+ */
55
+ // TODO get rid of this in favour of the `mergeGeneration` event sequence number field
56
+ const currentLocalPushGenerationRef = { current: 0 };
57
+ const mergeCounterRef = { current: dbStateMissing ? 0 : yield* getMergeCounterFromDb(dbState) };
58
+ const mergePayloads = new Map();
59
+ // This context depends on data from `boot`, we should find a better implementation to avoid this ref indirection.
60
+ const ctxRef = {
61
+ current: undefined,
42
62
  };
43
- const spanRef = { current: undefined };
44
63
  const localPushesQueue = yield* BucketQueue.make();
45
64
  const localPushesLatch = yield* Effect.makeLatch(true);
46
65
  const pullLatch = yield* Effect.makeLatch(true);
66
+ /**
67
+ * Additionally to the `syncStateSref` we also need the `pushHeadRef` in order to prevent old/duplicate
68
+ * events from being pushed in a scenario like this:
69
+ * - client session A pushes e1
70
+ * - leader sync processor takes a bit and hasn't yet taken e1 from the localPushesQueue
71
+ * - client session B also pushes e1 (which should be rejected)
72
+ *
73
+ * Thus the purpoe of the pushHeadRef is the guard the integrity of the local push queue
74
+ */
75
+ const pushHeadRef = { current: EventSequenceNumber.ROOT };
76
+ const advancePushHead = (eventNum) => {
77
+ pushHeadRef.current = EventSequenceNumber.max(pushHeadRef.current, eventNum);
78
+ };
79
+ // NOTE: New events are only pushed to sync backend after successful local push processing
47
80
  const push = (newEvents, options) => Effect.gen(function* () {
48
- // TODO validate batch
49
81
  if (newEvents.length === 0)
50
82
  return;
83
+ yield* validatePushBatch(newEvents, pushHeadRef.current);
84
+ advancePushHead(newEvents.at(-1).seqNum);
51
85
  const waitForProcessing = options?.waitForProcessing ?? false;
52
- const deferreds = waitForProcessing
53
- ? yield* Effect.forEach(newEvents, () => Deferred.make())
54
- : newEvents.map((_) => undefined);
55
- // TODO validate batch ordering
56
- const mappedEvents = newEvents.map((mutationEventEncoded, i) => new MutationEvent.EncodedWithMeta({
57
- ...mutationEventEncoded,
58
- meta: { deferred: deferreds[i] },
59
- }));
60
- yield* BucketQueue.offerAll(localPushesQueue, mappedEvents);
86
+ const generation = currentLocalPushGenerationRef.current;
61
87
  if (waitForProcessing) {
88
+ const deferreds = yield* Effect.forEach(newEvents, () => Deferred.make());
89
+ const items = newEvents.map((eventEncoded, i) => [eventEncoded, deferreds[i], generation]);
90
+ yield* BucketQueue.offerAll(localPushesQueue, items);
62
91
  yield* Effect.all(deferreds);
63
92
  }
64
- }).pipe(Effect.withSpan('@livestore/common:leader-thread:syncing:local-push', {
93
+ else {
94
+ const items = newEvents.map((eventEncoded) => [eventEncoded, undefined, generation]);
95
+ yield* BucketQueue.offerAll(localPushesQueue, items);
96
+ }
97
+ }).pipe(Effect.withSpan('@livestore/common:LeaderSyncProcessor:push', {
65
98
  attributes: {
66
99
  batchSize: newEvents.length,
67
100
  batch: TRACE_VERBOSE ? newEvents : undefined,
68
101
  },
69
- links: spanRef.current
70
- ? [{ _tag: 'SpanLink', span: OtelTracer.makeExternalSpan(spanRef.current.spanContext()), attributes: {} }]
71
- : undefined,
102
+ links: ctxRef.current?.span ? [{ _tag: 'SpanLink', span: ctxRef.current.span, attributes: {} }] : undefined,
72
103
  }));
73
- const pushPartial = (mutationEventEncoded_) => Effect.gen(function* () {
104
+ const pushPartial = ({ event: { name, args }, clientId, sessionId }) => Effect.gen(function* () {
74
105
  const syncState = yield* syncStateSref;
75
106
  if (syncState === undefined)
76
107
  return shouldNeverHappen('Not initialized');
77
- const mutationDef = schema.mutations.get(mutationEventEncoded_.mutation) ??
78
- shouldNeverHappen(`Unknown mutation: ${mutationEventEncoded_.mutation}`);
79
- const mutationEventEncoded = new MutationEvent.EncodedWithMeta({
80
- ...mutationEventEncoded_,
81
- ...EventId.nextPair(syncState.localHead, mutationDef.options.localOnly),
108
+ const { eventDef } = getEventDef(schema, name);
109
+ const eventEncoded = new LiveStoreEvent.EncodedWithMeta({
110
+ name,
111
+ args,
112
+ clientId,
113
+ sessionId,
114
+ ...EventSequenceNumber.nextPair(syncState.localHead, eventDef.options.clientOnly),
82
115
  });
83
- yield* push([mutationEventEncoded]);
84
- }).pipe(Effect.catchTag('InvalidPushError', Effect.orDie));
116
+ yield* push([eventEncoded]);
117
+ }).pipe(Effect.catchTag('LeaderAheadError', Effect.orDie));
85
118
  // Starts various background loops
86
- const boot = ({ dbReady }) => Effect.gen(function* () {
87
- const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.catchAll(() => Effect.succeed(undefined)));
88
- spanRef.current = span;
89
- const initialBackendHead = dbMissing ? EventId.ROOT.global : getBackendHeadFromDb(dbLog);
90
- const initialLocalHead = dbMissing ? EventId.ROOT : getLocalHeadFromDb(dbLog);
119
+ const boot = Effect.gen(function* () {
120
+ const span = yield* Effect.currentSpan.pipe(Effect.orDie);
121
+ const otelSpan = yield* OtelTracer.currentOtelSpan.pipe(Effect.catchAll(() => Effect.succeed(undefined)));
122
+ const { devtools, shutdownChannel } = yield* LeaderThreadCtx;
123
+ const runtime = yield* Effect.runtime();
124
+ ctxRef.current = {
125
+ otelSpan,
126
+ span,
127
+ devtoolsLatch: devtools.enabled ? devtools.syncBackendLatch : undefined,
128
+ runtime,
129
+ };
130
+ const initialLocalHead = dbEventlogMissing ? EventSequenceNumber.ROOT : Eventlog.getClientHeadFromDb(dbEventlog);
131
+ const initialBackendHead = dbEventlogMissing
132
+ ? EventSequenceNumber.ROOT.global
133
+ : Eventlog.getBackendHeadFromDb(dbEventlog);
91
134
  if (initialBackendHead > initialLocalHead.global) {
92
135
  return shouldNeverHappen(`During boot the backend head (${initialBackendHead}) should never be greater than the local head (${initialLocalHead.global})`);
93
136
  }
94
- const pendingMutationEvents = yield* getMutationEventsSince({
95
- global: initialBackendHead,
96
- local: EventId.localDefault,
97
- }).pipe(Effect.map(ReadonlyArray.map((_) => new MutationEvent.EncodedWithMeta(_))));
137
+ const pendingEvents = dbEventlogMissing
138
+ ? []
139
+ : yield* Eventlog.getEventsSince({ global: initialBackendHead, client: EventSequenceNumber.clientDefault });
98
140
  const initialSyncState = new SyncState.SyncState({
99
- pending: pendingMutationEvents,
100
- // On the leader we don't need a rollback tail beyond `pending` items
101
- rollbackTail: [],
102
- upstreamHead: { global: initialBackendHead, local: EventId.localDefault },
141
+ pending: pendingEvents,
142
+ upstreamHead: { global: initialBackendHead, client: EventSequenceNumber.clientDefault },
103
143
  localHead: initialLocalHead,
104
144
  });
105
145
  /** State transitions need to happen atomically, so we use a Ref to track the state */
106
146
  yield* SubscriptionRef.set(syncStateSref, initialSyncState);
107
147
  // Rehydrate sync queue
108
- if (pendingMutationEvents.length > 0) {
109
- const filteredBatch = pendingMutationEvents
110
- // Don't sync localOnly mutations
111
- .filter((mutationEventEncoded) => {
112
- const mutationDef = schema.mutations.get(mutationEventEncoded.mutation);
113
- return mutationDef.options.localOnly === false;
148
+ if (pendingEvents.length > 0) {
149
+ const globalPendingEvents = pendingEvents
150
+ // Don't sync clientOnly events
151
+ .filter((eventEncoded) => {
152
+ const { eventDef } = getEventDef(schema, eventEncoded.name);
153
+ return eventDef.options.clientOnly === false;
114
154
  });
115
- yield* BucketQueue.offerAll(syncBackendQueue, filteredBatch);
155
+ if (globalPendingEvents.length > 0) {
156
+ yield* BucketQueue.offerAll(syncBackendPushQueue, globalPendingEvents);
157
+ }
116
158
  }
159
+ const shutdownOnError = (cause) => Effect.gen(function* () {
160
+ if (onError === 'shutdown') {
161
+ yield* shutdownChannel.send(UnexpectedError.make({ cause }));
162
+ yield* Effect.die(cause);
163
+ }
164
+ });
117
165
  yield* backgroundApplyLocalPushes({
118
166
  localPushesLatch,
119
167
  localPushesQueue,
120
168
  pullLatch,
121
169
  syncStateSref,
122
- syncBackendQueue,
170
+ syncBackendPushQueue,
123
171
  schema,
124
- isLocalEvent,
125
- span,
126
- }).pipe(Effect.tapCauseLogPretty, Effect.forkScoped);
172
+ isClientEvent,
173
+ otelSpan,
174
+ currentLocalPushGenerationRef,
175
+ connectedClientSessionPullQueues,
176
+ mergeCounterRef,
177
+ mergePayloads,
178
+ localPushBatchSize,
179
+ testing: {
180
+ delay: testing?.delays?.localPushProcessing,
181
+ },
182
+ }).pipe(Effect.tapCauseLogPretty, Effect.catchAllCause(shutdownOnError), Effect.forkScoped);
127
183
  const backendPushingFiberHandle = yield* FiberHandle.make();
128
- yield* FiberHandle.run(backendPushingFiberHandle, backgroundBackendPushing({ dbReady, syncBackendQueue, span }).pipe(Effect.tapCauseLogPretty));
184
+ const backendPushingEffect = backgroundBackendPushing({
185
+ syncBackendPushQueue,
186
+ otelSpan,
187
+ devtoolsLatch: ctxRef.current?.devtoolsLatch,
188
+ backendPushBatchSize,
189
+ }).pipe(Effect.tapCauseLogPretty, Effect.catchAllCause(shutdownOnError));
190
+ yield* FiberHandle.run(backendPushingFiberHandle, backendPushingEffect);
129
191
  yield* backgroundBackendPulling({
130
- dbReady,
131
192
  initialBackendHead,
132
- isLocalEvent,
193
+ isClientEvent,
133
194
  restartBackendPushing: (filteredRebasedPending) => Effect.gen(function* () {
134
195
  // Stop current pushing fiber
135
196
  yield* FiberHandle.clear(backendPushingFiberHandle);
136
- // Reset the sync queue
137
- yield* BucketQueue.clear(syncBackendQueue);
138
- yield* BucketQueue.offerAll(syncBackendQueue, filteredRebasedPending);
197
+ // Reset the sync backend push queue
198
+ yield* BucketQueue.clear(syncBackendPushQueue);
199
+ yield* BucketQueue.offerAll(syncBackendPushQueue, filteredRebasedPending);
139
200
  // Restart pushing fiber
140
- yield* FiberHandle.run(backendPushingFiberHandle, backgroundBackendPushing({ dbReady, syncBackendQueue, span }).pipe(Effect.tapCauseLogPretty));
201
+ yield* FiberHandle.run(backendPushingFiberHandle, backendPushingEffect);
141
202
  }),
142
203
  syncStateSref,
143
204
  localPushesLatch,
144
205
  pullLatch,
145
- span,
206
+ otelSpan,
146
207
  initialBlockingSyncContext,
147
- }).pipe(Effect.tapCauseLogPretty, Effect.forkScoped);
148
- }).pipe(Effect.withSpanScoped('@livestore/common:leader-thread:syncing'));
208
+ devtoolsLatch: ctxRef.current?.devtoolsLatch,
209
+ connectedClientSessionPullQueues,
210
+ mergeCounterRef,
211
+ mergePayloads,
212
+ advancePushHead,
213
+ }).pipe(Effect.tapCauseLogPretty, Effect.catchAllCause(shutdownOnError), Effect.forkScoped);
214
+ return { initialLeaderHead: initialLocalHead };
215
+ }).pipe(Effect.withSpanScoped('@livestore/common:LeaderSyncProcessor:boot'));
216
+ const pull = ({ cursor }) => Effect.gen(function* () {
217
+ const queue = yield* pullQueue({ cursor });
218
+ return Stream.fromQueue(queue);
219
+ }).pipe(Stream.unwrapScoped);
220
+ const pullQueue = ({ cursor }) => {
221
+ const runtime = ctxRef.current?.runtime ?? shouldNeverHappen('Not initialized');
222
+ return Effect.gen(function* () {
223
+ const queue = yield* connectedClientSessionPullQueues.makeQueue;
224
+ const payloadsSinceCursor = Array.from(mergePayloads.entries())
225
+ .map(([mergeCounter, payload]) => ({ payload, mergeCounter }))
226
+ .filter(({ mergeCounter }) => mergeCounter > cursor.mergeCounter)
227
+ .toSorted((a, b) => a.mergeCounter - b.mergeCounter)
228
+ .map(({ payload, mergeCounter }) => {
229
+ if (payload._tag === 'upstream-advance') {
230
+ return {
231
+ payload: {
232
+ _tag: 'upstream-advance',
233
+ newEvents: ReadonlyArray.dropWhile(payload.newEvents, (eventEncoded) => EventSequenceNumber.isGreaterThanOrEqual(cursor.eventNum, eventEncoded.seqNum)),
234
+ },
235
+ mergeCounter,
236
+ };
237
+ }
238
+ else {
239
+ return { payload, mergeCounter };
240
+ }
241
+ });
242
+ yield* queue.offerAll(payloadsSinceCursor);
243
+ return queue;
244
+ }).pipe(Effect.provide(runtime));
245
+ };
246
+ const syncState = Subscribable.make({
247
+ get: Effect.gen(function* () {
248
+ const syncState = yield* syncStateSref;
249
+ if (syncState === undefined)
250
+ return shouldNeverHappen('Not initialized');
251
+ return syncState;
252
+ }),
253
+ changes: syncStateSref.changes.pipe(Stream.filter(isNotUndefined)),
254
+ });
149
255
  return {
256
+ pull,
257
+ pullQueue,
150
258
  push,
151
259
  pushPartial,
152
260
  boot,
153
- syncState: Subscribable.make({
154
- get: Effect.gen(function* () {
155
- const syncState = yield* syncStateSref;
156
- if (syncState === undefined)
157
- return shouldNeverHappen('Not initialized');
158
- return syncState;
159
- }),
160
- changes: syncStateSref.changes.pipe(Stream.filter(isNotUndefined)),
161
- }),
261
+ syncState,
262
+ getMergeCounter: () => mergeCounterRef.current,
162
263
  };
163
264
  });
164
- const backgroundApplyLocalPushes = ({ localPushesLatch, localPushesQueue, pullLatch, syncStateSref, syncBackendQueue, schema, isLocalEvent, span, }) => Effect.gen(function* () {
165
- const { connectedClientSessionPullQueues } = yield* LeaderThreadCtx;
166
- const applyMutationItems = yield* makeApplyMutationItems;
265
+ const backgroundApplyLocalPushes = ({ localPushesLatch, localPushesQueue, pullLatch, syncStateSref, syncBackendPushQueue, schema, isClientEvent, otelSpan, currentLocalPushGenerationRef, connectedClientSessionPullQueues, mergeCounterRef, mergePayloads, localPushBatchSize, testing, }) => Effect.gen(function* () {
167
266
  while (true) {
168
- // TODO make this configurable
169
- const newEvents = yield* BucketQueue.takeBetween(localPushesQueue, 1, 10);
267
+ if (testing.delay !== undefined) {
268
+ yield* testing.delay.pipe(Effect.withSpan('localPushProcessingDelay'));
269
+ }
270
+ const batchItems = yield* BucketQueue.takeBetween(localPushesQueue, 1, localPushBatchSize);
170
271
  // Wait for the backend pulling to finish
171
272
  yield* localPushesLatch.await;
172
- // Prevent the backend pulling from starting until this local push is finished
273
+ // Prevent backend pull processing until this local push is finished
173
274
  yield* pullLatch.close;
275
+ // Since the generation might have changed since enqueuing, we need to filter out items with older generation
276
+ // It's important that we filter after we got localPushesLatch, otherwise we might filter with the old generation
277
+ const filteredBatchItems = batchItems
278
+ .filter(([_1, _2, generation]) => generation === currentLocalPushGenerationRef.current)
279
+ .map(([eventEncoded, deferred]) => [eventEncoded, deferred]);
280
+ if (filteredBatchItems.length === 0) {
281
+ // console.log('dropping old-gen batch', currentLocalPushGenerationRef.current)
282
+ // Allow the backend pulling to start
283
+ yield* pullLatch.open;
284
+ continue;
285
+ }
286
+ const [newEvents, deferreds] = ReadonlyArray.unzip(filteredBatchItems);
174
287
  const syncState = yield* syncStateSref;
175
288
  if (syncState === undefined)
176
289
  return shouldNeverHappen('Not initialized');
177
- const updateResult = SyncState.updateSyncState({
290
+ const mergeResult = SyncState.merge({
178
291
  syncState,
179
292
  payload: { _tag: 'local-push', newEvents },
180
- isLocalEvent,
181
- isEqualEvent: MutationEvent.isEqualEncoded,
293
+ isClientEvent,
294
+ isEqualEvent: LiveStoreEvent.isEqualEncoded,
182
295
  });
183
- if (updateResult._tag === 'rebase') {
184
- return shouldNeverHappen('The leader thread should never have to rebase due to a local push');
185
- }
186
- else if (updateResult._tag === 'reject') {
187
- span?.addEvent('local-push:reject', {
188
- batchSize: newEvents.length,
189
- updateResult: TRACE_VERBOSE ? JSON.stringify(updateResult) : undefined,
190
- });
191
- const providedId = newEvents.at(0).id;
192
- const remainingEvents = yield* BucketQueue.takeAll(localPushesQueue);
193
- const allEvents = [...newEvents, ...remainingEvents];
194
- yield* Effect.forEach(allEvents, (mutationEventEncoded) => mutationEventEncoded.meta.deferred
195
- ? Deferred.fail(mutationEventEncoded.meta.deferred, InvalidPushError.make({
196
- // TODO improve error handling so it differentiates between a push being rejected
197
- // because of itself or because of another push
198
- reason: {
199
- _tag: 'LeaderAhead',
200
- minimumExpectedId: updateResult.expectedMinimumId,
201
- providedId,
202
- },
203
- }))
204
- : Effect.void);
205
- // Allow the backend pulling to start
206
- yield* pullLatch.open;
207
- // In this case we're skipping state update and down/upstream processing
208
- // We've cleared the local push queue and are now waiting for new local pushes / backend pulls
209
- continue;
296
+ const mergeCounter = yield* incrementMergeCounter(mergeCounterRef);
297
+ switch (mergeResult._tag) {
298
+ case 'unexpected-error': {
299
+ otelSpan?.addEvent(`[${mergeCounter}]:push:unexpected-error`, {
300
+ batchSize: newEvents.length,
301
+ newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
302
+ });
303
+ return yield* Effect.fail(mergeResult.cause);
304
+ }
305
+ case 'rebase': {
306
+ return shouldNeverHappen('The leader thread should never have to rebase due to a local push');
307
+ }
308
+ case 'reject': {
309
+ otelSpan?.addEvent(`[${mergeCounter}]:push:reject`, {
310
+ batchSize: newEvents.length,
311
+ mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
312
+ });
313
+ // TODO: how to test this?
314
+ currentLocalPushGenerationRef.current++;
315
+ const nextGeneration = currentLocalPushGenerationRef.current;
316
+ const providedNum = newEvents.at(0).seqNum;
317
+ // All subsequent pushes with same generation should be rejected as well
318
+ // We're also handling the case where the localPushQueue already contains events
319
+ // from the next generation which we preserve in the queue
320
+ const remainingEventsMatchingGeneration = yield* BucketQueue.takeSplitWhere(localPushesQueue, (item) => item[2] >= nextGeneration);
321
+ // TODO we still need to better understand and handle this scenario
322
+ if (LS_DEV && (yield* BucketQueue.size(localPushesQueue)) > 0) {
323
+ console.log('localPushesQueue is not empty', yield* BucketQueue.size(localPushesQueue));
324
+ debugger;
325
+ }
326
+ const allDeferredsToReject = [
327
+ ...deferreds,
328
+ ...remainingEventsMatchingGeneration.map(([_, deferred]) => deferred),
329
+ ].filter(isNotUndefined);
330
+ yield* Effect.forEach(allDeferredsToReject, (deferred) => Deferred.fail(deferred, LeaderAheadError.make({
331
+ minimumExpectedNum: mergeResult.expectedMinimumId,
332
+ providedNum,
333
+ // nextGeneration,
334
+ })));
335
+ // Allow the backend pulling to start
336
+ yield* pullLatch.open;
337
+ // In this case we're skipping state update and down/upstream processing
338
+ // We've cleared the local push queue and are now waiting for new local pushes / backend pulls
339
+ continue;
340
+ }
341
+ case 'advance': {
342
+ break;
343
+ }
344
+ default: {
345
+ casesHandled(mergeResult);
346
+ }
210
347
  }
211
- yield* SubscriptionRef.set(syncStateSref, updateResult.newSyncState);
348
+ yield* SubscriptionRef.set(syncStateSref, mergeResult.newSyncState);
212
349
  yield* connectedClientSessionPullQueues.offer({
213
- payload: { _tag: 'upstream-advance', newEvents: updateResult.newEvents },
214
- remaining: 0,
350
+ payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: mergeResult.newEvents }),
351
+ mergeCounter,
215
352
  });
216
- span?.addEvent('local-push', {
353
+ mergePayloads.set(mergeCounter, SyncState.PayloadUpstreamAdvance.make({ newEvents: mergeResult.newEvents }));
354
+ otelSpan?.addEvent(`[${mergeCounter}]:push:advance`, {
217
355
  batchSize: newEvents.length,
218
- updateResult: TRACE_VERBOSE ? JSON.stringify(updateResult) : undefined,
356
+ mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
219
357
  });
220
- // Don't sync localOnly mutations
221
- const filteredBatch = updateResult.newEvents.filter((mutationEventEncoded) => {
222
- const mutationDef = schema.mutations.get(mutationEventEncoded.mutation);
223
- return mutationDef.options.localOnly === false;
358
+ // Don't sync clientOnly events
359
+ const filteredBatch = mergeResult.newEvents.filter((eventEncoded) => {
360
+ const { eventDef } = getEventDef(schema, eventEncoded.name);
361
+ return eventDef.options.clientOnly === false;
224
362
  });
225
- yield* BucketQueue.offerAll(syncBackendQueue, filteredBatch);
226
- yield* applyMutationItems({ batchItems: newEvents });
363
+ yield* BucketQueue.offerAll(syncBackendPushQueue, filteredBatch);
364
+ yield* materializeEventsBatch({ batchItems: mergeResult.newEvents, deferreds });
227
365
  // Allow the backend pulling to start
228
366
  yield* pullLatch.open;
229
367
  }
230
368
  });
231
369
  // TODO how to handle errors gracefully
232
- const makeApplyMutationItems = Effect.gen(function* () {
233
- const leaderThreadCtx = yield* LeaderThreadCtx;
234
- const { db, dbLog } = leaderThreadCtx;
235
- const applyMutation = yield* makeApplyMutation;
236
- return ({ batchItems }) => Effect.gen(function* () {
237
- db.execute('BEGIN TRANSACTION', undefined); // Start the transaction
238
- dbLog.execute('BEGIN TRANSACTION', undefined); // Start the transaction
239
- yield* Effect.addFinalizer((exit) => Effect.gen(function* () {
240
- if (Exit.isSuccess(exit))
241
- return;
242
- // Rollback in case of an error
243
- db.execute('ROLLBACK', undefined);
244
- dbLog.execute('ROLLBACK', undefined);
245
- }));
246
- for (let i = 0; i < batchItems.length; i++) {
247
- const { meta, ...mutationEventEncoded } = batchItems[i];
248
- yield* applyMutation(mutationEventEncoded);
249
- if (meta?.deferred) {
250
- yield* Deferred.succeed(meta.deferred, void 0);
251
- }
370
+ const materializeEventsBatch = ({ batchItems, deferreds }) => Effect.gen(function* () {
371
+ const { dbState: db, dbEventlog, materializeEvent } = yield* LeaderThreadCtx;
372
+ // NOTE We always start a transaction to ensure consistency between db and eventlog (even for single-item batches)
373
+ db.execute('BEGIN TRANSACTION', undefined); // Start the transaction
374
+ dbEventlog.execute('BEGIN TRANSACTION', undefined); // Start the transaction
375
+ yield* Effect.addFinalizer((exit) => Effect.gen(function* () {
376
+ if (Exit.isSuccess(exit))
377
+ return;
378
+ // Rollback in case of an error
379
+ db.execute('ROLLBACK', undefined);
380
+ dbEventlog.execute('ROLLBACK', undefined);
381
+ }));
382
+ for (let i = 0; i < batchItems.length; i++) {
383
+ const { sessionChangeset } = yield* materializeEvent(batchItems[i]);
384
+ batchItems[i].meta.sessionChangeset = sessionChangeset;
385
+ if (deferreds?.[i] !== undefined) {
386
+ yield* Deferred.succeed(deferreds[i], void 0);
252
387
  }
253
- db.execute('COMMIT', undefined); // Commit the transaction
254
- dbLog.execute('COMMIT', undefined); // Commit the transaction
255
- }).pipe(Effect.uninterruptible, Effect.scoped, Effect.withSpan('@livestore/common:leader-thread:syncing:applyMutationItems', {
256
- attributes: { count: batchItems.length },
257
- }), Effect.tapCauseLogPretty, UnexpectedError.mapToUnexpectedError);
258
- });
259
- const backgroundBackendPulling = ({ dbReady, initialBackendHead, isLocalEvent, restartBackendPushing, span, syncStateSref, localPushesLatch, pullLatch, initialBlockingSyncContext, }) => Effect.gen(function* () {
260
- const { syncBackend, db, dbLog, connectedClientSessionPullQueues, schema } = yield* LeaderThreadCtx;
388
+ }
389
+ db.execute('COMMIT', undefined); // Commit the transaction
390
+ dbEventlog.execute('COMMIT', undefined); // Commit the transaction
391
+ }).pipe(Effect.uninterruptible, Effect.scoped, Effect.withSpan('@livestore/common:LeaderSyncProcessor:materializeEventItems', {
392
+ attributes: { batchSize: batchItems.length },
393
+ }), Effect.tapCauseLogPretty, UnexpectedError.mapToUnexpectedError);
394
+ const backgroundBackendPulling = ({ initialBackendHead, isClientEvent, restartBackendPushing, otelSpan, syncStateSref, localPushesLatch, pullLatch, devtoolsLatch, initialBlockingSyncContext, connectedClientSessionPullQueues, mergeCounterRef, mergePayloads, advancePushHead, }) => Effect.gen(function* () {
395
+ const { syncBackend, dbState: db, dbEventlog, schema } = yield* LeaderThreadCtx;
261
396
  if (syncBackend === undefined)
262
397
  return;
263
- const cursorInfo = yield* getCursorInfo(initialBackendHead);
264
- const applyMutationItems = yield* makeApplyMutationItems;
265
398
  const onNewPullChunk = (newEvents, remaining) => Effect.gen(function* () {
266
399
  if (newEvents.length === 0)
267
400
  return;
401
+ if (devtoolsLatch !== undefined) {
402
+ yield* devtoolsLatch.await;
403
+ }
268
404
  // Prevent more local pushes from being processed until this pull is finished
269
405
  yield* localPushesLatch.close;
270
406
  // Wait for pending local pushes to finish
@@ -272,62 +408,85 @@ const backgroundBackendPulling = ({ dbReady, initialBackendHead, isLocalEvent, r
272
408
  const syncState = yield* syncStateSref;
273
409
  if (syncState === undefined)
274
410
  return shouldNeverHappen('Not initialized');
275
- const trimRollbackUntil = newEvents.at(-1).id;
276
- const updateResult = SyncState.updateSyncState({
411
+ const mergeResult = SyncState.merge({
277
412
  syncState,
278
- payload: { _tag: 'upstream-advance', newEvents, trimRollbackUntil },
279
- isLocalEvent,
280
- isEqualEvent: MutationEvent.isEqualEncoded,
281
- ignoreLocalEvents: true,
413
+ payload: SyncState.PayloadUpstreamAdvance.make({ newEvents }),
414
+ isClientEvent,
415
+ isEqualEvent: LiveStoreEvent.isEqualEncoded,
416
+ ignoreClientEvents: true,
282
417
  });
283
- if (updateResult._tag === 'reject') {
418
+ const mergeCounter = yield* incrementMergeCounter(mergeCounterRef);
419
+ if (mergeResult._tag === 'reject') {
284
420
  return shouldNeverHappen('The leader thread should never reject upstream advances');
285
421
  }
286
- const newBackendHead = newEvents.at(-1).id;
287
- updateBackendHead(dbLog, newBackendHead);
288
- if (updateResult._tag === 'rebase') {
289
- span?.addEvent('backend-pull:rebase', {
422
+ else if (mergeResult._tag === 'unexpected-error') {
423
+ otelSpan?.addEvent(`[${mergeCounter}]:pull:unexpected-error`, {
290
424
  newEventsCount: newEvents.length,
291
425
  newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
292
- rollbackCount: updateResult.eventsToRollback.length,
293
- updateResult: TRACE_VERBOSE ? JSON.stringify(updateResult) : undefined,
294
426
  });
295
- const filteredRebasedPending = updateResult.newSyncState.pending.filter((mutationEvent) => {
296
- const mutationDef = schema.mutations.get(mutationEvent.mutation);
297
- return mutationDef.options.localOnly === false;
427
+ return yield* Effect.fail(mergeResult.cause);
428
+ }
429
+ const newBackendHead = newEvents.at(-1).seqNum;
430
+ Eventlog.updateBackendHead(dbEventlog, newBackendHead);
431
+ if (mergeResult._tag === 'rebase') {
432
+ otelSpan?.addEvent(`[${mergeCounter}]:pull:rebase`, {
433
+ newEventsCount: newEvents.length,
434
+ newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
435
+ rollbackCount: mergeResult.rollbackEvents.length,
436
+ mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
437
+ });
438
+ const globalRebasedPendingEvents = mergeResult.newSyncState.pending.filter((event) => {
439
+ const { eventDef } = getEventDef(schema, event.name);
440
+ return eventDef.options.clientOnly === false;
298
441
  });
299
- yield* restartBackendPushing(filteredRebasedPending);
300
- if (updateResult.eventsToRollback.length > 0) {
301
- yield* rollback({ db, dbLog, eventIdsToRollback: updateResult.eventsToRollback.map((_) => _.id) });
442
+ yield* restartBackendPushing(globalRebasedPendingEvents);
443
+ if (mergeResult.rollbackEvents.length > 0) {
444
+ yield* rollback({
445
+ dbState: db,
446
+ dbEventlog,
447
+ eventNumsToRollback: mergeResult.rollbackEvents.map((_) => _.seqNum),
448
+ });
302
449
  }
303
450
  yield* connectedClientSessionPullQueues.offer({
304
- payload: {
305
- _tag: 'upstream-rebase',
306
- newEvents: updateResult.newEvents,
307
- rollbackUntil: updateResult.eventsToRollback.at(0).id,
308
- trimRollbackUntil,
309
- },
310
- remaining,
451
+ payload: SyncState.PayloadUpstreamRebase.make({
452
+ newEvents: mergeResult.newEvents,
453
+ rollbackEvents: mergeResult.rollbackEvents,
454
+ }),
455
+ mergeCounter,
311
456
  });
457
+ mergePayloads.set(mergeCounter, SyncState.PayloadUpstreamRebase.make({
458
+ newEvents: mergeResult.newEvents,
459
+ rollbackEvents: mergeResult.rollbackEvents,
460
+ }));
312
461
  }
313
462
  else {
314
- span?.addEvent('backend-pull:advance', {
463
+ otelSpan?.addEvent(`[${mergeCounter}]:pull:advance`, {
315
464
  newEventsCount: newEvents.length,
316
- updateResult: TRACE_VERBOSE ? JSON.stringify(updateResult) : undefined,
465
+ mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
317
466
  });
318
467
  yield* connectedClientSessionPullQueues.offer({
319
- payload: { _tag: 'upstream-advance', newEvents: updateResult.newEvents, trimRollbackUntil },
320
- remaining,
468
+ payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: mergeResult.newEvents }),
469
+ mergeCounter,
321
470
  });
471
+ mergePayloads.set(mergeCounter, SyncState.PayloadUpstreamAdvance.make({ newEvents: mergeResult.newEvents }));
472
+ if (mergeResult.confirmedEvents.length > 0) {
473
+ // `mergeResult.confirmedEvents` don't contain the correct sync metadata, so we need to use
474
+ // `newEvents` instead which we filter via `mergeResult.confirmedEvents`
475
+ const confirmedNewEvents = newEvents.filter((event) => mergeResult.confirmedEvents.some((confirmedEvent) => EventSequenceNumber.isEqual(event.seqNum, confirmedEvent.seqNum)));
476
+ yield* Eventlog.updateSyncMetadata(confirmedNewEvents);
477
+ }
322
478
  }
479
+ // Removes the changeset rows which are no longer needed as we'll never have to rollback beyond this point
323
480
  trimChangesetRows(db, newBackendHead);
324
- yield* applyMutationItems({ batchItems: updateResult.newEvents });
325
- yield* SubscriptionRef.set(syncStateSref, updateResult.newSyncState);
481
+ advancePushHead(mergeResult.newSyncState.localHead);
482
+ yield* materializeEventsBatch({ batchItems: mergeResult.newEvents, deferreds: undefined });
483
+ yield* SubscriptionRef.set(syncStateSref, mergeResult.newSyncState);
484
+ // Allow local pushes to be processed again
326
485
  if (remaining === 0) {
327
- // Allow local pushes to be processed again
328
486
  yield* localPushesLatch.open;
329
487
  }
330
488
  });
489
+ const cursorInfo = yield* Eventlog.getSyncBackendCursorInfo(initialBackendHead);
331
490
  yield* syncBackend.pull(cursorInfo).pipe(
332
491
  // TODO only take from queue while connected
333
492
  Stream.tap(({ batch, remaining }) => Effect.gen(function* () {
@@ -337,85 +496,100 @@ const backgroundBackendPulling = ({ dbReady, initialBackendHead, isLocalEvent, r
337
496
  // batch: TRACE_VERBOSE ? batch : undefined,
338
497
  // },
339
498
  // })
340
- // Wait for the db to be initially created
341
- yield* dbReady;
342
- // NOTE we only want to take process mutations when the sync backend is connected
499
+ // NOTE we only want to take process events when the sync backend is connected
343
500
  // (e.g. needed for simulating being offline)
344
501
  // TODO remove when there's a better way to handle this in stream above
345
502
  yield* SubscriptionRef.waitUntil(syncBackend.isConnected, (isConnected) => isConnected === true);
346
- yield* onNewPullChunk(batch.map((_) => MutationEvent.EncodedWithMeta.fromGlobal(_.mutationEventEncoded)), remaining);
503
+ yield* onNewPullChunk(batch.map((_) => LiveStoreEvent.EncodedWithMeta.fromGlobal(_.eventEncoded, _.metadata)), remaining);
347
504
  yield* initialBlockingSyncContext.update({ processed: batch.length, remaining });
348
505
  })), Stream.runDrain, Effect.interruptible);
349
- }).pipe(Effect.withSpan('@livestore/common:leader-thread:syncing:backend-pulling'));
350
- const rollback = ({ db, dbLog, eventIdsToRollback, }) => Effect.gen(function* () {
351
- const rollbackEvents = db
352
- .select(sql `SELECT * FROM ${SESSION_CHANGESET_META_TABLE} WHERE (idGlobal, idLocal) IN (${eventIdsToRollback.map((id) => `(${id.global}, ${id.local})`).join(', ')})`)
353
- .map((_) => ({ id: { global: _.idGlobal, local: _.idLocal }, changeset: _.changeset, debug: _.debug }))
354
- .toSorted((a, b) => EventId.compare(a.id, b.id));
355
- // Apply changesets in reverse order
356
- for (let i = rollbackEvents.length - 1; i >= 0; i--) {
357
- const { changeset } = rollbackEvents[i];
358
- if (changeset !== null) {
359
- db.makeChangeset(changeset).invert().apply();
360
- }
361
- }
362
- // Delete the changeset rows
363
- db.execute(sql `DELETE FROM ${SESSION_CHANGESET_META_TABLE} WHERE (idGlobal, idLocal) IN (${eventIdsToRollback.map((id) => `(${id.global}, ${id.local})`).join(', ')})`);
364
- // Delete the mutation log rows
365
- dbLog.execute(sql `DELETE FROM ${MUTATION_LOG_META_TABLE} WHERE (idGlobal, idLocal) IN (${eventIdsToRollback.map((id) => `(${id.global}, ${id.local})`).join(', ')})`);
366
- }).pipe(Effect.withSpan('@livestore/common:leader-thread:syncing:rollback', {
367
- attributes: { count: eventIdsToRollback.length },
368
- }));
369
- const getCursorInfo = (remoteHead) => Effect.gen(function* () {
370
- const { dbLog } = yield* LeaderThreadCtx;
371
- if (remoteHead === EventId.ROOT.global)
372
- return Option.none();
373
- const MutationlogQuerySchema = Schema.Struct({
374
- syncMetadataJson: Schema.parseJson(Schema.Option(Schema.JsonValue)),
375
- }).pipe(Schema.pluck('syncMetadataJson'), Schema.Array, Schema.head);
376
- const syncMetadataOption = yield* Effect.sync(() => dbLog.select(sql `SELECT syncMetadataJson FROM ${MUTATION_LOG_META_TABLE} WHERE idGlobal = ${remoteHead} ORDER BY idLocal ASC LIMIT 1`)).pipe(Effect.andThen(Schema.decode(MutationlogQuerySchema)), Effect.map(Option.flatten), Effect.orDie);
377
- return Option.some({
378
- cursor: { global: remoteHead, local: EventId.localDefault },
379
- metadata: syncMetadataOption,
380
- });
381
- }).pipe(Effect.withSpan('@livestore/common:leader-thread:syncing:getCursorInfo', { attributes: { remoteHead } }));
382
- const backgroundBackendPushing = ({ dbReady, syncBackendQueue, span, }) => Effect.gen(function* () {
383
- const { syncBackend, dbLog } = yield* LeaderThreadCtx;
506
+ }).pipe(Effect.withSpan('@livestore/common:LeaderSyncProcessor:backend-pulling'));
507
+ const backgroundBackendPushing = ({ syncBackendPushQueue, otelSpan, devtoolsLatch, backendPushBatchSize, }) => Effect.gen(function* () {
508
+ const { syncBackend } = yield* LeaderThreadCtx;
384
509
  if (syncBackend === undefined)
385
510
  return;
386
- yield* dbReady;
387
511
  while (true) {
388
512
  yield* SubscriptionRef.waitUntil(syncBackend.isConnected, (isConnected) => isConnected === true);
389
- // TODO make batch size configurable
390
- const queueItems = yield* BucketQueue.takeBetween(syncBackendQueue, 1, 50);
513
+ const queueItems = yield* BucketQueue.takeBetween(syncBackendPushQueue, 1, backendPushBatchSize);
391
514
  yield* SubscriptionRef.waitUntil(syncBackend.isConnected, (isConnected) => isConnected === true);
392
- span?.addEvent('backend-push', {
515
+ if (devtoolsLatch !== undefined) {
516
+ yield* devtoolsLatch.await;
517
+ }
518
+ otelSpan?.addEvent('backend-push', {
393
519
  batchSize: queueItems.length,
394
520
  batch: TRACE_VERBOSE ? JSON.stringify(queueItems) : undefined,
395
521
  });
396
522
  // TODO handle push errors (should only happen during concurrent pull+push)
397
523
  const pushResult = yield* syncBackend.push(queueItems.map((_) => _.toGlobal())).pipe(Effect.either);
398
524
  if (pushResult._tag === 'Left') {
399
- span?.addEvent('backend-push-error', { error: pushResult.left.toString() });
525
+ if (LS_DEV) {
526
+ yield* Effect.logDebug('handled backend-push-error', { error: pushResult.left.toString() });
527
+ }
528
+ otelSpan?.addEvent('backend-push-error', { error: pushResult.left.toString() });
400
529
  // wait for interrupt caused by background pulling which will then restart pushing
401
530
  return yield* Effect.never;
402
531
  }
403
- const { metadata } = pushResult.right;
404
- // TODO try to do this in a single query
405
- for (let i = 0; i < queueItems.length; i++) {
406
- const mutationEventEncoded = queueItems[i];
407
- yield* execSql(dbLog, ...updateRows({
408
- tableName: MUTATION_LOG_META_TABLE,
409
- columns: mutationLogMetaTable.sqliteDef.columns,
410
- where: { idGlobal: mutationEventEncoded.id.global, idLocal: mutationEventEncoded.id.local },
411
- updateValues: { syncMetadataJson: metadata[i] },
412
- }));
413
- }
414
532
  }
415
- }).pipe(Effect.interruptible, Effect.withSpan('@livestore/common:leader-thread:syncing:backend-pushing'));
533
+ }).pipe(Effect.interruptible, Effect.withSpan('@livestore/common:LeaderSyncProcessor:backend-pushing'));
416
534
  const trimChangesetRows = (db, newHead) => {
417
535
  // Since we're using the session changeset rows to query for the current head,
418
536
  // we're keeping at least one row for the current head, and thus are using `<` instead of `<=`
419
- db.execute(sql `DELETE FROM ${SESSION_CHANGESET_META_TABLE} WHERE idGlobal < ${newHead.global}`);
537
+ db.execute(sql `DELETE FROM ${SystemTables.SESSION_CHANGESET_META_TABLE} WHERE seqNumGlobal < ${newHead.global}`);
420
538
  };
539
+ const makePullQueueSet = Effect.gen(function* () {
540
+ const set = new Set();
541
+ yield* Effect.addFinalizer(() => Effect.gen(function* () {
542
+ for (const queue of set) {
543
+ yield* Queue.shutdown(queue);
544
+ }
545
+ set.clear();
546
+ }));
547
+ const makeQueue = Effect.gen(function* () {
548
+ const queue = yield* Queue.unbounded().pipe(Effect.acquireRelease(Queue.shutdown));
549
+ yield* Effect.addFinalizer(() => Effect.sync(() => set.delete(queue)));
550
+ set.add(queue);
551
+ return queue;
552
+ });
553
+ const offer = (item) => Effect.gen(function* () {
554
+ // Short-circuit if the payload is an empty upstream advance
555
+ if (item.payload._tag === 'upstream-advance' && item.payload.newEvents.length === 0) {
556
+ return;
557
+ }
558
+ for (const queue of set) {
559
+ yield* Queue.offer(queue, item);
560
+ }
561
+ });
562
+ return {
563
+ makeQueue,
564
+ offer,
565
+ };
566
+ });
567
+ const incrementMergeCounter = (mergeCounterRef) => Effect.gen(function* () {
568
+ const { dbState } = yield* LeaderThreadCtx;
569
+ mergeCounterRef.current++;
570
+ dbState.execute(sql `INSERT OR REPLACE INTO ${SystemTables.LEADER_MERGE_COUNTER_TABLE} (id, mergeCounter) VALUES (0, ${mergeCounterRef.current})`);
571
+ return mergeCounterRef.current;
572
+ });
573
+ const getMergeCounterFromDb = (dbState) => Effect.gen(function* () {
574
+ const result = dbState.select(sql `SELECT mergeCounter FROM ${SystemTables.LEADER_MERGE_COUNTER_TABLE} WHERE id = 0`);
575
+ return result[0]?.mergeCounter ?? 0;
576
+ });
577
+ const validatePushBatch = (batch, pushHead) => Effect.gen(function* () {
578
+ if (batch.length === 0) {
579
+ return;
580
+ }
581
+ // Make sure batch is monotonically increasing
582
+ for (let i = 1; i < batch.length; i++) {
583
+ if (EventSequenceNumber.isGreaterThanOrEqual(batch[i - 1].seqNum, batch[i].seqNum)) {
584
+ shouldNeverHappen(`Events must be ordered in monotonically ascending order by eventNum. Received: [${batch.map((e) => EventSequenceNumber.toString(e.seqNum)).join(', ')}]`);
585
+ }
586
+ }
587
+ // Make sure smallest sequence number is > pushHead
588
+ if (EventSequenceNumber.isGreaterThanOrEqual(pushHead, batch[0].seqNum)) {
589
+ return yield* LeaderAheadError.make({
590
+ minimumExpectedNum: pushHead,
591
+ providedNum: batch[0].seqNum,
592
+ });
593
+ }
594
+ });
421
595
  //# sourceMappingURL=LeaderSyncProcessor.js.map