@livestore/common 0.4.0-dev.2 → 0.4.0-dev.20

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 (449) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/ClientSessionLeaderThreadProxy.d.ts +17 -12
  3. package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
  4. package/dist/ClientSessionLeaderThreadProxy.js.map +1 -1
  5. package/dist/adapter-types.d.ts +14 -6
  6. package/dist/adapter-types.d.ts.map +1 -1
  7. package/dist/adapter-types.js.map +1 -1
  8. package/dist/debug-info.d.ts.map +1 -1
  9. package/dist/debug-info.js +33 -6
  10. package/dist/debug-info.js.map +1 -1
  11. package/dist/devtools/devtools-messages-client-session.d.ts +28 -23
  12. package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -1
  13. package/dist/devtools/devtools-messages-client-session.js +2 -2
  14. package/dist/devtools/devtools-messages-client-session.js.map +1 -1
  15. package/dist/devtools/devtools-messages-common.d.ts +7 -14
  16. package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
  17. package/dist/devtools/devtools-messages-common.js +1 -6
  18. package/dist/devtools/devtools-messages-common.js.map +1 -1
  19. package/dist/devtools/devtools-messages-leader.d.ts +36 -29
  20. package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
  21. package/dist/devtools/devtools-messages-leader.js +8 -8
  22. package/dist/devtools/devtools-messages-leader.js.map +1 -1
  23. package/dist/devtools/devtools-sessioninfo.d.ts +14 -2
  24. package/dist/devtools/devtools-sessioninfo.d.ts.map +1 -1
  25. package/dist/devtools/devtools-sessioninfo.js +7 -4
  26. package/dist/devtools/devtools-sessioninfo.js.map +1 -1
  27. package/dist/devtools/mod.d.ts +13 -2
  28. package/dist/devtools/mod.d.ts.map +1 -1
  29. package/dist/devtools/mod.js +10 -3
  30. package/dist/devtools/mod.js.map +1 -1
  31. package/dist/errors.d.ts +52 -10
  32. package/dist/errors.d.ts.map +1 -1
  33. package/dist/errors.js +25 -6
  34. package/dist/errors.js.map +1 -1
  35. package/dist/index.d.ts +2 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +2 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/leader-thread/LeaderSyncProcessor.d.ts +8 -4
  40. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
  41. package/dist/leader-thread/LeaderSyncProcessor.js +156 -73
  42. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  43. package/dist/leader-thread/eventlog.d.ts +15 -21
  44. package/dist/leader-thread/eventlog.d.ts.map +1 -1
  45. package/dist/leader-thread/eventlog.js +18 -18
  46. package/dist/leader-thread/eventlog.js.map +1 -1
  47. package/dist/leader-thread/leader-worker-devtools.d.ts +2 -2
  48. package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
  49. package/dist/leader-thread/leader-worker-devtools.js +30 -42
  50. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  51. package/dist/leader-thread/make-leader-thread-layer.d.ts +6 -6
  52. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  53. package/dist/leader-thread/make-leader-thread-layer.js +79 -27
  54. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  55. package/dist/leader-thread/make-leader-thread-layer.test.d.ts +2 -0
  56. package/dist/leader-thread/make-leader-thread-layer.test.d.ts.map +1 -0
  57. package/dist/leader-thread/make-leader-thread-layer.test.js +32 -0
  58. package/dist/leader-thread/make-leader-thread-layer.test.js.map +1 -0
  59. package/dist/leader-thread/materialize-event.d.ts +3 -3
  60. package/dist/leader-thread/materialize-event.d.ts.map +1 -1
  61. package/dist/leader-thread/materialize-event.js +25 -11
  62. package/dist/leader-thread/materialize-event.js.map +1 -1
  63. package/dist/leader-thread/recreate-db.d.ts +2 -3
  64. package/dist/leader-thread/recreate-db.d.ts.map +1 -1
  65. package/dist/leader-thread/recreate-db.js +5 -5
  66. package/dist/leader-thread/recreate-db.js.map +1 -1
  67. package/dist/leader-thread/shutdown-channel.d.ts +2 -2
  68. package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
  69. package/dist/leader-thread/shutdown-channel.js +2 -2
  70. package/dist/leader-thread/shutdown-channel.js.map +1 -1
  71. package/dist/leader-thread/types.d.ts +21 -19
  72. package/dist/leader-thread/types.d.ts.map +1 -1
  73. package/dist/leader-thread/types.js.map +1 -1
  74. package/dist/logging.d.ts +40 -0
  75. package/dist/logging.d.ts.map +1 -0
  76. package/dist/logging.js +33 -0
  77. package/dist/logging.js.map +1 -0
  78. package/dist/make-client-session.d.ts +5 -3
  79. package/dist/make-client-session.d.ts.map +1 -1
  80. package/dist/make-client-session.js +5 -2
  81. package/dist/make-client-session.js.map +1 -1
  82. package/dist/materializer-helper.d.ts +6 -6
  83. package/dist/materializer-helper.d.ts.map +1 -1
  84. package/dist/materializer-helper.js +20 -4
  85. package/dist/materializer-helper.js.map +1 -1
  86. package/dist/rematerialize-from-eventlog.d.ts +2 -2
  87. package/dist/rematerialize-from-eventlog.d.ts.map +1 -1
  88. package/dist/rematerialize-from-eventlog.js +29 -20
  89. package/dist/rematerialize-from-eventlog.js.map +1 -1
  90. package/dist/schema/EventDef/define.d.ts +147 -0
  91. package/dist/schema/EventDef/define.d.ts.map +1 -0
  92. package/dist/schema/EventDef/define.js +139 -0
  93. package/dist/schema/EventDef/define.js.map +1 -0
  94. package/dist/schema/EventDef/event-def.d.ts +106 -0
  95. package/dist/schema/EventDef/event-def.d.ts.map +1 -0
  96. package/dist/schema/EventDef/event-def.js +2 -0
  97. package/dist/schema/EventDef/event-def.js.map +1 -0
  98. package/dist/schema/EventDef/facts.d.ts +118 -0
  99. package/dist/schema/EventDef/facts.d.ts.map +1 -0
  100. package/dist/schema/EventDef/facts.js +53 -0
  101. package/dist/schema/EventDef/facts.js.map +1 -0
  102. package/dist/schema/EventDef/materializer.d.ts +155 -0
  103. package/dist/schema/EventDef/materializer.d.ts.map +1 -0
  104. package/dist/schema/EventDef/materializer.js +83 -0
  105. package/dist/schema/EventDef/materializer.js.map +1 -0
  106. package/dist/schema/EventDef/mod.d.ts +5 -0
  107. package/dist/schema/EventDef/mod.d.ts.map +1 -0
  108. package/dist/schema/EventDef/mod.js +5 -0
  109. package/dist/schema/EventDef/mod.js.map +1 -0
  110. package/dist/schema/EventSequenceNumber/client.d.ts +136 -0
  111. package/dist/schema/EventSequenceNumber/client.d.ts.map +1 -0
  112. package/dist/schema/EventSequenceNumber/client.js +193 -0
  113. package/dist/schema/EventSequenceNumber/client.js.map +1 -0
  114. package/dist/schema/EventSequenceNumber/global.d.ts +15 -0
  115. package/dist/schema/EventSequenceNumber/global.d.ts.map +1 -0
  116. package/dist/schema/EventSequenceNumber/global.js +14 -0
  117. package/dist/schema/EventSequenceNumber/global.js.map +1 -0
  118. package/dist/schema/EventSequenceNumber/mod.d.ts +37 -0
  119. package/dist/schema/EventSequenceNumber/mod.d.ts.map +1 -0
  120. package/dist/schema/EventSequenceNumber/mod.js +37 -0
  121. package/dist/schema/EventSequenceNumber/mod.js.map +1 -0
  122. package/dist/schema/EventSequenceNumber.test.js +43 -43
  123. package/dist/schema/EventSequenceNumber.test.js.map +1 -1
  124. package/dist/schema/{LiveStoreEvent.d.ts → LiveStoreEvent/client.d.ts} +89 -106
  125. package/dist/schema/LiveStoreEvent/client.d.ts.map +1 -0
  126. package/dist/schema/{LiveStoreEvent.js → LiveStoreEvent/client.js} +74 -58
  127. package/dist/schema/LiveStoreEvent/client.js.map +1 -0
  128. package/dist/schema/LiveStoreEvent/for-event-def.d.ts +52 -0
  129. package/dist/schema/LiveStoreEvent/for-event-def.d.ts.map +1 -0
  130. package/dist/schema/LiveStoreEvent/for-event-def.js +2 -0
  131. package/dist/schema/LiveStoreEvent/for-event-def.js.map +1 -0
  132. package/dist/schema/LiveStoreEvent/global.d.ts +36 -0
  133. package/dist/schema/LiveStoreEvent/global.d.ts.map +1 -0
  134. package/dist/schema/LiveStoreEvent/global.js +31 -0
  135. package/dist/schema/LiveStoreEvent/global.js.map +1 -0
  136. package/dist/schema/LiveStoreEvent/input.d.ts +46 -0
  137. package/dist/schema/LiveStoreEvent/input.d.ts.map +1 -0
  138. package/dist/schema/LiveStoreEvent/input.js +26 -0
  139. package/dist/schema/LiveStoreEvent/input.js.map +1 -0
  140. package/dist/schema/LiveStoreEvent/mod.d.ts +5 -0
  141. package/dist/schema/LiveStoreEvent/mod.d.ts.map +1 -0
  142. package/dist/schema/LiveStoreEvent/mod.js +5 -0
  143. package/dist/schema/LiveStoreEvent/mod.js.map +1 -0
  144. package/dist/schema/events.d.ts +1 -1
  145. package/dist/schema/events.d.ts.map +1 -1
  146. package/dist/schema/events.js +1 -1
  147. package/dist/schema/events.js.map +1 -1
  148. package/dist/schema/mod.d.ts +6 -4
  149. package/dist/schema/mod.d.ts.map +1 -1
  150. package/dist/schema/mod.js +5 -4
  151. package/dist/schema/mod.js.map +1 -1
  152. package/dist/schema/schema.d.ts +16 -1
  153. package/dist/schema/schema.d.ts.map +1 -1
  154. package/dist/schema/schema.js +27 -2
  155. package/dist/schema/schema.js.map +1 -1
  156. package/dist/schema/state/sqlite/client-document-def.d.ts +36 -6
  157. package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
  158. package/dist/schema/state/sqlite/client-document-def.js +97 -6
  159. package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
  160. package/dist/schema/state/sqlite/client-document-def.test.js +16 -0
  161. package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -1
  162. package/dist/schema/state/sqlite/column-annotations.d.ts.map +1 -1
  163. package/dist/schema/state/sqlite/column-annotations.js +14 -6
  164. package/dist/schema/state/sqlite/column-annotations.js.map +1 -1
  165. package/dist/schema/state/sqlite/column-annotations.test.js +1 -1
  166. package/dist/schema/state/sqlite/column-annotations.test.js.map +1 -1
  167. package/dist/schema/state/sqlite/column-def.js +69 -22
  168. package/dist/schema/state/sqlite/column-def.js.map +1 -1
  169. package/dist/schema/state/sqlite/column-def.test.js +46 -7
  170. package/dist/schema/state/sqlite/column-def.test.js.map +1 -1
  171. package/dist/schema/state/sqlite/column-spec.d.ts.map +1 -1
  172. package/dist/schema/state/sqlite/column-spec.js +30 -12
  173. package/dist/schema/state/sqlite/column-spec.js.map +1 -1
  174. package/dist/schema/state/sqlite/column-spec.test.js +23 -14
  175. package/dist/schema/state/sqlite/column-spec.test.js.map +1 -1
  176. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +2 -1
  177. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -1
  178. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +23 -6
  179. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -1
  180. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts +14 -8
  181. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts.map +1 -1
  182. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +5 -3
  183. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -1
  184. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -1
  185. package/dist/schema/state/sqlite/db-schema/dsl/mod.js +2 -1
  186. package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
  187. package/dist/schema/state/sqlite/mod.d.ts +3 -3
  188. package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
  189. package/dist/schema/state/sqlite/mod.js +3 -3
  190. package/dist/schema/state/sqlite/mod.js.map +1 -1
  191. package/dist/schema/state/sqlite/query-builder/api.d.ts +17 -10
  192. package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
  193. package/dist/schema/state/sqlite/query-builder/astToSql.d.ts.map +1 -1
  194. package/dist/schema/state/sqlite/query-builder/astToSql.js +22 -15
  195. package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -1
  196. package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
  197. package/dist/schema/state/sqlite/query-builder/impl.js +6 -3
  198. package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
  199. package/dist/schema/state/sqlite/query-builder/impl.test.js +252 -88
  200. package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
  201. package/dist/schema/state/sqlite/schema-helpers.d.ts +2 -2
  202. package/dist/schema/state/sqlite/schema-helpers.d.ts.map +1 -1
  203. package/dist/schema/state/sqlite/schema-helpers.js +22 -12
  204. package/dist/schema/state/sqlite/schema-helpers.js.map +1 -1
  205. package/dist/schema/state/sqlite/schema-helpers.test.d.ts +2 -0
  206. package/dist/schema/state/sqlite/schema-helpers.test.d.ts.map +1 -0
  207. package/dist/schema/state/sqlite/schema-helpers.test.js +36 -0
  208. package/dist/schema/state/sqlite/schema-helpers.test.js.map +1 -0
  209. package/dist/schema/state/sqlite/{system-tables.d.ts → system-tables/eventlog-tables.d.ts} +63 -456
  210. package/dist/schema/state/sqlite/system-tables/eventlog-tables.d.ts.map +1 -0
  211. package/dist/schema/state/sqlite/system-tables/eventlog-tables.js +54 -0
  212. package/dist/schema/state/sqlite/system-tables/eventlog-tables.js.map +1 -0
  213. package/dist/schema/state/sqlite/system-tables/mod.d.ts +3 -0
  214. package/dist/schema/state/sqlite/system-tables/mod.d.ts.map +1 -0
  215. package/dist/schema/state/sqlite/system-tables/mod.js +3 -0
  216. package/dist/schema/state/sqlite/system-tables/mod.js.map +1 -0
  217. package/dist/schema/state/sqlite/system-tables/state-tables.d.ts +456 -0
  218. package/dist/schema/state/sqlite/system-tables/state-tables.d.ts.map +1 -0
  219. package/dist/schema/state/sqlite/system-tables/state-tables.js +55 -0
  220. package/dist/schema/state/sqlite/system-tables/state-tables.js.map +1 -0
  221. package/dist/schema/state/sqlite/table-def.d.ts +4 -4
  222. package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
  223. package/dist/schema/state/sqlite/table-def.js +2 -2
  224. package/dist/schema/state/sqlite/table-def.js.map +1 -1
  225. package/dist/schema/state/sqlite/table-def.test.js +80 -0
  226. package/dist/schema/state/sqlite/table-def.test.js.map +1 -1
  227. package/dist/schema/unknown-events.d.ts +47 -0
  228. package/dist/schema/unknown-events.d.ts.map +1 -0
  229. package/dist/schema/unknown-events.js +69 -0
  230. package/dist/schema/unknown-events.js.map +1 -0
  231. package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.d.ts +2 -0
  232. package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.d.ts.map +1 -0
  233. package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js +73 -0
  234. package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js.map +1 -0
  235. package/dist/schema-management/migrations.d.ts +32 -2
  236. package/dist/schema-management/migrations.d.ts.map +1 -1
  237. package/dist/schema-management/migrations.js +37 -5
  238. package/dist/schema-management/migrations.js.map +1 -1
  239. package/dist/schema-management/validate-schema.d.ts +3 -3
  240. package/dist/schema-management/validate-schema.d.ts.map +1 -1
  241. package/dist/schema-management/validate-schema.js +2 -2
  242. package/dist/schema-management/validate-schema.js.map +1 -1
  243. package/dist/sql-queries/sql-queries.d.ts.map +1 -1
  244. package/dist/sql-queries/sql-queries.js +11 -1
  245. package/dist/sql-queries/sql-queries.js.map +1 -1
  246. package/dist/sql-queries/sql-query-builder.d.ts.map +1 -1
  247. package/dist/sql-queries/sql-query-builder.js +2 -1
  248. package/dist/sql-queries/sql-query-builder.js.map +1 -1
  249. package/dist/sqlite-types.d.ts +3 -3
  250. package/dist/sqlite-types.d.ts.map +1 -1
  251. package/dist/sync/ClientSessionSyncProcessor.d.ts +11 -13
  252. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  253. package/dist/sync/ClientSessionSyncProcessor.js +45 -42
  254. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  255. package/dist/sync/errors.d.ts +66 -0
  256. package/dist/sync/errors.d.ts.map +1 -0
  257. package/dist/sync/errors.js +36 -0
  258. package/dist/sync/errors.js.map +1 -0
  259. package/dist/sync/index.d.ts +3 -0
  260. package/dist/sync/index.d.ts.map +1 -1
  261. package/dist/sync/index.js +3 -0
  262. package/dist/sync/index.js.map +1 -1
  263. package/dist/sync/mock-sync-backend.d.ts +23 -0
  264. package/dist/sync/mock-sync-backend.d.ts.map +1 -0
  265. package/dist/sync/mock-sync-backend.js +114 -0
  266. package/dist/sync/mock-sync-backend.js.map +1 -0
  267. package/dist/sync/next/compact-events.d.ts.map +1 -1
  268. package/dist/sync/next/compact-events.js +6 -7
  269. package/dist/sync/next/compact-events.js.map +1 -1
  270. package/dist/sync/next/facts.d.ts +5 -5
  271. package/dist/sync/next/facts.d.ts.map +1 -1
  272. package/dist/sync/next/facts.js +1 -2
  273. package/dist/sync/next/facts.js.map +1 -1
  274. package/dist/sync/next/history-dag-common.d.ts +54 -15
  275. package/dist/sync/next/history-dag-common.d.ts.map +1 -1
  276. package/dist/sync/next/history-dag-common.js +198 -9
  277. package/dist/sync/next/history-dag-common.js.map +1 -1
  278. package/dist/sync/next/history-dag.d.ts.map +1 -1
  279. package/dist/sync/next/history-dag.js +10 -8
  280. package/dist/sync/next/history-dag.js.map +1 -1
  281. package/dist/sync/next/rebase-events.d.ts +5 -5
  282. package/dist/sync/next/rebase-events.d.ts.map +1 -1
  283. package/dist/sync/next/rebase-events.js +5 -5
  284. package/dist/sync/next/rebase-events.js.map +1 -1
  285. package/dist/sync/next/test/event-fixtures.d.ts +2 -2
  286. package/dist/sync/next/test/event-fixtures.d.ts.map +1 -1
  287. package/dist/sync/next/test/event-fixtures.js +9 -9
  288. package/dist/sync/next/test/event-fixtures.js.map +1 -1
  289. package/dist/sync/sync-backend-kv.d.ts +7 -0
  290. package/dist/sync/sync-backend-kv.d.ts.map +1 -0
  291. package/dist/sync/sync-backend-kv.js +18 -0
  292. package/dist/sync/sync-backend-kv.js.map +1 -0
  293. package/dist/sync/sync-backend.d.ts +105 -0
  294. package/dist/sync/sync-backend.d.ts.map +1 -0
  295. package/dist/sync/sync-backend.js +61 -0
  296. package/dist/sync/sync-backend.js.map +1 -0
  297. package/dist/sync/sync.d.ts +9 -86
  298. package/dist/sync/sync.d.ts.map +1 -1
  299. package/dist/sync/sync.js +2 -27
  300. package/dist/sync/sync.js.map +1 -1
  301. package/dist/sync/syncstate.d.ts +57 -44
  302. package/dist/sync/syncstate.d.ts.map +1 -1
  303. package/dist/sync/syncstate.js +50 -45
  304. package/dist/sync/syncstate.js.map +1 -1
  305. package/dist/sync/syncstate.test.js +83 -46
  306. package/dist/sync/syncstate.test.js.map +1 -1
  307. package/dist/sync/transport-chunking.d.ts +36 -0
  308. package/dist/sync/transport-chunking.d.ts.map +1 -0
  309. package/dist/sync/transport-chunking.js +56 -0
  310. package/dist/sync/transport-chunking.js.map +1 -0
  311. package/dist/sync/validate-push-payload.d.ts +2 -2
  312. package/dist/sync/validate-push-payload.d.ts.map +1 -1
  313. package/dist/sync/validate-push-payload.js +6 -6
  314. package/dist/sync/validate-push-payload.js.map +1 -1
  315. package/dist/testing/event-factory.d.ts +68 -0
  316. package/dist/testing/event-factory.d.ts.map +1 -0
  317. package/dist/testing/event-factory.js +78 -0
  318. package/dist/testing/event-factory.js.map +1 -0
  319. package/dist/testing/mod.d.ts +2 -0
  320. package/dist/testing/mod.d.ts.map +1 -0
  321. package/dist/testing/mod.js +2 -0
  322. package/dist/testing/mod.js.map +1 -0
  323. package/dist/version.d.ts +16 -6
  324. package/dist/version.d.ts.map +1 -1
  325. package/dist/version.js +16 -6
  326. package/dist/version.js.map +1 -1
  327. package/package.json +7 -8
  328. package/src/ClientSessionLeaderThreadProxy.ts +17 -12
  329. package/src/adapter-types.ts +18 -6
  330. package/src/debug-info.ts +37 -6
  331. package/src/devtools/devtools-messages-client-session.ts +2 -2
  332. package/src/devtools/devtools-messages-common.ts +1 -8
  333. package/src/devtools/devtools-messages-leader.ts +8 -8
  334. package/src/devtools/devtools-sessioninfo.ts +8 -5
  335. package/src/devtools/mod.ts +11 -2
  336. package/src/errors.ts +38 -11
  337. package/src/index.ts +2 -1
  338. package/src/leader-thread/LeaderSyncProcessor.ts +242 -103
  339. package/src/leader-thread/eventlog.ts +33 -34
  340. package/src/leader-thread/leader-worker-devtools.ts +50 -54
  341. package/src/leader-thread/make-leader-thread-layer.test.ts +44 -0
  342. package/src/leader-thread/make-leader-thread-layer.ts +156 -37
  343. package/src/leader-thread/materialize-event.ts +37 -12
  344. package/src/leader-thread/recreate-db.ts +15 -7
  345. package/src/leader-thread/shutdown-channel.ts +16 -2
  346. package/src/leader-thread/types.ts +21 -19
  347. package/src/logging.ts +62 -0
  348. package/src/make-client-session.ts +9 -3
  349. package/src/materializer-helper.ts +27 -10
  350. package/src/rematerialize-from-eventlog.ts +37 -27
  351. package/src/schema/EventDef/define.ts +201 -0
  352. package/src/schema/EventDef/event-def.ts +120 -0
  353. package/src/schema/EventDef/facts.ts +135 -0
  354. package/src/schema/EventDef/materializer.ts +172 -0
  355. package/src/schema/EventDef/mod.ts +4 -0
  356. package/src/schema/EventSequenceNumber/client.ts +257 -0
  357. package/src/schema/EventSequenceNumber/global.ts +19 -0
  358. package/src/schema/EventSequenceNumber/mod.ts +37 -0
  359. package/src/schema/EventSequenceNumber.test.ts +70 -52
  360. package/src/schema/LiveStoreEvent/client.ts +221 -0
  361. package/src/schema/LiveStoreEvent/for-event-def.ts +60 -0
  362. package/src/schema/LiveStoreEvent/global.ts +45 -0
  363. package/src/schema/LiveStoreEvent/input.ts +63 -0
  364. package/src/schema/LiveStoreEvent/mod.ts +4 -0
  365. package/src/schema/events.ts +1 -1
  366. package/src/schema/mod.ts +6 -4
  367. package/src/schema/schema.ts +39 -3
  368. package/src/schema/state/sqlite/client-document-def.test.ts +19 -2
  369. package/src/schema/state/sqlite/client-document-def.ts +120 -8
  370. package/src/schema/state/sqlite/column-annotations.test.ts +1 -1
  371. package/src/schema/state/sqlite/column-annotations.ts +16 -6
  372. package/src/schema/state/sqlite/column-def.test.ts +60 -7
  373. package/src/schema/state/sqlite/column-def.ts +88 -21
  374. package/src/schema/state/sqlite/column-spec.test.ts +29 -16
  375. package/src/schema/state/sqlite/column-spec.ts +36 -11
  376. package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +26 -6
  377. package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +29 -12
  378. package/src/schema/state/sqlite/db-schema/dsl/mod.ts +2 -1
  379. package/src/schema/state/sqlite/mod.ts +4 -3
  380. package/src/schema/state/sqlite/query-builder/api.ts +19 -10
  381. package/src/schema/state/sqlite/query-builder/astToSql.ts +23 -14
  382. package/src/schema/state/sqlite/query-builder/impl.test.ts +305 -92
  383. package/src/schema/state/sqlite/query-builder/impl.ts +8 -3
  384. package/src/schema/state/sqlite/schema-helpers.test.ts +44 -0
  385. package/src/schema/state/sqlite/schema-helpers.ts +28 -20
  386. package/src/schema/state/sqlite/system-tables/eventlog-tables.ts +64 -0
  387. package/src/schema/state/sqlite/system-tables/mod.ts +2 -0
  388. package/src/schema/state/sqlite/system-tables/state-tables.ts +69 -0
  389. package/src/schema/state/sqlite/table-def.test.ts +101 -0
  390. package/src/schema/state/sqlite/table-def.ts +9 -8
  391. package/src/schema/unknown-events.ts +131 -0
  392. package/src/schema-management/__tests__/migrations-autoincrement-quoting.test.ts +86 -0
  393. package/src/schema-management/migrations.ts +41 -8
  394. package/src/schema-management/validate-schema.ts +3 -3
  395. package/src/sql-queries/sql-queries.ts +9 -1
  396. package/src/sql-queries/sql-query-builder.ts +2 -1
  397. package/src/sqlite-types.ts +3 -3
  398. package/src/sync/ClientSessionSyncProcessor.ts +69 -62
  399. package/src/sync/errors.ts +38 -0
  400. package/src/sync/index.ts +3 -0
  401. package/src/sync/mock-sync-backend.ts +184 -0
  402. package/src/sync/next/compact-events.ts +6 -7
  403. package/src/sync/next/facts.ts +7 -9
  404. package/src/sync/next/history-dag-common.ts +277 -26
  405. package/src/sync/next/history-dag.ts +16 -10
  406. package/src/sync/next/rebase-events.ts +11 -11
  407. package/src/sync/next/test/event-fixtures.ts +11 -11
  408. package/src/sync/sync-backend-kv.ts +22 -0
  409. package/src/sync/sync-backend.ts +185 -0
  410. package/src/sync/sync.ts +9 -91
  411. package/src/sync/syncstate.test.ts +96 -52
  412. package/src/sync/syncstate.ts +69 -58
  413. package/src/sync/transport-chunking.ts +90 -0
  414. package/src/sync/validate-push-payload.ts +8 -9
  415. package/src/testing/event-factory.ts +131 -0
  416. package/src/testing/mod.ts +1 -0
  417. package/src/version.ts +16 -6
  418. package/dist/schema/EventDef.d.ts +0 -123
  419. package/dist/schema/EventDef.d.ts.map +0 -1
  420. package/dist/schema/EventDef.js +0 -46
  421. package/dist/schema/EventDef.js.map +0 -1
  422. package/dist/schema/EventSequenceNumber.d.ts +0 -80
  423. package/dist/schema/EventSequenceNumber.d.ts.map +0 -1
  424. package/dist/schema/EventSequenceNumber.js +0 -139
  425. package/dist/schema/EventSequenceNumber.js.map +0 -1
  426. package/dist/schema/LiveStoreEvent.d.ts.map +0 -1
  427. package/dist/schema/LiveStoreEvent.js.map +0 -1
  428. package/dist/schema/state/sqlite/system-tables.d.ts.map +0 -1
  429. package/dist/schema/state/sqlite/system-tables.js +0 -79
  430. package/dist/schema/state/sqlite/system-tables.js.map +0 -1
  431. package/dist/schema-management/migrations.test.d.ts +0 -2
  432. package/dist/schema-management/migrations.test.d.ts.map +0 -1
  433. package/dist/schema-management/migrations.test.js +0 -52
  434. package/dist/schema-management/migrations.test.js.map +0 -1
  435. package/dist/sync/next/graphology.d.ts +0 -8
  436. package/dist/sync/next/graphology.d.ts.map +0 -1
  437. package/dist/sync/next/graphology.js +0 -30
  438. package/dist/sync/next/graphology.js.map +0 -1
  439. package/dist/sync/next/graphology_.d.ts +0 -3
  440. package/dist/sync/next/graphology_.d.ts.map +0 -1
  441. package/dist/sync/next/graphology_.js +0 -3
  442. package/dist/sync/next/graphology_.js.map +0 -1
  443. package/src/schema/EventDef.ts +0 -219
  444. package/src/schema/EventSequenceNumber.ts +0 -199
  445. package/src/schema/LiveStoreEvent.ts +0 -287
  446. package/src/schema/state/sqlite/system-tables.ts +0 -104
  447. package/src/sync/next/ambient.d.ts +0 -3
  448. package/src/sync/next/graphology.ts +0 -41
  449. package/src/sync/next/graphology_.ts +0 -2
