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

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