@@ -2,27 +2,34 @@ import { casesHandled, isNotUndefined, LS_DEV, shouldNeverHappen, TRACE_VERBOSE
2
2
  import type { HttpClient, Runtime, Scope, Tracer } from '@livestore/utils/effect'
3
3
  import {
4
4
  BucketQueue,
5
+ Cause,
5
6
  Deferred,
7
+ Duration,
6
8
  Effect,
7
9
  Exit,
8
10
  FiberHandle,
11
+ Layer,
9
12
  Option,
10
13
  OtelTracer,
11
- pipe,
12
14
  Queue,
13
15
  ReadonlyArray,
16
+ Schedule,
14
17
  Stream,
15
18
  Subscribable,
16
19
  SubscriptionRef,
17
20
  } from '@livestore/utils/effect'
18
21
  import type * as otel from '@opentelemetry/api'
19
-
20
- import type { SqliteDb } from '../adapter-types.ts'
21
- import { SyncError, UnexpectedError } from '../adapter-types.ts'
22
+ import { type IntentionalShutdownCause, type MaterializeError, type SqliteDb, UnknownError } from '../adapter-types.ts'
22
23
  import { makeMaterializerHash } from '../materializer-helper.ts'
23
24
  import type { LiveStoreSchema } from '../schema/mod.ts'
24
- import { EventSequenceNumber, getEventDef, LiveStoreEvent, SystemTables } from '../schema/mod.ts'
25
- import { LeaderAheadError } from '../sync/sync.ts'
25
+ import { EventSequenceNumber, LiveStoreEvent, resolveEventDef, SystemTables } from '../schema/mod.ts'
26
+ import {
27
+ type InvalidPullError,
28
+ type InvalidPushError,
29
+ type IsOfflineError,
30
+ LeaderAheadError,
31
+ type SyncBackend,
32
+ } from '../sync/sync.ts'
26
33
  import * as SyncState from '../sync/syncstate.ts'
27
34
  import { sql } from '../util.ts'
28
35
  import * as Eventlog from './eventlog.ts'
@@ -31,7 +38,7 @@ import type { InitialBlockingSyncContext, LeaderSyncProcessor } from './types.ts
31
38
  import { LeaderThreadCtx } from './types.ts'
32
39
 
33
40
  type LocalPushQueueItem = [
34
- event: LiveStoreEvent.EncodedWithMeta,
41
+ event: LiveStoreEvent.Client.EncodedWithMeta,
35
42
  deferred: Deferred.Deferred<void, LeaderAheadError> | undefined,
36
43
  ]
37
44
 
@@ -71,6 +78,7 @@ export const makeLeaderSyncProcessor = ({
71
78
  initialBlockingSyncContext,
72
79
  initialSyncState,
73
80
  onError,
81
+ livePull,
74
82
  params,
75
83
  testing,
76
84
  }: {
@@ -90,23 +98,26 @@ export const makeLeaderSyncProcessor = ({
90
98
  */
91
99
  backendPushBatchSize?: number
92
100
  }
101
+ /**
102
+ * Whether the sync backend should reactively pull new events from the sync backend
103
+ * When `false`, the sync processor will only do an initial pull
104
+ */
105
+ livePull: boolean
93
106
  testing: {
94
107
  delays?: {
95
108
  localPushProcessing?: Effect.Effect<void>
96
109
  }
97
110
  }
98
- }): Effect.Effect<LeaderSyncProcessor, UnexpectedError, Scope.Scope> =>
111
+ }): Effect.Effect<LeaderSyncProcessor, UnknownError, Scope.Scope> =>
99
112
  Effect.gen(function* () {
100
- const syncBackendPushQueue = yield* BucketQueue.make<LiveStoreEvent.EncodedWithMeta>()
113
+ const syncBackendPushQueue = yield* BucketQueue.make<LiveStoreEvent.Client.EncodedWithMeta>()
101
114
  const localPushBatchSize = params.localPushBatchSize ?? 1
102
115
  const backendPushBatchSize = params.backendPushBatchSize ?? 2
103
116
 
104
117
  const syncStateSref = yield* SubscriptionRef.make<SyncState.SyncState | undefined>(undefined)
105
118
 
106
- const isClientEvent = (eventEncoded: LiveStoreEvent.EncodedWithMeta) => {
107
- const { eventDef } = getEventDef(schema, eventEncoded.name)
108
- return eventDef.options.clientOnly
109
- }
119
+ const isClientEvent = (eventEncoded: LiveStoreEvent.Client.EncodedWithMeta) =>
120
+ schema.eventsDefsMap.get(eventEncoded.name)?.options.clientOnly ?? false
110
121
 
111
122
  const connectedClientSessionPullQueues = yield* makePullQueueSet
112
123
 
@@ -135,9 +146,9 @@ export const makeLeaderSyncProcessor = ({
135
146
  *
136
147
  * Thus the purpose of the pushHeadRef is the guard the integrity of the local push queue
137
148
  */
138
- const pushHeadRef = { current: EventSequenceNumber.ROOT }
139
- const advancePushHead = (eventNum: EventSequenceNumber.EventSequenceNumber) => {
140
- pushHeadRef.current = EventSequenceNumber.max(pushHeadRef.current, eventNum)
149
+ const pushHeadRef = { current: EventSequenceNumber.Client.ROOT }
150
+ const advancePushHead = (eventNum: EventSequenceNumber.Client.Composite) => {
151
+ pushHeadRef.current = EventSequenceNumber.Client.max(pushHeadRef.current, eventNum)
141
152
  }
142
153
 
143
154
  // NOTE: New events are only pushed to sync backend after successful local push processing
@@ -180,14 +191,32 @@ export const makeLeaderSyncProcessor = ({
180
191
  const syncState = yield* syncStateSref
181
192
  if (syncState === undefined) return shouldNeverHappen('Not initialized')
182
193
 
183
- const { eventDef } = getEventDef(schema, name)
194
+ const resolution = yield* resolveEventDef(schema, {
195
+ operation: '@livestore/common:LeaderSyncProcessor:pushPartial',
196
+ event: {
197
+ name,
198
+ args,
199
+ clientId,
200
+ sessionId,
201
+ seqNum: syncState.localHead,
202
+ },
203
+ }).pipe(UnknownError.mapToUnknownError)
184
204
 
185
- const eventEncoded = new LiveStoreEvent.EncodedWithMeta({
205
+ if (resolution._tag === 'unknown') {
206
+ // Ignore partial pushes for unrecognised events – they are still
207
+ // persisted server-side once a schema update ships.
208
+ return
209
+ }
210
+
211
+ const eventEncoded = new LiveStoreEvent.Client.EncodedWithMeta({
186
212
  name,
187
213
  args,
188
214
  clientId,
189
215
  sessionId,
190
- ...EventSequenceNumber.nextPair({ seqNum: syncState.localHead, isClient: eventDef.options.clientOnly }),
216
+ ...EventSequenceNumber.Client.nextPair({
217
+ seqNum: syncState.localHead,
218
+ isClient: resolution.eventDef.options.clientOnly,
219
+ }),
191
220
  })
192
221
 
193
222
  yield* push([eventEncoded])
@@ -213,10 +242,10 @@ export const makeLeaderSyncProcessor = ({
213
242
  // Rehydrate sync queue
214
243
  if (initialSyncState.pending.length > 0) {
215
244
  const globalPendingEvents = initialSyncState.pending
216
- // Don't sync clientOnly events
245
+ // Don't sync client-local events
217
246
  .filter((eventEncoded) => {
218
- const { eventDef } = getEventDef(schema, eventEncoded.name)
219
- return eventDef.options.clientOnly === false
247
+ const eventDef = schema.eventsDefsMap.get(eventEncoded.name)
248
+ return eventDef === undefined ? true : eventDef.options.clientOnly === false
220
249
  })
221
250
 
222
251
  if (globalPendingEvents.length > 0) {
@@ -224,12 +253,31 @@ export const makeLeaderSyncProcessor = ({
224
253
  }
225
254
  }
226
255
 
227
- const shutdownOnError = (cause: unknown) =>
256
+ const maybeShutdownOnError = (
257
+ cause: Cause.Cause<
258
+ | UnknownError
259
+ | IntentionalShutdownCause
260
+ | IsOfflineError
261
+ | InvalidPushError
262
+ | InvalidPullError
263
+ | MaterializeError
264
+ >,
265
+ ) =>
228
266
  Effect.gen(function* () {
229
- if (onError === 'shutdown') {
230
- yield* shutdownChannel.send(UnexpectedError.make({ cause }))
231
- yield* Effect.die(cause)
267
+ if (onError === 'ignore') {
268
+ if (LS_DEV) {
269
+ yield* Effect.logDebug(
270
+ `Ignoring sync error (${cause._tag === 'Fail' ? cause.error._tag : cause._tag})`,
271
+ Cause.pretty(cause),
272
+ )
273
+ }
274
+ return
232
275
  }
276
+
277
+ const errorToSend = Cause.isFailType(cause) ? cause.error : UnknownError.make({ cause })
278
+ yield* shutdownChannel.send(errorToSend).pipe(Effect.orDie)
279
+
280
+ return yield* Effect.die(cause)
233
281
  })
234
282
 
235
283
  yield* backgroundApplyLocalPushes({
@@ -246,20 +294,19 @@ export const makeLeaderSyncProcessor = ({
246
294
  testing: {
247
295
  delay: testing?.delays?.localPushProcessing,
248
296
  },
249
- }).pipe(Effect.tapCauseLogPretty, Effect.catchAllCause(shutdownOnError), Effect.forkScoped)
297
+ }).pipe(Effect.catchAllCause(maybeShutdownOnError), Effect.forkScoped)
250
298
 
251
- const backendPushingFiberHandle = yield* FiberHandle.make()
299
+ const backendPushingFiberHandle = yield* FiberHandle.make<void, never>()
252
300
  const backendPushingEffect = backgroundBackendPushing({
253
301
  syncBackendPushQueue,
254
302
  otelSpan,
255
303
  devtoolsLatch: ctxRef.current?.devtoolsLatch,
256
304
  backendPushBatchSize,
257
- }).pipe(Effect.tapCauseLogPretty, Effect.catchAllCause(shutdownOnError))
305
+ }).pipe(Effect.catchAllCause(maybeShutdownOnError))
258
306
 
259
307
  yield* FiberHandle.run(backendPushingFiberHandle, backendPushingEffect)
260
308
 
261
309
  yield* backgroundBackendPulling({
262
- initialBackendHead: initialSyncState.upstreamHead.global,
263
310
  isClientEvent,
264
311
  restartBackendPushing: (filteredRebasedPending) =>
265
312
  Effect.gen(function* () {
@@ -276,13 +323,24 @@ export const makeLeaderSyncProcessor = ({
276
323
  syncStateSref,
277
324
  localPushesLatch,
278
325
  pullLatch,
326
+ livePull,
279
327
  dbState,
280
328
  otelSpan,
281
329
  initialBlockingSyncContext,
282
330
  devtoolsLatch: ctxRef.current?.devtoolsLatch,
283
331
  connectedClientSessionPullQueues,
284
332
  advancePushHead,
285
- }).pipe(Effect.tapCauseLogPretty, Effect.catchAllCause(shutdownOnError), Effect.forkScoped)
333
+ }).pipe(
334
+ Effect.retry({
335
+ // We want to retry pulling if we've lost connection to the sync backend
336
+ while: (cause) => cause._tag === 'IsOfflineError',
337
+ }),
338
+ Effect.catchAllCause(maybeShutdownOnError),
339
+ // Needed to avoid `Fiber terminated with an unhandled error` logs which seem to happen because of the `Effect.retry` above.
340
+ // This might be a bug in Effect. Only seems to happen in the browser.
341
+ Effect.provide(Layer.setUnhandledErrorLogLevel(Option.none())),
342
+ Effect.forkScoped,
343
+ )
286
344
 
287
345
  return { initialLeaderHead: initialSyncState.localHead }
288
346
  }).pipe(Effect.withSpanScoped('@livestore/common:LeaderSyncProcessor:boot'))
@@ -348,9 +406,9 @@ const backgroundApplyLocalPushes = ({
348
406
  localPushesLatch: Effect.Latch
349
407
  localPushesQueue: BucketQueue.BucketQueue<LocalPushQueueItem>
350
408
  syncStateSref: SubscriptionRef.SubscriptionRef<SyncState.SyncState | undefined>
351
- syncBackendPushQueue: BucketQueue.BucketQueue<LiveStoreEvent.EncodedWithMeta>
409
+ syncBackendPushQueue: BucketQueue.BucketQueue<LiveStoreEvent.Client.EncodedWithMeta>
352
410
  schema: LiveStoreSchema
353
- isClientEvent: (eventEncoded: LiveStoreEvent.EncodedWithMeta) => boolean
411
+ isClientEvent: (eventEncoded: LiveStoreEvent.Client.EncodedWithMeta) => boolean
354
412
  otelSpan: otel.Span | undefined
355
413
  connectedClientSessionPullQueues: PullQueueSet
356
414
  localPushBatchSize: number
@@ -379,33 +437,58 @@ const backgroundApplyLocalPushes = ({
379
437
 
380
438
  // Since the rebase generation might have changed since enqueuing, we need to filter out items with older generation
381
439
  // It's important that we filter after we got localPushesLatch, otherwise we might filter with the old generation
382
- const [newEvents, deferreds] = pipe(
440
+ const [droppedItems, filteredItems] = ReadonlyArray.partition(
383
441
  batchItems,
384
- ReadonlyArray.filter(([eventEncoded]) => eventEncoded.seqNum.rebaseGeneration === currentRebaseGeneration),
385
- ReadonlyArray.unzip,
442
+ ([eventEncoded]) => eventEncoded.seqNum.rebaseGeneration >= currentRebaseGeneration,
386
443
  )
387
444
 
388
- if (newEvents.length === 0) {
389
- // console.log('dropping old-gen batch', currentLocalPushGenerationRef.current)
390
- // Allow the backend pulling to start
445
+ if (droppedItems.length > 0) {
446
+ otelSpan?.addEvent(`push:drop-old-generation`, {
447
+ droppedCount: droppedItems.length,
448
+ currentRebaseGeneration,
449
+ })
450
+
451
+ /**
452
+ * Dropped pushes may still have a deferred awaiting completion.
453
+ * Fail it so the caller learns the leader advanced and resubmits with the updated generation.
454
+ */
455
+ yield* Effect.forEach(
456
+ droppedItems.filter(
457
+ (item): item is [LiveStoreEvent.Client.EncodedWithMeta, Deferred.Deferred<void, LeaderAheadError>] =>
458
+ item[1] !== undefined,
459
+ ),
460
+ ([eventEncoded, deferred]) =>
461
+ Deferred.fail(
462
+ deferred,
463
+ LeaderAheadError.make({
464
+ minimumExpectedNum: syncState.localHead,
465
+ providedNum: eventEncoded.seqNum,
466
+ }),
467
+ ),
468
+ )
469
+ }
470
+
471
+ if (filteredItems.length === 0) {
391
472
  yield* pullLatch.open
392
473
  continue
393
474
  }
394
475
 
476
+ const [newEvents, deferreds] = ReadonlyArray.unzip(filteredItems)
477
+
395
478
  const mergeResult = SyncState.merge({
396
479
  syncState,
397
480
  payload: { _tag: 'local-push', newEvents },
398
481
  isClientEvent,
399
- isEqualEvent: LiveStoreEvent.isEqualEncoded,
482
+ isEqualEvent: LiveStoreEvent.Client.isEqualEncoded,
400
483
  })
401
484
 
402
485
  switch (mergeResult._tag) {
403
- case 'unexpected-error': {
404
- otelSpan?.addEvent(`push:unexpected-error`, {
486
+ case 'unknown-error': {
487
+ otelSpan?.addEvent(`push:unknown-error`, {
405
488
  batchSize: newEvents.length,
406
489
  newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
407
490
  })
408
- return yield* new SyncError({ cause: mergeResult.message })
491
+ return yield* new UnknownError({ cause: mergeResult.message })
409
492
  }
410
493
  case 'rebase': {
411
494
  return shouldNeverHappen('The leader thread should never have to rebase due to a local push')
@@ -474,10 +557,10 @@ const backgroundApplyLocalPushes = ({
474
557
  mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
475
558
  })
476
559
 
477
- // Don't sync clientOnly events
560
+ // Don't sync client-local events
478
561
  const filteredBatch = mergeResult.newEvents.filter((eventEncoded) => {
479
- const { eventDef } = getEventDef(schema, eventEncoded.name)
480
- return eventDef.options.clientOnly === false
562
+ const eventDef = schema.eventsDefsMap.get(eventEncoded.name)
563
+ return eventDef === undefined ? true : eventDef.options.clientOnly === false
481
564
  })
482
565
 
483
566
  yield* BucketQueue.offerAll(syncBackendPushQueue, filteredBatch)
@@ -490,13 +573,13 @@ const backgroundApplyLocalPushes = ({
490
573
  })
491
574
 
492
575
  type MaterializeEventsBatch = (_: {
493
- batchItems: ReadonlyArray<LiveStoreEvent.EncodedWithMeta>
576
+ batchItems: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>
494
577
  /**
495
578
  * The deferreds are used by the caller to know when the mutation has been processed.
496
579
  * Indexes are aligned with `batchItems`
497
580
  */
498
581
  deferreds: ReadonlyArray<Deferred.Deferred<void, LeaderAheadError> | undefined> | undefined
499
- }) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx>
582
+ }) => Effect.Effect<void, MaterializeError, LeaderThreadCtx>
500
583
 
501
584
  // TODO how to handle errors gracefully
502
585
  const materializeEventsBatch: MaterializeEventsBatch = ({ batchItems, deferreds }) =>
@@ -536,44 +619,46 @@ const materializeEventsBatch: MaterializeEventsBatch = ({ batchItems, deferreds
536
619
  attributes: { batchSize: batchItems.length },
537
620
  }),
538
621
  Effect.tapCauseLogPretty,
539
- UnexpectedError.mapToUnexpectedError,
540
622
  )
541
623
 
542
624
  const backgroundBackendPulling = ({
543
- initialBackendHead,
544
625
  isClientEvent,
545
626
  restartBackendPushing,
546
627
  otelSpan,
547
628
  dbState,
548
629
  syncStateSref,
549
630
  localPushesLatch,
631
+ livePull,
550
632
  pullLatch,
551
633
  devtoolsLatch,
552
634
  initialBlockingSyncContext,
553
635
  connectedClientSessionPullQueues,
554
636
  advancePushHead,
555
637
  }: {
556
- initialBackendHead: EventSequenceNumber.GlobalEventSequenceNumber
557
- isClientEvent: (eventEncoded: LiveStoreEvent.EncodedWithMeta) => boolean
638
+ isClientEvent: (eventEncoded: LiveStoreEvent.Client.EncodedWithMeta) => boolean
558
639
  restartBackendPushing: (
559
- filteredRebasedPending: ReadonlyArray<LiveStoreEvent.EncodedWithMeta>,
560
- ) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx | HttpClient.HttpClient>
640
+ filteredRebasedPending: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>,
641
+ ) => Effect.Effect<void, UnknownError, LeaderThreadCtx | HttpClient.HttpClient>
561
642
  otelSpan: otel.Span | undefined
562
643
  syncStateSref: SubscriptionRef.SubscriptionRef<SyncState.SyncState | undefined>
563
644
  dbState: SqliteDb
564
645
  localPushesLatch: Effect.Latch
565
646
  pullLatch: Effect.Latch
647
+ livePull: boolean
566
648
  devtoolsLatch: Effect.Latch | undefined
567
649
  initialBlockingSyncContext: InitialBlockingSyncContext
568
650
  connectedClientSessionPullQueues: PullQueueSet
569
- advancePushHead: (eventNum: EventSequenceNumber.EventSequenceNumber) => void
651
+ advancePushHead: (eventNum: EventSequenceNumber.Client.Composite) => void
570
652
  }) =>
571
653
  Effect.gen(function* () {
572
654
  const { syncBackend, dbState: db, dbEventlog, schema } = yield* LeaderThreadCtx
573
655
 
574
656
  if (syncBackend === undefined) return
575
657
 
576
- const onNewPullChunk = (newEvents: LiveStoreEvent.EncodedWithMeta[], remaining: number) =>
658
+ const onNewPullChunk = (
659
+ newEvents: LiveStoreEvent.Client.EncodedWithMeta[],
660
+ pageInfo: SyncBackend.PullResPageInfo,
661
+ ) =>
577
662
  Effect.gen(function* () {
578
663
  if (newEvents.length === 0) return
579
664
 
@@ -594,18 +679,18 @@ const backgroundBackendPulling = ({
594
679
  syncState,
595
680
  payload: SyncState.PayloadUpstreamAdvance.make({ newEvents }),
596
681
  isClientEvent,
597
- isEqualEvent: LiveStoreEvent.isEqualEncoded,
682
+ isEqualEvent: LiveStoreEvent.Client.isEqualEncoded,
598
683
  ignoreClientEvents: true,
599
684
  })
600
685
 
601
686
  if (mergeResult._tag === 'reject') {
602
687
  return shouldNeverHappen('The leader thread should never reject upstream advances')
603
- } else if (mergeResult._tag === 'unexpected-error') {
604
- otelSpan?.addEvent(`pull:unexpected-error`, {
688
+ } else if (mergeResult._tag === 'unknown-error') {
689
+ otelSpan?.addEvent(`pull:unknown-error`, {
605
690
  newEventsCount: newEvents.length,
606
691
  newEvents: TRACE_VERBOSE ? JSON.stringify(newEvents) : undefined,
607
692
  })
608
- return yield* new SyncError({ cause: mergeResult.message })
693
+ return yield* new UnknownError({ cause: mergeResult.message })
609
694
  }
610
695
 
611
696
  const newBackendHead = newEvents.at(-1)!.seqNum
@@ -621,8 +706,8 @@ const backgroundBackendPulling = ({
621
706
  })
622
707
 
623
708
  const globalRebasedPendingEvents = mergeResult.newSyncState.pending.filter((event) => {
624
- const { eventDef } = getEventDef(schema, event.name)
625
- return eventDef.options.clientOnly === false
709
+ const eventDef = schema.eventsDefsMap.get(event.name)
710
+ return eventDef === undefined ? true : eventDef.options.clientOnly === false
626
711
  })
627
712
  yield* restartBackendPushing(globalRebasedPendingEvents)
628
713
 
@@ -644,6 +729,13 @@ const backgroundBackendPulling = ({
644
729
  mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
645
730
  })
646
731
 
732
+ // Ensure push fiber is active after advance by restarting with current pending (non-client) events
733
+ const globalPendingEvents = mergeResult.newSyncState.pending.filter((event) => {
734
+ const eventDef = schema.eventsDefsMap.get(event.name)
735
+ return eventDef === undefined ? true : eventDef.options.clientOnly === false
736
+ })
737
+ yield* restartBackendPushing(globalPendingEvents)
738
+
647
739
  yield* connectedClientSessionPullQueues.offer({
648
740
  payload: SyncState.payloadFromMergeResult(mergeResult),
649
741
  leaderHead: mergeResult.newSyncState.localHead,
@@ -654,10 +746,10 @@ const backgroundBackendPulling = ({
654
746
  // `newEvents` instead which we filter via `mergeResult.confirmedEvents`
655
747
  const confirmedNewEvents = newEvents.filter((event) =>
656
748
  mergeResult.confirmedEvents.some((confirmedEvent) =>
657
- EventSequenceNumber.isEqual(event.seqNum, confirmedEvent.seqNum),
749
+ EventSequenceNumber.Client.isEqual(event.seqNum, confirmedEvent.seqNum),
658
750
  ),
659
751
  )
660
- yield* Eventlog.updateSyncMetadata(confirmedNewEvents)
752
+ yield* Eventlog.updateSyncMetadata(confirmedNewEvents).pipe(UnknownError.mapToUnknownError)
661
753
  }
662
754
  }
663
755
 
@@ -671,18 +763,20 @@ const backgroundBackendPulling = ({
671
763
  yield* SubscriptionRef.set(syncStateSref, mergeResult.newSyncState)
672
764
 
673
765
  // Allow local pushes to be processed again
674
- if (remaining === 0) {
766
+ if (pageInfo._tag === 'NoMore') {
675
767
  yield* localPushesLatch.open
676
768
  }
677
769
  })
678
770
 
679
- const cursorInfo = yield* Eventlog.getSyncBackendCursorInfo({ remoteHead: initialBackendHead })
771
+ const syncState = yield* syncStateSref
772
+ if (syncState === undefined) return shouldNeverHappen('Not initialized')
773
+ const cursorInfo = yield* Eventlog.getSyncBackendCursorInfo({ remoteHead: syncState.upstreamHead.global })
680
774
 
681
775
  const hashMaterializerResult = makeMaterializerHash({ schema, dbState })
682
776
 
683
- yield* syncBackend.pull(cursorInfo).pipe(
777
+ yield* syncBackend.pull(cursorInfo, { live: livePull }).pipe(
684
778
  // TODO only take from queue while connected
685
- Stream.tap(({ batch, remaining }) =>
779
+ Stream.tap(({ batch, pageInfo }) =>
686
780
  Effect.gen(function* () {
687
781
  // yield* Effect.spanEvent('batch', {
688
782
  // attributes: {
@@ -690,31 +784,31 @@ const backgroundBackendPulling = ({
690
784
  // batch: TRACE_VERBOSE ? batch : undefined,
691
785
  // },
692
786
  // })
693
-
694
787
  // NOTE we only want to take process events when the sync backend is connected
695
788
  // (e.g. needed for simulating being offline)
696
789
  // TODO remove when there's a better way to handle this in stream above
697
790
  yield* SubscriptionRef.waitUntil(syncBackend.isConnected, (isConnected) => isConnected === true)
698
-
699
791
  yield* onNewPullChunk(
700
792
  batch.map((_) =>
701
- LiveStoreEvent.EncodedWithMeta.fromGlobal(_.eventEncoded, {
793
+ LiveStoreEvent.Client.EncodedWithMeta.fromGlobal(_.eventEncoded, {
702
794
  syncMetadata: _.metadata,
703
795
  // TODO we can't really know the materializer result here yet beyond the first event batch item as we need to materialize it one by one first
704
796
  // This is a bug and needs to be fixed https://github.com/livestorejs/livestore/issues/503#issuecomment-3114533165
705
- materializerHashLeader: hashMaterializerResult(LiveStoreEvent.encodedFromGlobal(_.eventEncoded)),
797
+ materializerHashLeader: hashMaterializerResult(LiveStoreEvent.Global.toClientEncoded(_.eventEncoded)),
706
798
  materializerHashSession: Option.none(),
707
799
  }),
708
800
  ),
709
- remaining,
801
+ pageInfo,
710
802
  )
711
-
712
- yield* initialBlockingSyncContext.update({ processed: batch.length, remaining })
803
+ yield* initialBlockingSyncContext.update({ processed: batch.length, pageInfo })
713
804
  }),
714
805
  ),
715
806
  Stream.runDrain,
716
807
  Effect.interruptible,
717
808
  )
809
+
810
+ // Should only ever happen when livePull is false
811
+ yield* Effect.logDebug('backend-pulling finished', { livePull })
718
812
  }).pipe(Effect.withSpan('@livestore/common:LeaderSyncProcessor:backend-pulling'))
719
813
 
720
814
  const backgroundBackendPushing = ({
@@ -723,7 +817,7 @@ const backgroundBackendPushing = ({
723
817
  devtoolsLatch,
724
818
  backendPushBatchSize,
725
819
  }: {
726
- syncBackendPushQueue: BucketQueue.BucketQueue<LiveStoreEvent.EncodedWithMeta>
820
+ syncBackendPushQueue: BucketQueue.BucketQueue<LiveStoreEvent.Client.EncodedWithMeta>
727
821
  otelSpan: otel.Span | undefined
728
822
  devtoolsLatch: Effect.Latch | undefined
729
823
  backendPushBatchSize: number
@@ -748,21 +842,57 @@ const backgroundBackendPushing = ({
748
842
  batch: TRACE_VERBOSE ? JSON.stringify(queueItems) : undefined,
749
843
  })
750
844
 
751
- // TODO handle push errors (should only happen during concurrent pull+push)
752
- const pushResult = yield* syncBackend.push(queueItems.map((_) => _.toGlobal())).pipe(Effect.either)
845
+ // Push with declarative retry/backoff using Effect schedules
846
+ // - Exponential backoff starting at 1s and doubling (1s, 2s, 4s, 8s, 16s, 30s ...)
847
+ // - Delay clamped at 30s (continues retrying at 30s)
848
+ // - Resets automatically after successful push
849
+ // TODO(metrics): expose counters/gauges for retry attempts and queue health via devtools/metrics
850
+
851
+ // Only retry for transient UnknownError cases
852
+ const isRetryable = (err: InvalidPushError | IsOfflineError) =>
853
+ err._tag === 'InvalidPushError' && err.cause._tag === 'LiveStore.UnknownError'
854
+
855
+ // Input: InvalidPushError | IsOfflineError, Output: Duration
856
+ const retrySchedule: Schedule.Schedule<Duration.DurationInput, InvalidPushError | IsOfflineError> =
857
+ Schedule.exponential(Duration.seconds(1)).pipe(
858
+ Schedule.andThenEither(Schedule.spaced(Duration.seconds(30))), // clamp at 30 second intervals
859
+ Schedule.compose(Schedule.elapsed),
860
+ Schedule.whileInput(isRetryable),
861
+ )
862
+
863
+ yield* Effect.gen(function* () {
864
+ const iteration = yield* Schedule.CurrentIterationMetadata
753
865
 
754
- if (pushResult._tag === 'Left') {
755
- if (LS_DEV) {
756
- yield* Effect.logDebug('handled backend-push-error', { error: pushResult.left.toString() })
866
+ const pushResult = yield* syncBackend.push(queueItems.map((_) => _.toGlobal())).pipe(Effect.either)
867
+
868
+ const retries = iteration.recurrence
869
+ if (retries > 0 && pushResult._tag === 'Right') {
870
+ otelSpan?.addEvent('backend-push-retry-success', { retries, batchSize: queueItems.length })
757
871
  }
758
- otelSpan?.addEvent('backend-push-error', { error: pushResult.left.toString() })
759
- // wait for interrupt caused by background pulling which will then restart pushing
760
- return yield* Effect.never
761
- }
872
+
873
+ if (pushResult._tag === 'Left') {
874
+ otelSpan?.addEvent('backend-push-error', {
875
+ error: pushResult.left.toString(),
876
+ retries,
877
+ batchSize: queueItems.length,
878
+ })
879
+ const error = pushResult.left
880
+ if (
881
+ error._tag === 'IsOfflineError' ||
882
+ (error._tag === 'InvalidPushError' && error.cause._tag === 'ServerAheadError')
883
+ ) {
884
+ // It's a core part of the sync protocol that the sync backend will emit a new pull chunk alongside the ServerAheadError
885
+ yield* Effect.logDebug('handled backend-push-error (waiting for interupt caused by pull)', { error })
886
+ return yield* Effect.never
887
+ }
888
+
889
+ return yield* error
890
+ }
891
+ }).pipe(Effect.retry(retrySchedule))
762
892
  }
763
893
  }).pipe(Effect.interruptible, Effect.withSpan('@livestore/common:LeaderSyncProcessor:backend-pushing'))
764
894
 
765
- const trimChangesetRows = (db: SqliteDb, newHead: EventSequenceNumber.EventSequenceNumber) => {
895
+ const trimChangesetRows = (db: SqliteDb, newHead: EventSequenceNumber.Client.Composite) => {
766
896
  // Since we're using the session changeset rows to query for the current head,
767
897
  // we're keeping at least one row for the current head, and thus are using `<` instead of `<=`
768
898
  db.execute(sql`DELETE FROM ${SystemTables.SESSION_CHANGESET_META_TABLE} WHERE seqNumGlobal < ${newHead.global}`)
@@ -770,16 +900,16 @@ const trimChangesetRows = (db: SqliteDb, newHead: EventSequenceNumber.EventSeque
770
900
 
771
901
  interface PullQueueSet {
772
902
  makeQueue: (
773
- cursor: EventSequenceNumber.EventSequenceNumber,
903
+ cursor: EventSequenceNumber.Client.Composite,
774
904
  ) => Effect.Effect<
775
905
  Queue.Queue<{ payload: typeof SyncState.PayloadUpstream.Type }>,
776
- UnexpectedError,
906
+ UnknownError,
777
907
  Scope.Scope | LeaderThreadCtx
778
908
  >
779
909
  offer: (item: {
780
910
  payload: typeof SyncState.PayloadUpstream.Type
781
- leaderHead: EventSequenceNumber.EventSequenceNumber
782
- }) => Effect.Effect<void, UnexpectedError>
911
+ leaderHead: EventSequenceNumber.Client.Composite
912
+ }) => Effect.Effect<void, UnknownError>
783
913
  }
784
914
 
785
915
  const makePullQueueSet = Effect.gen(function* () {
@@ -809,17 +939,17 @@ const makePullQueueSet = Effect.gen(function* () {
809
939
 
810
940
  const payloadsSinceCursor = Array.from(cachedPayloads.entries())
811
941
  .flatMap(([seqNumStr, payloads]) =>
812
- payloads.map((payload) => ({ payload, seqNum: EventSequenceNumber.fromString(seqNumStr) })),
942
+ payloads.map((payload) => ({ payload, seqNum: EventSequenceNumber.Client.fromString(seqNumStr) })),
813
943
  )
814
- .filter(({ seqNum }) => EventSequenceNumber.isGreaterThan(seqNum, cursor))
815
- .toSorted((a, b) => EventSequenceNumber.compare(a.seqNum, b.seqNum))
944
+ .filter(({ seqNum }) => EventSequenceNumber.Client.isGreaterThan(seqNum, cursor))
945
+ .toSorted((a, b) => EventSequenceNumber.Client.compare(a.seqNum, b.seqNum))
816
946
  .map(({ payload }) => {
817
947
  if (payload._tag === 'upstream-advance') {
818
948
  return {
819
949
  payload: {
820
950
  _tag: 'upstream-advance' as const,
821
951
  newEvents: ReadonlyArray.dropWhile(payload.newEvents, (eventEncoded) =>
822
- EventSequenceNumber.isGreaterThanOrEqual(cursor, eventEncoded.seqNum),
952
+ EventSequenceNumber.Client.isGreaterThanOrEqual(cursor, eventEncoded.seqNum),
823
953
  ),
824
954
  },
825
955
  }
@@ -865,7 +995,7 @@ const makePullQueueSet = Effect.gen(function* () {
865
995
 
866
996
  const offer: PullQueueSet['offer'] = (item) =>
867
997
  Effect.gen(function* () {
868
- const seqNumStr = EventSequenceNumber.toString(item.leaderHead)
998
+ const seqNumStr = EventSequenceNumber.Client.toString(item.leaderHead)
869
999
  if (cachedPayloads.has(seqNumStr)) {
870
1000
  cachedPayloads.get(seqNumStr)!.push(item.payload)
871
1001
  } else {
@@ -890,26 +1020,35 @@ const makePullQueueSet = Effect.gen(function* () {
890
1020
  }
891
1021
  })
892
1022
 
1023
+ /**
1024
+ * Validate a client-provided batch before it is admitted to the leader queue.
1025
+ * Ensures the numbers form a strictly increasing chain and that the first
1026
+ * event sits ahead of the current push head.
1027
+ */
893
1028
  const validatePushBatch = (
894
- batch: ReadonlyArray<LiveStoreEvent.EncodedWithMeta>,
895
- pushHead: EventSequenceNumber.EventSequenceNumber,
1029
+ batch: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>,
1030
+ pushHead: EventSequenceNumber.Client.Composite,
896
1031
  ) =>
897
1032
  Effect.gen(function* () {
898
1033
  if (batch.length === 0) {
899
1034
  return
900
1035
  }
901
1036
 
902
- // Make sure batch is monotonically increasing
1037
+ // Example: session A already enqueued e1…e6 while session B (same client, different
1038
+ // session) still believes the head is e1 and submits [e2, e7, e8]. The numbers look
1039
+ // monotonic from B’s perspective, but we must reject and force B to rebase locally
1040
+ // so the leader never regresses.
903
1041
  for (let i = 1; i < batch.length; i++) {
904
- if (EventSequenceNumber.isGreaterThanOrEqual(batch[i - 1]!.seqNum, batch[i]!.seqNum)) {
905
- shouldNeverHappen(
906
- `Events must be ordered in monotonically ascending order by eventNum. Received: [${batch.map((e) => EventSequenceNumber.toString(e.seqNum)).join(', ')}]`,
907
- )
1042
+ if (EventSequenceNumber.Client.isGreaterThanOrEqual(batch[i - 1]!.seqNum, batch[i]!.seqNum)) {
1043
+ return yield* LeaderAheadError.make({
1044
+ minimumExpectedNum: batch[i - 1]!.seqNum,
1045
+ providedNum: batch[i]!.seqNum,
1046
+ })
908
1047
  }
909
1048
  }
910
1049
 
911
1050
  // Make sure smallest sequence number is > pushHead
912
- if (EventSequenceNumber.isGreaterThanOrEqual(pushHead, batch[0]!.seqNum)) {
1051
+ if (EventSequenceNumber.Client.isGreaterThanOrEqual(pushHead, batch[0]!.seqNum)) {
913
1052
  return yield* LeaderAheadError.make({
914
1053
  minimumExpectedNum: pushHead,
915
1054
  providedNum: batch[0]!.seqNum,