@livestore/common 0.4.0-dev.9 → 0.5.0-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (580) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/ClientSessionLeaderThreadProxy.d.ts +27 -12
  3. package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
  4. package/dist/ClientSessionLeaderThreadProxy.js.map +1 -1
  5. package/dist/MaterializationJournal.d.ts +44 -0
  6. package/dist/MaterializationJournal.d.ts.map +1 -0
  7. package/dist/MaterializationJournal.js +112 -0
  8. package/dist/MaterializationJournal.js.map +1 -0
  9. package/dist/StateHead.d.ts +26 -0
  10. package/dist/StateHead.d.ts.map +1 -0
  11. package/dist/StateHead.js +50 -0
  12. package/dist/StateHead.js.map +1 -0
  13. package/dist/WorkerTransportError.d.ts +11 -0
  14. package/dist/WorkerTransportError.d.ts.map +1 -0
  15. package/dist/WorkerTransportError.js +11 -0
  16. package/dist/WorkerTransportError.js.map +1 -0
  17. package/dist/__tests__/fixture.d.ts +1 -1
  18. package/dist/__tests__/fixture.d.ts.map +1 -1
  19. package/dist/__tests__/fixture.js +3 -3
  20. package/dist/__tests__/fixture.js.map +1 -1
  21. package/dist/adapter-types.d.ts +59 -57
  22. package/dist/adapter-types.d.ts.map +1 -1
  23. package/dist/adapter-types.js +31 -22
  24. package/dist/adapter-types.js.map +1 -1
  25. package/dist/bounded-collections.d.ts.map +1 -1
  26. package/dist/bounded-collections.js +6 -4
  27. package/dist/bounded-collections.js.map +1 -1
  28. package/dist/debug-info.d.ts +56 -56
  29. package/dist/debug-info.d.ts.map +1 -1
  30. package/dist/debug-info.js +46 -32
  31. package/dist/debug-info.js.map +1 -1
  32. package/dist/defs.d.ts +9 -9
  33. package/dist/defs.js +2 -2
  34. package/dist/devtools/devtools-compatibility.test.d.ts +2 -0
  35. package/dist/devtools/devtools-compatibility.test.d.ts.map +1 -0
  36. package/dist/devtools/devtools-compatibility.test.js +16 -0
  37. package/dist/devtools/devtools-compatibility.test.js.map +1 -0
  38. package/dist/devtools/devtools-messages-client-session.d.ts +531 -312
  39. package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -1
  40. package/dist/devtools/devtools-messages-client-session.js +86 -67
  41. package/dist/devtools/devtools-messages-client-session.js.map +1 -1
  42. package/dist/devtools/devtools-messages-common.d.ts +32 -35
  43. package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
  44. package/dist/devtools/devtools-messages-common.js +12 -14
  45. package/dist/devtools/devtools-messages-common.js.map +1 -1
  46. package/dist/devtools/devtools-messages-leader.d.ts +417 -320
  47. package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
  48. package/dist/devtools/devtools-messages-leader.js +117 -87
  49. package/dist/devtools/devtools-messages-leader.js.map +1 -1
  50. package/dist/devtools/devtools-sessioninfo.d.ts +28 -17
  51. package/dist/devtools/devtools-sessioninfo.d.ts.map +1 -1
  52. package/dist/devtools/devtools-sessioninfo.js +12 -6
  53. package/dist/devtools/devtools-sessioninfo.js.map +1 -1
  54. package/dist/devtools/mod.d.ts +17 -7
  55. package/dist/devtools/mod.d.ts.map +1 -1
  56. package/dist/devtools/mod.js +19 -8
  57. package/dist/devtools/mod.js.map +1 -1
  58. package/dist/errors.d.ts +52 -46
  59. package/dist/errors.d.ts.map +1 -1
  60. package/dist/errors.js +31 -21
  61. package/dist/errors.js.map +1 -1
  62. package/dist/index.d.ts +7 -1
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +7 -1
  65. package/dist/index.js.map +1 -1
  66. package/dist/leader-thread/LeaderSyncProcessor.d.ts +116 -21
  67. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
  68. package/dist/leader-thread/LeaderSyncProcessor.js +596 -470
  69. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  70. package/dist/leader-thread/RejectedPushError.d.ts +138 -0
  71. package/dist/leader-thread/RejectedPushError.d.ts.map +1 -0
  72. package/dist/leader-thread/RejectedPushError.js +97 -0
  73. package/dist/leader-thread/RejectedPushError.js.map +1 -0
  74. package/dist/leader-thread/connection.d.ts +1 -0
  75. package/dist/leader-thread/connection.d.ts.map +1 -1
  76. package/dist/leader-thread/connection.js +10 -9
  77. package/dist/leader-thread/connection.js.map +1 -1
  78. package/dist/leader-thread/eventlog.d.ts +20 -15
  79. package/dist/leader-thread/eventlog.d.ts.map +1 -1
  80. package/dist/leader-thread/eventlog.js +80 -20
  81. package/dist/leader-thread/eventlog.js.map +1 -1
  82. package/dist/leader-thread/leader-worker-devtools.d.ts +1 -2
  83. package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
  84. package/dist/leader-thread/leader-worker-devtools.js +110 -62
  85. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  86. package/dist/leader-thread/make-leader-thread-layer.d.ts +19 -7
  87. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  88. package/dist/leader-thread/make-leader-thread-layer.js +67 -45
  89. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  90. package/dist/leader-thread/make-leader-thread-layer.test.d.ts +2 -0
  91. package/dist/leader-thread/make-leader-thread-layer.test.d.ts.map +1 -0
  92. package/dist/leader-thread/make-leader-thread-layer.test.js +32 -0
  93. package/dist/leader-thread/make-leader-thread-layer.test.js.map +1 -0
  94. package/dist/leader-thread/materialize-event.d.ts +3 -2
  95. package/dist/leader-thread/materialize-event.d.ts.map +1 -1
  96. package/dist/leader-thread/materialize-event.js +32 -9
  97. package/dist/leader-thread/materialize-event.js.map +1 -1
  98. package/dist/leader-thread/mod.d.ts +1 -0
  99. package/dist/leader-thread/mod.d.ts.map +1 -1
  100. package/dist/leader-thread/mod.js +1 -0
  101. package/dist/leader-thread/mod.js.map +1 -1
  102. package/dist/leader-thread/recreate-db.d.ts +2 -2
  103. package/dist/leader-thread/recreate-db.d.ts.map +1 -1
  104. package/dist/leader-thread/recreate-db.js +19 -40
  105. package/dist/leader-thread/recreate-db.js.map +1 -1
  106. package/dist/leader-thread/shutdown-channel.d.ts +3 -7
  107. package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
  108. package/dist/leader-thread/shutdown-channel.js +2 -3
  109. package/dist/leader-thread/shutdown-channel.js.map +1 -1
  110. package/dist/leader-thread/stream-events.d.ts +53 -0
  111. package/dist/leader-thread/stream-events.d.ts.map +1 -0
  112. package/dist/leader-thread/stream-events.js +164 -0
  113. package/dist/leader-thread/stream-events.js.map +1 -0
  114. package/dist/leader-thread/types.d.ts +92 -51
  115. package/dist/leader-thread/types.d.ts.map +1 -1
  116. package/dist/leader-thread/types.js +17 -4
  117. package/dist/leader-thread/types.js.map +1 -1
  118. package/dist/logging.d.ts +15 -0
  119. package/dist/logging.d.ts.map +1 -0
  120. package/dist/logging.js +2 -0
  121. package/dist/logging.js.map +1 -0
  122. package/dist/make-client-session.d.ts +6 -5
  123. package/dist/make-client-session.d.ts.map +1 -1
  124. package/dist/make-client-session.js +10 -7
  125. package/dist/make-client-session.js.map +1 -1
  126. package/dist/materializer-helper.d.ts +6 -7
  127. package/dist/materializer-helper.d.ts.map +1 -1
  128. package/dist/materializer-helper.js +22 -40
  129. package/dist/materializer-helper.js.map +1 -1
  130. package/dist/otel.d.ts +4 -4
  131. package/dist/otel.d.ts.map +1 -1
  132. package/dist/otel.js +8 -8
  133. package/dist/otel.js.map +1 -1
  134. package/dist/rematerialize-from-eventlog.d.ts +3 -3
  135. package/dist/rematerialize-from-eventlog.d.ts.map +1 -1
  136. package/dist/rematerialize-from-eventlog.js +52 -39
  137. package/dist/rematerialize-from-eventlog.js.map +1 -1
  138. package/dist/schema/EventDef/define.d.ts +161 -0
  139. package/dist/schema/EventDef/define.d.ts.map +1 -0
  140. package/dist/schema/EventDef/define.js +140 -0
  141. package/dist/schema/EventDef/define.js.map +1 -0
  142. package/dist/schema/EventDef/define.test.d.ts +2 -0
  143. package/dist/schema/EventDef/define.test.d.ts.map +1 -0
  144. package/dist/schema/EventDef/define.test.js +23 -0
  145. package/dist/schema/EventDef/define.test.js.map +1 -0
  146. package/dist/schema/EventDef/deprecated.d.ts +94 -0
  147. package/dist/schema/EventDef/deprecated.d.ts.map +1 -0
  148. package/dist/schema/EventDef/deprecated.js +143 -0
  149. package/dist/schema/EventDef/deprecated.js.map +1 -0
  150. package/dist/schema/EventDef/deprecated.test.d.ts +2 -0
  151. package/dist/schema/EventDef/deprecated.test.d.ts.map +1 -0
  152. package/dist/schema/EventDef/deprecated.test.js +95 -0
  153. package/dist/schema/EventDef/deprecated.test.js.map +1 -0
  154. package/dist/schema/EventDef/event-def.d.ts +110 -0
  155. package/dist/schema/EventDef/event-def.d.ts.map +1 -0
  156. package/dist/schema/EventDef/event-def.js +2 -0
  157. package/dist/schema/EventDef/event-def.js.map +1 -0
  158. package/dist/schema/EventDef/facts.d.ts +118 -0
  159. package/dist/schema/EventDef/facts.d.ts.map +1 -0
  160. package/dist/schema/EventDef/facts.js +53 -0
  161. package/dist/schema/EventDef/facts.js.map +1 -0
  162. package/dist/schema/EventDef/materializer.d.ts +155 -0
  163. package/dist/schema/EventDef/materializer.d.ts.map +1 -0
  164. package/dist/schema/EventDef/materializer.js +83 -0
  165. package/dist/schema/EventDef/materializer.js.map +1 -0
  166. package/dist/schema/EventDef/mod.d.ts +6 -0
  167. package/dist/schema/EventDef/mod.d.ts.map +1 -0
  168. package/dist/schema/EventDef/mod.js +6 -0
  169. package/dist/schema/EventDef/mod.js.map +1 -0
  170. package/dist/schema/EventSequenceNumber/client.d.ts +136 -0
  171. package/dist/schema/EventSequenceNumber/client.d.ts.map +1 -0
  172. package/dist/schema/EventSequenceNumber/client.js +192 -0
  173. package/dist/schema/EventSequenceNumber/client.js.map +1 -0
  174. package/dist/schema/EventSequenceNumber/global.d.ts +15 -0
  175. package/dist/schema/EventSequenceNumber/global.d.ts.map +1 -0
  176. package/dist/schema/EventSequenceNumber/global.js +14 -0
  177. package/dist/schema/EventSequenceNumber/global.js.map +1 -0
  178. package/dist/schema/EventSequenceNumber/mod.d.ts +37 -0
  179. package/dist/schema/EventSequenceNumber/mod.d.ts.map +1 -0
  180. package/dist/schema/EventSequenceNumber/mod.js +37 -0
  181. package/dist/schema/EventSequenceNumber/mod.js.map +1 -0
  182. package/dist/schema/EventSequenceNumber.test.js +44 -44
  183. package/dist/schema/EventSequenceNumber.test.js.map +1 -1
  184. package/dist/schema/LiveStoreEvent/client.d.ts +168 -0
  185. package/dist/schema/LiveStoreEvent/client.d.ts.map +1 -0
  186. package/dist/schema/LiveStoreEvent/client.js +179 -0
  187. package/dist/schema/LiveStoreEvent/client.js.map +1 -0
  188. package/dist/schema/LiveStoreEvent/client.test.d.ts +2 -0
  189. package/dist/schema/LiveStoreEvent/client.test.d.ts.map +1 -0
  190. package/dist/schema/LiveStoreEvent/client.test.js +120 -0
  191. package/dist/schema/LiveStoreEvent/client.test.js.map +1 -0
  192. package/dist/schema/LiveStoreEvent/for-event-def.d.ts +52 -0
  193. package/dist/schema/LiveStoreEvent/for-event-def.d.ts.map +1 -0
  194. package/dist/schema/LiveStoreEvent/for-event-def.js +2 -0
  195. package/dist/schema/LiveStoreEvent/for-event-def.js.map +1 -0
  196. package/dist/schema/LiveStoreEvent/global.d.ts +36 -0
  197. package/dist/schema/LiveStoreEvent/global.d.ts.map +1 -0
  198. package/dist/schema/LiveStoreEvent/global.js +31 -0
  199. package/dist/schema/LiveStoreEvent/global.js.map +1 -0
  200. package/dist/schema/LiveStoreEvent/input.d.ts +46 -0
  201. package/dist/schema/LiveStoreEvent/input.d.ts.map +1 -0
  202. package/dist/schema/LiveStoreEvent/input.js +26 -0
  203. package/dist/schema/LiveStoreEvent/input.js.map +1 -0
  204. package/dist/schema/LiveStoreEvent/mod.d.ts +5 -0
  205. package/dist/schema/LiveStoreEvent/mod.d.ts.map +1 -0
  206. package/dist/schema/LiveStoreEvent/mod.js +5 -0
  207. package/dist/schema/LiveStoreEvent/mod.js.map +1 -0
  208. package/dist/schema/events.d.ts +1 -1
  209. package/dist/schema/events.d.ts.map +1 -1
  210. package/dist/schema/events.js +1 -1
  211. package/dist/schema/events.js.map +1 -1
  212. package/dist/schema/mod.d.ts +7 -4
  213. package/dist/schema/mod.d.ts.map +1 -1
  214. package/dist/schema/mod.js +6 -4
  215. package/dist/schema/mod.js.map +1 -1
  216. package/dist/schema/schema.d.ts +16 -1
  217. package/dist/schema/schema.d.ts.map +1 -1
  218. package/dist/schema/schema.js +34 -5
  219. package/dist/schema/schema.js.map +1 -1
  220. package/dist/schema/state/sqlite/client-document-def.d.ts +14 -18
  221. package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
  222. package/dist/schema/state/sqlite/client-document-def.js +54 -30
  223. package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
  224. package/dist/schema/state/sqlite/client-document-def.test.js +146 -6
  225. package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -1
  226. package/dist/schema/state/sqlite/column-annotations.d.ts +13 -13
  227. package/dist/schema/state/sqlite/column-annotations.d.ts.map +1 -1
  228. package/dist/schema/state/sqlite/column-annotations.js +13 -15
  229. package/dist/schema/state/sqlite/column-annotations.js.map +1 -1
  230. package/dist/schema/state/sqlite/column-annotations.test.js +19 -43
  231. package/dist/schema/state/sqlite/column-annotations.test.js.map +1 -1
  232. package/dist/schema/state/sqlite/column-def.d.ts +1 -1
  233. package/dist/schema/state/sqlite/column-def.d.ts.map +1 -1
  234. package/dist/schema/state/sqlite/column-def.js +132 -57
  235. package/dist/schema/state/sqlite/column-def.js.map +1 -1
  236. package/dist/schema/state/sqlite/column-def.test.js +155 -70
  237. package/dist/schema/state/sqlite/column-def.test.js.map +1 -1
  238. package/dist/schema/state/sqlite/column-spec.d.ts.map +1 -1
  239. package/dist/schema/state/sqlite/column-spec.js +30 -12
  240. package/dist/schema/state/sqlite/column-spec.js.map +1 -1
  241. package/dist/schema/state/sqlite/column-spec.test.js +26 -17
  242. package/dist/schema/state/sqlite/column-spec.test.js.map +1 -1
  243. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +2 -2
  244. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -1
  245. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +15 -6
  246. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -1
  247. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts +25 -19
  248. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts.map +1 -1
  249. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +22 -16
  250. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -1
  251. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js +28 -17
  252. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js.map +1 -1
  253. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts +14 -12
  254. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -1
  255. package/dist/schema/state/sqlite/db-schema/dsl/mod.js +6 -4
  256. package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
  257. package/dist/schema/state/sqlite/mod.d.ts +3 -5
  258. package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
  259. package/dist/schema/state/sqlite/mod.js +6 -8
  260. package/dist/schema/state/sqlite/mod.js.map +1 -1
  261. package/dist/schema/state/sqlite/persistence.d.ts +11 -0
  262. package/dist/schema/state/sqlite/persistence.d.ts.map +1 -0
  263. package/dist/schema/state/sqlite/persistence.js +10 -0
  264. package/dist/schema/state/sqlite/persistence.js.map +1 -0
  265. package/dist/schema/state/sqlite/query-builder/api.d.ts +58 -29
  266. package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
  267. package/dist/schema/state/sqlite/query-builder/astToSql.d.ts.map +1 -1
  268. package/dist/schema/state/sqlite/query-builder/astToSql.js +105 -24
  269. package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -1
  270. package/dist/schema/state/sqlite/query-builder/impl.d.ts +4 -2
  271. package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
  272. package/dist/schema/state/sqlite/query-builder/impl.js +42 -27
  273. package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
  274. package/dist/schema/state/sqlite/query-builder/impl.test.js +402 -498
  275. package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
  276. package/dist/schema/state/sqlite/schema-helpers.d.ts +2 -2
  277. package/dist/schema/state/sqlite/schema-helpers.d.ts.map +1 -1
  278. package/dist/schema/state/sqlite/schema-helpers.js +24 -14
  279. package/dist/schema/state/sqlite/schema-helpers.js.map +1 -1
  280. package/dist/schema/state/sqlite/schema-helpers.test.d.ts +2 -0
  281. package/dist/schema/state/sqlite/schema-helpers.test.d.ts.map +1 -0
  282. package/dist/schema/state/sqlite/schema-helpers.test.js +36 -0
  283. package/dist/schema/state/sqlite/schema-helpers.test.js.map +1 -0
  284. package/dist/schema/state/sqlite/system-tables/eventlog-tables.d.ts +515 -0
  285. package/dist/schema/state/sqlite/system-tables/eventlog-tables.d.ts.map +1 -0
  286. package/dist/schema/state/sqlite/system-tables/eventlog-tables.js +54 -0
  287. package/dist/schema/state/sqlite/system-tables/eventlog-tables.js.map +1 -0
  288. package/dist/schema/state/sqlite/system-tables/mod.d.ts +3 -0
  289. package/dist/schema/state/sqlite/system-tables/mod.d.ts.map +1 -0
  290. package/dist/schema/state/sqlite/system-tables/mod.js +3 -0
  291. package/dist/schema/state/sqlite/system-tables/mod.js.map +1 -0
  292. package/dist/schema/state/sqlite/system-tables/state-tables.d.ts +678 -0
  293. package/dist/schema/state/sqlite/system-tables/state-tables.d.ts.map +1 -0
  294. package/dist/schema/state/sqlite/system-tables/state-tables.js +87 -0
  295. package/dist/schema/state/sqlite/system-tables/state-tables.js.map +1 -0
  296. package/dist/schema/state/sqlite/table-def.d.ts +19 -20
  297. package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
  298. package/dist/schema/state/sqlite/table-def.js +54 -4
  299. package/dist/schema/state/sqlite/table-def.js.map +1 -1
  300. package/dist/schema/state/sqlite/table-def.test.js +226 -20
  301. package/dist/schema/state/sqlite/table-def.test.js.map +1 -1
  302. package/dist/schema/unknown-events.d.ts +47 -0
  303. package/dist/schema/unknown-events.d.ts.map +1 -0
  304. package/dist/schema/unknown-events.js +70 -0
  305. package/dist/schema/unknown-events.js.map +1 -0
  306. package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.d.ts +2 -0
  307. package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.d.ts.map +1 -0
  308. package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js +73 -0
  309. package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js.map +1 -0
  310. package/dist/schema-management/common.js +2 -2
  311. package/dist/schema-management/common.js.map +1 -1
  312. package/dist/schema-management/migrations.d.ts +32 -2
  313. package/dist/schema-management/migrations.d.ts.map +1 -1
  314. package/dist/schema-management/migrations.js +38 -6
  315. package/dist/schema-management/migrations.js.map +1 -1
  316. package/dist/schema-management/validate-schema.d.ts +3 -3
  317. package/dist/schema-management/validate-schema.d.ts.map +1 -1
  318. package/dist/schema-management/validate-schema.js +2 -2
  319. package/dist/schema-management/validate-schema.js.map +1 -1
  320. package/dist/session-id-symbol.d.ts +29 -0
  321. package/dist/session-id-symbol.d.ts.map +1 -0
  322. package/dist/session-id-symbol.js +44 -0
  323. package/dist/session-id-symbol.js.map +1 -0
  324. package/dist/sql-queries/sql-queries.d.ts.map +1 -1
  325. package/dist/sql-queries/sql-queries.js +26 -16
  326. package/dist/sql-queries/sql-queries.js.map +1 -1
  327. package/dist/sql-queries/sql-query-builder.d.ts.map +1 -1
  328. package/dist/sql-queries/sql-query-builder.js.map +1 -1
  329. package/dist/sql-queries/types.d.ts +6 -7
  330. package/dist/sql-queries/types.d.ts.map +1 -1
  331. package/dist/sql-queries/types.js.map +1 -1
  332. package/dist/sqlite-db-helper.d.ts +21 -1
  333. package/dist/sqlite-db-helper.d.ts.map +1 -1
  334. package/dist/sqlite-db-helper.js +40 -5
  335. package/dist/sqlite-db-helper.js.map +1 -1
  336. package/dist/sqlite-types.d.ts +8 -11
  337. package/dist/sqlite-types.d.ts.map +1 -1
  338. package/dist/sqlite-types.js +3 -3
  339. package/dist/sqlite-types.js.map +1 -1
  340. package/dist/sync/ClientSessionSyncProcessor.d.ts +47 -25
  341. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  342. package/dist/sync/ClientSessionSyncProcessor.js +269 -154
  343. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  344. package/dist/sync/errors.d.ts +12 -51
  345. package/dist/sync/errors.d.ts.map +1 -1
  346. package/dist/sync/errors.js +7 -24
  347. package/dist/sync/errors.js.map +1 -1
  348. package/dist/sync/index.d.ts +2 -0
  349. package/dist/sync/index.d.ts.map +1 -1
  350. package/dist/sync/index.js +2 -0
  351. package/dist/sync/index.js.map +1 -1
  352. package/dist/sync/mock-sync-backend.d.ts +11 -10
  353. package/dist/sync/mock-sync-backend.d.ts.map +1 -1
  354. package/dist/sync/mock-sync-backend.js +97 -74
  355. package/dist/sync/mock-sync-backend.js.map +1 -1
  356. package/dist/sync/mock-sync-backend.test.d.ts +2 -0
  357. package/dist/sync/mock-sync-backend.test.d.ts.map +1 -0
  358. package/dist/sync/mock-sync-backend.test.js +49 -0
  359. package/dist/sync/mock-sync-backend.test.js.map +1 -0
  360. package/dist/sync/next/compact-events.d.ts.map +1 -1
  361. package/dist/sync/next/compact-events.js +11 -12
  362. package/dist/sync/next/compact-events.js.map +1 -1
  363. package/dist/sync/next/facts.d.ts +5 -5
  364. package/dist/sync/next/facts.d.ts.map +1 -1
  365. package/dist/sync/next/facts.js +7 -8
  366. package/dist/sync/next/facts.js.map +1 -1
  367. package/dist/sync/next/history-dag-common.d.ts +58 -15
  368. package/dist/sync/next/history-dag-common.d.ts.map +1 -1
  369. package/dist/sync/next/history-dag-common.js +219 -9
  370. package/dist/sync/next/history-dag-common.js.map +1 -1
  371. package/dist/sync/next/history-dag.d.ts.map +1 -1
  372. package/dist/sync/next/history-dag.js +12 -11
  373. package/dist/sync/next/history-dag.js.map +1 -1
  374. package/dist/sync/next/rebase-events.d.ts +5 -5
  375. package/dist/sync/next/rebase-events.d.ts.map +1 -1
  376. package/dist/sync/next/rebase-events.js +6 -6
  377. package/dist/sync/next/rebase-events.js.map +1 -1
  378. package/dist/sync/next/test/compact-events.calculator.test.js +4 -4
  379. package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
  380. package/dist/sync/next/test/compact-events.test.d.ts.map +1 -1
  381. package/dist/sync/next/test/compact-events.test.js +2 -2
  382. package/dist/sync/next/test/compact-events.test.js.map +1 -1
  383. package/dist/sync/next/test/event-fixtures.d.ts +9 -9
  384. package/dist/sync/next/test/event-fixtures.d.ts.map +1 -1
  385. package/dist/sync/next/test/event-fixtures.js +12 -12
  386. package/dist/sync/next/test/event-fixtures.js.map +1 -1
  387. package/dist/sync/next/test/history-dag-common.test.d.ts +2 -0
  388. package/dist/sync/next/test/history-dag-common.test.d.ts.map +1 -0
  389. package/dist/sync/next/test/history-dag-common.test.js +73 -0
  390. package/dist/sync/next/test/history-dag-common.test.js.map +1 -0
  391. package/dist/sync/sync-backend-kv.d.ts +3 -3
  392. package/dist/sync/sync-backend-kv.d.ts.map +1 -1
  393. package/dist/sync/sync-backend-kv.js +6 -4
  394. package/dist/sync/sync-backend-kv.js.map +1 -1
  395. package/dist/sync/sync-backend.d.ts +41 -21
  396. package/dist/sync/sync-backend.d.ts.map +1 -1
  397. package/dist/sync/sync-backend.js +45 -4
  398. package/dist/sync/sync-backend.js.map +1 -1
  399. package/dist/sync/sync.d.ts +23 -2
  400. package/dist/sync/sync.d.ts.map +1 -1
  401. package/dist/sync/syncstate.d.ts +115 -191
  402. package/dist/sync/syncstate.d.ts.map +1 -1
  403. package/dist/sync/syncstate.js +108 -129
  404. package/dist/sync/syncstate.js.map +1 -1
  405. package/dist/sync/syncstate.test.js +289 -161
  406. package/dist/sync/syncstate.test.js.map +1 -1
  407. package/dist/sync/transport-chunking.d.ts +35 -0
  408. package/dist/sync/transport-chunking.d.ts.map +1 -0
  409. package/dist/sync/transport-chunking.js +47 -0
  410. package/dist/sync/transport-chunking.js.map +1 -0
  411. package/dist/sync/validate-push-payload.d.ts +2 -2
  412. package/dist/sync/validate-push-payload.d.ts.map +1 -1
  413. package/dist/sync/validate-push-payload.js +4 -6
  414. package/dist/sync/validate-push-payload.js.map +1 -1
  415. package/dist/testing/event-factory.d.ts +3 -3
  416. package/dist/testing/event-factory.d.ts.map +1 -1
  417. package/dist/testing/event-factory.js +5 -7
  418. package/dist/testing/event-factory.js.map +1 -1
  419. package/dist/util.d.ts +11 -4
  420. package/dist/util.d.ts.map +1 -1
  421. package/dist/util.js +21 -4
  422. package/dist/util.js.map +1 -1
  423. package/dist/version.d.ts +24 -5
  424. package/dist/version.d.ts.map +1 -1
  425. package/dist/version.js +25 -8
  426. package/dist/version.js.map +1 -1
  427. package/package.json +50 -16
  428. package/src/ClientSessionLeaderThreadProxy.ts +27 -12
  429. package/src/MaterializationJournal.ts +184 -0
  430. package/src/StateHead.ts +80 -0
  431. package/src/WorkerTransportError.ts +12 -0
  432. package/src/__tests__/fixture.ts +3 -3
  433. package/src/adapter-types.ts +62 -56
  434. package/src/bounded-collections.ts +6 -5
  435. package/src/debug-info.ts +54 -43
  436. package/src/defs.ts +2 -2
  437. package/src/devtools/devtools-compatibility.test.ts +19 -0
  438. package/src/devtools/devtools-messages-client-session.ts +72 -63
  439. package/src/devtools/devtools-messages-common.ts +18 -22
  440. package/src/devtools/devtools-messages-leader.ts +89 -68
  441. package/src/devtools/devtools-sessioninfo.ts +17 -12
  442. package/src/devtools/mod.ts +15 -7
  443. package/src/errors.ts +58 -38
  444. package/src/index.ts +11 -1
  445. package/src/leader-thread/LeaderSyncProcessor.ts +927 -674
  446. package/src/leader-thread/RejectedPushError.ts +119 -0
  447. package/src/leader-thread/connection.ts +13 -9
  448. package/src/leader-thread/eventlog.ts +114 -41
  449. package/src/leader-thread/leader-worker-devtools.ts +223 -124
  450. package/src/leader-thread/make-leader-thread-layer.test.ts +44 -0
  451. package/src/leader-thread/make-leader-thread-layer.ts +170 -82
  452. package/src/leader-thread/materialize-event.ts +51 -16
  453. package/src/leader-thread/mod.ts +1 -0
  454. package/src/leader-thread/recreate-db.ts +26 -61
  455. package/src/leader-thread/shutdown-channel.ts +3 -18
  456. package/src/leader-thread/stream-events.ts +209 -0
  457. package/src/leader-thread/types.ts +77 -65
  458. package/src/logging.ts +15 -0
  459. package/src/make-client-session.ts +16 -10
  460. package/src/materializer-helper.ts +31 -49
  461. package/src/otel.ts +15 -13
  462. package/src/rematerialize-from-eventlog.ts +106 -93
  463. package/src/schema/EventDef/define.test.ts +28 -0
  464. package/src/schema/EventDef/define.ts +218 -0
  465. package/src/schema/EventDef/deprecated.test.ts +122 -0
  466. package/src/schema/EventDef/deprecated.ts +172 -0
  467. package/src/schema/EventDef/event-def.ts +123 -0
  468. package/src/schema/EventDef/facts.ts +135 -0
  469. package/src/schema/EventDef/materializer.ts +172 -0
  470. package/src/schema/EventDef/mod.ts +5 -0
  471. package/src/schema/EventSequenceNumber/client.ts +256 -0
  472. package/src/schema/EventSequenceNumber/global.ts +19 -0
  473. package/src/schema/EventSequenceNumber/mod.ts +37 -0
  474. package/src/schema/EventSequenceNumber.test.ts +72 -53
  475. package/src/schema/LiveStoreEvent/client.test.ts +142 -0
  476. package/src/schema/LiveStoreEvent/client.ts +236 -0
  477. package/src/schema/LiveStoreEvent/for-event-def.ts +60 -0
  478. package/src/schema/LiveStoreEvent/global.ts +45 -0
  479. package/src/schema/LiveStoreEvent/input.ts +63 -0
  480. package/src/schema/LiveStoreEvent/mod.ts +4 -0
  481. package/src/schema/events.ts +1 -1
  482. package/src/schema/mod.ts +7 -4
  483. package/src/schema/schema.ts +48 -6
  484. package/src/schema/state/sqlite/client-document-def.test.ts +172 -14
  485. package/src/schema/state/sqlite/client-document-def.ts +126 -98
  486. package/src/schema/state/sqlite/column-annotations.test.ts +27 -45
  487. package/src/schema/state/sqlite/column-annotations.ts +27 -30
  488. package/src/schema/state/sqlite/column-def.test.ts +186 -77
  489. package/src/schema/state/sqlite/column-def.ts +169 -62
  490. package/src/schema/state/sqlite/column-spec.test.ts +34 -19
  491. package/src/schema/state/sqlite/column-spec.ts +38 -11
  492. package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +17 -7
  493. package/src/schema/state/sqlite/db-schema/dsl/__snapshots__/field-defs.test.ts.snap +123 -63
  494. package/src/schema/state/sqlite/db-schema/dsl/field-defs.test.ts +37 -17
  495. package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +65 -45
  496. package/src/schema/state/sqlite/db-schema/dsl/mod.ts +48 -40
  497. package/src/schema/state/sqlite/mod.ts +9 -12
  498. package/src/schema/state/sqlite/persistence.ts +11 -0
  499. package/src/schema/state/sqlite/query-builder/api.ts +61 -23
  500. package/src/schema/state/sqlite/query-builder/astToSql.ts +117 -23
  501. package/src/schema/state/sqlite/query-builder/impl.test.ts +489 -551
  502. package/src/schema/state/sqlite/query-builder/impl.ts +43 -26
  503. package/src/schema/state/sqlite/schema-helpers.test.ts +44 -0
  504. package/src/schema/state/sqlite/schema-helpers.ts +30 -22
  505. package/src/schema/state/sqlite/system-tables/eventlog-tables.ts +64 -0
  506. package/src/schema/state/sqlite/system-tables/mod.ts +2 -0
  507. package/src/schema/state/sqlite/system-tables/state-tables.ts +109 -0
  508. package/src/schema/state/sqlite/table-def.test.ts +295 -46
  509. package/src/schema/state/sqlite/table-def.ts +109 -64
  510. package/src/schema/unknown-events.ts +132 -0
  511. package/src/schema-management/__tests__/migrations-autoincrement-quoting.test.ts +88 -0
  512. package/src/schema-management/common.ts +2 -2
  513. package/src/schema-management/migrations.ts +42 -9
  514. package/src/schema-management/validate-schema.ts +3 -3
  515. package/src/session-id-symbol.ts +59 -0
  516. package/src/sql-queries/sql-queries.ts +26 -18
  517. package/src/sql-queries/sql-query-builder.ts +1 -0
  518. package/src/sql-queries/types.ts +6 -7
  519. package/src/sqlite-db-helper.ts +67 -5
  520. package/src/sqlite-types.ts +9 -11
  521. package/src/sync/ClientSessionSyncProcessor.ts +388 -194
  522. package/src/sync/errors.ts +18 -28
  523. package/src/sync/index.ts +2 -0
  524. package/src/sync/mock-sync-backend.test.ts +78 -0
  525. package/src/sync/mock-sync-backend.ts +196 -108
  526. package/src/sync/next/compact-events.ts +10 -11
  527. package/src/sync/next/facts.ts +13 -14
  528. package/src/sync/next/history-dag-common.ts +306 -26
  529. package/src/sync/next/history-dag.ts +20 -13
  530. package/src/sync/next/rebase-events.ts +12 -12
  531. package/src/sync/next/test/compact-events.calculator.test.ts +5 -4
  532. package/src/sync/next/test/compact-events.test.ts +4 -3
  533. package/src/sync/next/test/event-fixtures.ts +14 -14
  534. package/src/sync/next/test/history-dag-common.test.ts +87 -0
  535. package/src/sync/sync-backend-kv.ts +7 -4
  536. package/src/sync/sync-backend.ts +75 -26
  537. package/src/sync/sync.ts +24 -2
  538. package/src/sync/syncstate.test.ts +657 -442
  539. package/src/sync/syncstate.ts +175 -160
  540. package/src/sync/transport-chunking.ts +85 -0
  541. package/src/sync/validate-push-payload.ts +6 -8
  542. package/src/testing/event-factory.ts +10 -12
  543. package/src/util.ts +20 -11
  544. package/src/version.ts +32 -8
  545. package/dist/schema/EventDef.d.ts +0 -126
  546. package/dist/schema/EventDef.d.ts.map +0 -1
  547. package/dist/schema/EventDef.js +0 -46
  548. package/dist/schema/EventDef.js.map +0 -1
  549. package/dist/schema/EventSequenceNumber.d.ts +0 -80
  550. package/dist/schema/EventSequenceNumber.d.ts.map +0 -1
  551. package/dist/schema/EventSequenceNumber.js +0 -139
  552. package/dist/schema/EventSequenceNumber.js.map +0 -1
  553. package/dist/schema/LiveStoreEvent.d.ts +0 -306
  554. package/dist/schema/LiveStoreEvent.d.ts.map +0 -1
  555. package/dist/schema/LiveStoreEvent.js +0 -147
  556. package/dist/schema/LiveStoreEvent.js.map +0 -1
  557. package/dist/schema/state/sqlite/system-tables.d.ts +0 -976
  558. package/dist/schema/state/sqlite/system-tables.d.ts.map +0 -1
  559. package/dist/schema/state/sqlite/system-tables.js +0 -81
  560. package/dist/schema/state/sqlite/system-tables.js.map +0 -1
  561. package/dist/sql-queries/misc.d.ts +0 -2
  562. package/dist/sql-queries/misc.d.ts.map +0 -1
  563. package/dist/sql-queries/misc.js +0 -2
  564. package/dist/sql-queries/misc.js.map +0 -1
  565. package/dist/sync/next/graphology.d.ts +0 -8
  566. package/dist/sync/next/graphology.d.ts.map +0 -1
  567. package/dist/sync/next/graphology.js +0 -30
  568. package/dist/sync/next/graphology.js.map +0 -1
  569. package/dist/sync/next/graphology_.d.ts +0 -3
  570. package/dist/sync/next/graphology_.d.ts.map +0 -1
  571. package/dist/sync/next/graphology_.js +0 -3
  572. package/dist/sync/next/graphology_.js.map +0 -1
  573. package/src/schema/EventDef.ts +0 -222
  574. package/src/schema/EventSequenceNumber.ts +0 -199
  575. package/src/schema/LiveStoreEvent.ts +0 -286
  576. package/src/schema/state/sqlite/system-tables.ts +0 -106
  577. package/src/sql-queries/misc.ts +0 -2
  578. package/src/sync/next/ambient.d.ts +0 -3
  579. package/src/sync/next/graphology.ts +0 -41
  580. package/src/sync/next/graphology_.ts +0 -2
@@ -1,26 +1,35 @@
1
1
  /// <reference lib="dom" />
2
- import { LS_DEV, shouldNeverHappen, TRACE_VERBOSE } from '@livestore/utils'
2
+ import { LS_DEV, TRACE_VERBOSE } from '@livestore/utils'
3
3
  import {
4
- BucketQueue,
4
+ Cause,
5
+ Deferred,
5
6
  Effect,
6
7
  Exit,
8
+ Filter,
7
9
  FiberHandle,
8
10
  Option,
9
11
  Queue,
10
- type Runtime,
11
12
  Schema,
13
+ Semaphore,
12
14
  type Scope,
13
15
  Stream,
14
16
  Subscribable,
17
+ TxQueue,
15
18
  } from '@livestore/utils/effect'
16
- import type * as otel from '@opentelemetry/api'
17
19
 
18
- import { type ClientSession, type MaterializeError, UnexpectedError } from '../adapter-types.ts'
19
- import * as EventSequenceNumber from '../schema/EventSequenceNumber.ts'
20
- import * as LiveStoreEvent from '../schema/LiveStoreEvent.ts'
21
- import { getEventDef, type LiveStoreSchema } from '../schema/mod.ts'
20
+ import type { ClientSession } from '../adapter-types.ts'
21
+ import { MaterializeError } from '../errors.ts'
22
+ import { isRejectedPushError } from '../leader-thread/RejectedPushError.ts'
23
+ import * as EventSequenceNumber from '../schema/EventSequenceNumber/mod.ts'
24
+ import * as LiveStoreEvent from '../schema/LiveStoreEvent/mod.ts'
25
+ import type { LiveStoreSchema } from '../schema/mod.ts'
26
+ import { resolveSessionIdSymbolInEventArgs } from '../session-id-symbol.ts'
27
+ import * as StateHead from '../StateHead.ts'
22
28
  import * as SyncState from './syncstate.ts'
23
29
 
30
+ /** Serialize value to JSON string for trace attributes */
31
+ const jsonStringify = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown))
32
+
24
33
  /**
25
34
  * Rebase behaviour:
26
35
  * - We continously pull events from the leader and apply them to the local store.
@@ -36,23 +45,20 @@ import * as SyncState from './syncstate.ts'
36
45
  * - The leader sync processor pulls regular LiveStore events, while the session sync processor pulls SyncState.PayloadUpstream items
37
46
  * - The session sync processor has no downstream nodes.
38
47
  */
39
- export const makeClientSessionSyncProcessor = ({
48
+ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncProcessor')(function* ({
40
49
  schema,
41
50
  clientSession,
42
- runtime,
43
51
  materializeEvent,
44
52
  rollback,
45
53
  refreshTables,
46
- span,
47
54
  params,
48
55
  confirmUnsavedChanges,
49
56
  }: {
50
57
  schema: LiveStoreSchema
51
58
  clientSession: ClientSession
52
- runtime: Runtime.Runtime<Scope.Scope>
53
59
  materializeEvent: (
54
- eventDecoded: LiveStoreEvent.AnyDecoded,
55
- options: { withChangeset: boolean; materializerHashLeader: Option.Option<number> },
60
+ eventEncoded: LiveStoreEvent.Client.EncodedWithMeta,
61
+ options: { materializerHashLeader: Option.Option<number> },
56
62
  ) => Effect.Effect<
57
63
  {
58
64
  writeTables: Set<string>
@@ -66,23 +72,27 @@ export const makeClientSessionSyncProcessor = ({
66
72
  >
67
73
  rollback: (changeset: Uint8Array<ArrayBuffer>) => void
68
74
  refreshTables: (tables: Set<string>) => void
69
- span: otel.Span
70
75
  params: {
71
76
  leaderPushBatchSize: number
72
- simulation?: ClientSessionSyncProcessorSimulationParams
77
+ /**
78
+ * Test-only deterministic barriers, awaited at fixed points of the rebase critical section.
79
+ * A test can park the pull fiber at a chosen point, inject a concurrent operation
80
+ * (a synchronous `push` or a `shutdown`), then release the barrier — without relying on
81
+ * virtual-time scheduling. Unset in production, where each lookup resolves to `Effect.void`.
82
+ */
83
+ rebaseBarriers?: Partial<Record<RebaseBarrierPoint, Effect.Effect<void>>>
73
84
  }
74
85
  /**
75
86
  * Currently only used in the web adapter:
76
87
  * If true, registers a beforeunload event listener to confirm unsaved changes.
77
88
  */
78
89
  confirmUnsavedChanges: boolean
79
- }): ClientSessionSyncProcessor => {
80
- const eventSchema = LiveStoreEvent.makeEventDefSchemaMemo(schema)
90
+ }) {
91
+ const stateHead = yield* StateHead.StateHead
92
+ const eventSchema = LiveStoreEvent.Client.makeSchemaMemo(schema)
81
93
 
82
- const simSleep = <TKey extends keyof ClientSessionSyncProcessorSimulationParams>(
83
- key: TKey,
84
- key2: keyof ClientSessionSyncProcessorSimulationParams[TKey],
85
- ) => Effect.sleep((params.simulation?.[key]?.[key2] ?? 0) as number)
94
+ const rebaseBarrier = (point: RebaseBarrierPoint): Effect.Effect<void> =>
95
+ params.rebaseBarriers?.[point] ?? Effect.void
86
96
 
87
97
  const syncStateRef = {
88
98
  // The initial state is identical to the leader's initial state
@@ -95,98 +105,50 @@ export const makeClientSessionSyncProcessor = ({
95
105
  }
96
106
 
97
107
  /** Only used for debugging / observability / testing, it's not relied upon for correctness of the sync processor. */
98
- const syncStateUpdateQueue = Queue.unbounded<SyncState.SyncState>().pipe(Effect.runSync)
99
- const isClientEvent = (eventEncoded: LiveStoreEvent.EncodedWithMeta) =>
100
- getEventDef(schema, eventEncoded.name).eventDef.options.clientOnly
108
+ const syncStateUpdateQueue = yield* Queue.unbounded<SyncState.SyncState>()
109
+ const isClientOnlyEvent = (eventEncoded: LiveStoreEvent.Client.EncodedWithMeta) =>
110
+ schema.eventsDefsMap.get(eventEncoded.name)?.options.clientOnly ?? false
101
111
 
102
112
  /** We're queuing push requests to reduce the number of messages sent to the leader by batching them */
103
- const leaderPushQueue = BucketQueue.make<LiveStoreEvent.EncodedWithMeta>().pipe(Effect.runSync)
104
-
105
- const push: ClientSessionSyncProcessor['push'] = Effect.fn('client-session-sync-processor:push')(function* (batch) {
106
- // TODO validate batch
107
-
108
- let baseEventSequenceNumber = syncStateRef.current.localHead
109
- const encodedEventDefs = batch.map(({ name, args }) => {
110
- const eventDef = getEventDef(schema, name)
111
- const nextNumPair = EventSequenceNumber.nextPair({
112
- seqNum: baseEventSequenceNumber,
113
- isClient: eventDef.eventDef.options.clientOnly,
114
- })
115
- baseEventSequenceNumber = nextNumPair.seqNum
116
- return new LiveStoreEvent.EncodedWithMeta(
117
- Schema.encodeUnknownSync(eventSchema)({
118
- name,
119
- args,
120
- ...nextNumPair,
121
- clientId: clientSession.clientId,
122
- sessionId: clientSession.sessionId,
123
- }),
124
- )
125
- })
126
-
127
- const mergeResult = SyncState.merge({
128
- syncState: syncStateRef.current,
129
- payload: { _tag: 'local-push', newEvents: encodedEventDefs },
130
- isClientEvent,
131
- isEqualEvent: LiveStoreEvent.isEqualEncoded,
132
- })
133
-
134
- yield* Effect.annotateCurrentSpan({
135
- batchSize: encodedEventDefs.length,
136
- mergeResultTag: mergeResult._tag,
137
- eventCounts: encodedEventDefs.reduce<Record<string, number>>((acc, event) => {
138
- acc[event.name] = (acc[event.name] ?? 0) + 1
139
- return acc
140
- }, {}),
141
- ...(TRACE_VERBOSE && { mergeResult: JSON.stringify(mergeResult) }),
142
- })
143
-
144
- if (mergeResult._tag === 'unexpected-error') {
145
- return shouldNeverHappen('Unexpected error in client-session-sync-processor', mergeResult.message)
146
- }
147
-
148
- if (mergeResult._tag !== 'advance') {
149
- return shouldNeverHappen(`Expected advance, got ${mergeResult._tag}`)
150
- }
151
-
152
- syncStateRef.current = mergeResult.newSyncState
153
- yield* syncStateUpdateQueue.offer(mergeResult.newSyncState)
154
-
155
- // Materialize events to state
156
- const writeTables = new Set<string>()
157
- for (const event of mergeResult.newEvents) {
158
- // TODO avoid encoding and decoding here again
159
- const decodedEventDef = Schema.decodeSync(eventSchema)(event)
160
- const {
161
- writeTables: newWriteTables,
162
- sessionChangeset,
163
- materializerHash,
164
- } = yield* materializeEvent(decodedEventDef, {
165
- withChangeset: true,
166
- materializerHashLeader: Option.none(),
167
- })
168
- for (const table of newWriteTables) {
169
- writeTables.add(table)
113
+ const leaderPushQueue = yield* TxQueue.unbounded<LiveStoreEvent.Client.EncodedWithMeta, Cause.Done>()
114
+ /**
115
+ * Prevents pull reconciliation, push-rejection handling, and shutdown from running concurrently.
116
+ * These transitions inspect or update the pending events, leader push queue, and rejection state,
117
+ * so each must observe the others either fully before or fully after—not midway through bookkeeping.
118
+ *
119
+ * Regular local commits do not acquire this mutex; they remain synchronous and are incorporated
120
+ * when `reconcileLeaderPushQueue` reads the current pending events transactionally.
121
+ */
122
+ const pullReconciliationMutex = yield* Semaphore.make(1)
123
+ const shutdownDone = yield* Deferred.make<void>()
124
+ const drainStartedSignal = yield* Deferred.make<void>()
125
+ const rejectionObserved = yield* Deferred.make<void>()
126
+ let shutdownStarted = false
127
+ let terminalPushCause: Cause.Cause<never> | undefined
128
+ let unresolvedRejection:
129
+ | {
130
+ readonly error: Error
131
+ readonly events: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>
170
132
  }
171
- event.meta.sessionChangeset = sessionChangeset
172
- event.meta.materializerHashSession = materializerHash
173
- }
174
-
175
- // Trigger push to leader
176
- // console.debug('pushToLeader', encodedEventDefs.length, ...encodedEventDefs.map((_) => _.toJSON()))
177
- yield* BucketQueue.offerAll(leaderPushQueue, encodedEventDefs)
178
-
179
- return { writeTables }
180
- })
181
-
182
- const debugInfo = {
183
- rebaseCount: 0,
184
- advanceCount: 0,
185
- rejectCount: 0,
186
- }
133
+ | undefined
134
+ let leaderPushingFiberHandle: FiberHandle.FiberHandle<void, never> | undefined
135
+ let pullingFiberHandle: FiberHandle.FiberHandle<void, never> | undefined
136
+
137
+ /** Rebuild the leader push queue from the current pending events without racing synchronous commits. */
138
+ const reconcileLeaderPushQueue = Effect.tx(
139
+ Effect.gen(function* () {
140
+ const livePending = syncStateRef.current.pending
141
+ yield* TxQueue.clear(leaderPushQueue)
142
+ yield* TxQueue.offerAll(leaderPushQueue, livePending)
143
+ }),
144
+ )
187
145
 
188
146
  const boot: ClientSessionSyncProcessor['boot'] = Effect.gen(function* () {
189
- if (confirmUnsavedChanges && typeof window !== 'undefined' && typeof window.addEventListener === 'function') {
147
+ if (
148
+ confirmUnsavedChanges === true &&
149
+ typeof window !== 'undefined' &&
150
+ typeof window.addEventListener === 'function'
151
+ ) {
190
152
  const onBeforeUnload = (event: BeforeUnloadEvent) => {
191
153
  if (syncStateRef.current.pending.length > 0) {
192
154
  // Trigger the default browser dialog
@@ -200,70 +162,117 @@ export const makeClientSessionSyncProcessor = ({
200
162
  )
201
163
  }
202
164
 
203
- const leaderPushingFiberHandle = yield* FiberHandle.make()
165
+ const leaderPushingHandle = yield* FiberHandle.make<void, never>()
166
+ const pullingHandle = yield* FiberHandle.make<void, never>()
167
+ leaderPushingFiberHandle = leaderPushingHandle
168
+ pullingFiberHandle = pullingHandle
169
+
170
+ const backgroundLeaderPushing: Effect.Effect<void> = Effect.gen(function* () {
171
+ while (true) {
172
+ const batch = yield* TxQueue.takeBetween(leaderPushQueue, 1, params.leaderPushBatchSize).pipe(
173
+ Effect.catchIf(Cause.isDone, () => Effect.void),
174
+ )
175
+ if (batch === undefined) return
176
+
177
+ yield* clientSession.leaderThread.events.push(batch).pipe(
178
+ Effect.catchIf(isRejectedPushError, (error) =>
179
+ // A pull carrying the leader's corrective history can complete before or after this
180
+ // rejection response. Use the same mutex as pull reconciliation so this decision uses
181
+ // one stable view of pending events and cannot install a stale rejection fence after recovery.
182
+ pullReconciliationMutex
183
+ .withPermits(1)(
184
+ Effect.gen(function* () {
185
+ debugInfo.rejectCount++
186
+ if (shutdownStarted === true) return yield* Effect.die(error)
187
+
188
+ // A concurrent pull may have already confirmed or rebased this batch while the
189
+ // push response was in flight. In that case, rebuild the queue from the reconciled
190
+ // pending events instead of creating a fence that no future pull could release.
191
+ if (isRejectedBatchRecovered(batch, syncStateRef.current.pending) === true) {
192
+ yield* reconcileLeaderPushQueue
193
+ yield* Deferred.succeed(rejectionObserved, undefined)
194
+ return false
195
+ }
196
+
197
+ unresolvedRejection = { error, events: batch }
198
+ yield* TxQueue.clear(leaderPushQueue)
199
+ yield* Deferred.succeed(rejectionObserved, undefined)
200
+ return true
201
+ }),
202
+ )
203
+ .pipe(
204
+ Effect.flatMap((shouldFence) =>
205
+ // Local commits remain synchronous and continue accumulating in pending/the FIFO,
206
+ // but this sole drain worker stays parked until pull reconciliation reseeds it.
207
+ shouldFence === true ? Effect.never : Effect.void,
208
+ ),
209
+ ),
210
+ ),
211
+ )
212
+ }
213
+ }).pipe(
214
+ Effect.interruptible,
215
+ Effect.tapCauseLogPretty,
216
+ Effect.catchCause((cause) => {
217
+ if (Cause.hasInterruptsOnly(cause) === true) return Effect.void
204
218
 
205
- const backgroundLeaderPushing = Effect.gen(function* () {
206
- const batch = yield* BucketQueue.takeBetween(leaderPushQueue, 1, params.leaderPushBatchSize)
207
- yield* clientSession.leaderThread.events.push(batch).pipe(
208
- Effect.catchTag('LeaderAheadError', () => {
209
- debugInfo.rejectCount++
210
- return BucketQueue.clear(leaderPushQueue)
211
- }),
212
- )
213
- }).pipe(Effect.forever, Effect.interruptible, Effect.tapCauseLogPretty)
219
+ terminalPushCause ??= cause
220
+ return shutdownStarted === true
221
+ ? Effect.void
222
+ : clientSession.shutdown(Exit.failCause(cause)).pipe(Effect.forkDetach, Effect.asVoid)
223
+ }),
224
+ )
214
225
 
215
- yield* FiberHandle.run(leaderPushingFiberHandle, backgroundLeaderPushing)
226
+ yield* FiberHandle.run(leaderPushingHandle, backgroundLeaderPushing)
216
227
 
217
228
  // NOTE We need to lazily call `.pull` as we want the cursor to be updated
218
- yield* Stream.suspend(() =>
229
+ const backgroundPulling = Stream.suspend(() =>
219
230
  clientSession.leaderThread.events.pull({ cursor: syncStateRef.current.upstreamHead }),
220
231
  ).pipe(
232
+ Stream.tap(() =>
233
+ clientSession.devtools.enabled === true ? clientSession.devtools.pullLatch.await : Effect.void,
234
+ ),
221
235
  Stream.tap(({ payload }) =>
222
236
  Effect.gen(function* () {
223
237
  // yield* Effect.logDebug('ClientSessionSyncProcessor:pull', payload)
224
238
 
225
- if (clientSession.devtools.enabled) {
226
- yield* clientSession.devtools.pullLatch.await
227
- }
228
-
229
- const mergeResult = SyncState.merge({
239
+ const rejectionAtPullStart = unresolvedRejection
240
+ const mergeResult = yield* SyncState.merge({
230
241
  syncState: syncStateRef.current,
231
242
  payload,
232
- isClientEvent,
233
- isEqualEvent: LiveStoreEvent.isEqualEncoded,
234
- })
235
-
236
- if (mergeResult._tag === 'unexpected-error') {
237
- return yield* new UnexpectedError({ cause: mergeResult.message })
238
- } else if (mergeResult._tag === 'reject') {
239
- return shouldNeverHappen('Unexpected reject in client-session-sync-processor', mergeResult)
240
- }
243
+ isClientOnlyEvent,
244
+ isEqualEvent: LiveStoreEvent.Client.isEqualEncoded,
245
+ }).pipe(
246
+ Effect.filterOrElse(
247
+ (r) => r._tag !== 'reject',
248
+ () => Effect.die(new Error('Unexpected reject in client-session-sync-processor')),
249
+ ),
250
+ )
241
251
 
242
252
  syncStateRef.current = mergeResult.newSyncState
243
253
 
254
+ const recoveredRejection =
255
+ rejectionAtPullStart !== undefined &&
256
+ unresolvedRejection === rejectionAtPullStart &&
257
+ isRejectedBatchRecovered(rejectionAtPullStart.events, mergeResult.newSyncState.pending) === true
258
+
244
259
  if (mergeResult._tag === 'rebase') {
245
- span.addEvent('merge:pull:rebase', {
260
+ yield* Effect.spanEvent('merge:pull:rebase', {
246
261
  payloadTag: payload._tag,
247
- payload: TRACE_VERBOSE ? JSON.stringify(payload) : undefined,
262
+ ...(TRACE_VERBOSE === true ? { payload: jsonStringify(payload) } : {}),
248
263
  newEventsCount: mergeResult.newEvents.length,
249
264
  rollbackCount: mergeResult.rollbackEvents.length,
250
- res: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
265
+ ...(TRACE_VERBOSE === true ? { res: jsonStringify(mergeResult) } : {}),
251
266
  })
252
267
 
253
268
  debugInfo.rebaseCount++
254
269
 
255
- if (SIMULATION_ENABLED) yield* simSleep('pull', '1_before_leader_push_fiber_interrupt')
256
-
257
- yield* FiberHandle.clear(leaderPushingFiberHandle)
258
-
259
- if (SIMULATION_ENABLED) yield* simSleep('pull', '2_before_leader_push_queue_clear')
270
+ // Barrier: before we interrupt the in-flight leader-push worker.
271
+ yield* rebaseBarrier('before_leader_push_fiber_interrupt')
260
272
 
261
- // Reset the leader push queue since we're rebasing and will push again
262
- yield* BucketQueue.clear(leaderPushQueue)
273
+ yield* FiberHandle.clear(leaderPushingHandle)
263
274
 
264
- if (SIMULATION_ENABLED) yield* simSleep('pull', '3_before_rebase_rollback')
265
-
266
- if (LS_DEV) {
275
+ if (LS_DEV === true) {
267
276
  yield* Effect.logDebug(
268
277
  'merge:pull:rebase: rollback',
269
278
  mergeResult.rollbackEvents.length,
@@ -271,6 +280,8 @@ export const makeClientSessionSyncProcessor = ({
271
280
  )
272
281
  }
273
282
 
283
+ // Roll back the optimistic session changesets for the events this rebase discards.
284
+ // (Independent of the push queue below; order relative to the queue reconcile is irrelevant.)
274
285
  for (let i = mergeResult.rollbackEvents.length - 1; i >= 0; i--) {
275
286
  const event = mergeResult.rollbackEvents[i]!
276
287
  if (event.meta.sessionChangeset._tag !== 'no-op' && event.meta.sessionChangeset._tag !== 'unset') {
@@ -278,41 +289,68 @@ export const makeClientSessionSyncProcessor = ({
278
289
  event.meta.sessionChangeset = { _tag: 'unset' }
279
290
  }
280
291
  }
292
+ if (mergeResult.rollbackEvents.length > 0) {
293
+ yield* stateHead
294
+ .set(mergeResult.rollbackEvents[0]!.parentSeqNum)
295
+ .pipe(Effect.mapError((cause) => MaterializeError.make({ cause })))
296
+ }
281
297
 
282
- if (SIMULATION_ENABLED) yield* simSleep('pull', '4_before_leader_push_queue_offer')
283
-
284
- yield* BucketQueue.offerAll(leaderPushQueue, mergeResult.newSyncState.pending)
285
-
286
- if (SIMULATION_ENABLED) yield* simSleep('pull', '5_before_leader_push_fiber_run')
287
-
288
- yield* FiberHandle.run(leaderPushingFiberHandle, backgroundLeaderPushing)
298
+ // Barrier: before the atomic queue reconciliation (the "discard + re-offer" step).
299
+ // A `push` admitted here appends its event to `syncStateRef.current.pending` AND to
300
+ // `leaderPushQueue`; the reconciliation below re-reads the current pending events, so
301
+ // that event is preserved rather than removed by the queue clear.
302
+ yield* rebaseBarrier('before_queue_reconcile')
303
+
304
+ // Atomic queue reconciliation. `push` runs via `Effect.runSyncWith` (a synchronous run,
305
+ // per the store's fully-synchronous commit contract), so it executes as an indivisible
306
+ // unit that can only interleave in THIS fiber's async gaps — never inside a synchronous
307
+ // stretch. By reading the current pending events, clearing, and re-offering with no async
308
+ // park between them, this block is atomic w.r.t. `push`. This replaces the blocking
309
+ // `pullReconciliationMutex` permit that previously forced `push` to wait: queue rebuilding
310
+ // and synchronous commits cannot interleave, without suspending the commit path.
311
+ //
312
+ // We re-read the current `syncStateRef.current.pending` instead of the stale
313
+ // `mergeResult.newSyncState.pending` snapshot captured at merge time, so any event a
314
+ // concurrent push appended during the async steps above (fiber interrupt / rollback /
315
+ // barriers) is included. `Effect.tx` commits the clear+offer as one transaction.
316
+ yield* reconcileLeaderPushQueue
317
+
318
+ // Barrier: before restarting the leader-push worker.
319
+ yield* rebaseBarrier('before_leader_push_fiber_run')
320
+
321
+ if (recoveredRejection === true) unresolvedRejection = undefined
322
+ yield* FiberHandle.run(leaderPushingHandle, backgroundLeaderPushing)
289
323
  } else {
290
- span.addEvent('merge:pull:advance', {
324
+ yield* Effect.spanEvent('merge:pull:advance', {
291
325
  payloadTag: payload._tag,
292
- payload: TRACE_VERBOSE ? JSON.stringify(payload) : undefined,
326
+ ...(TRACE_VERBOSE === true ? { payload: jsonStringify(payload) } : {}),
293
327
  newEventsCount: mergeResult.newEvents.length,
294
- res: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
328
+ ...(TRACE_VERBOSE === true ? { res: jsonStringify(mergeResult) } : {}),
295
329
  })
296
330
 
297
331
  debugInfo.advanceCount++
332
+
333
+ if (recoveredRejection === true) {
334
+ yield* FiberHandle.clear(leaderPushingHandle)
335
+ yield* reconcileLeaderPushQueue
336
+ unresolvedRejection = undefined
337
+ yield* FiberHandle.run(leaderPushingHandle, backgroundLeaderPushing)
338
+ }
298
339
  }
299
340
 
300
341
  if (mergeResult.newEvents.length === 0) {
301
342
  // If there are no new events, we need to update the sync state as well
302
- yield* syncStateUpdateQueue.offer(mergeResult.newSyncState)
343
+ yield* Queue.offer(syncStateUpdateQueue, mergeResult.newSyncState)
303
344
  return
304
345
  }
305
346
 
306
347
  const writeTables = new Set<string>()
307
348
  for (const event of mergeResult.newEvents) {
308
- // TODO apply changeset if available (will require tracking of write tables as well)
309
- const decodedEventDef = Schema.decodeSync(eventSchema)(event)
310
349
  const {
311
350
  writeTables: newWriteTables,
312
351
  sessionChangeset,
313
352
  materializerHash,
314
- } = yield* materializeEvent(decodedEventDef, {
315
- withChangeset: true,
353
+ } = yield* materializeEvent(event, {
316
354
  materializerHashLeader: event.meta.materializerHashLeader,
317
355
  })
318
356
  for (const table of newWriteTables) {
@@ -326,10 +364,11 @@ export const makeClientSessionSyncProcessor = ({
326
364
  refreshTables(writeTables)
327
365
 
328
366
  // We're only triggering the sync state update after all events have been materialized
329
- yield* syncStateUpdateQueue.offer(mergeResult.newSyncState)
367
+ yield* Queue.offer(syncStateUpdateQueue, mergeResult.newSyncState)
330
368
  }).pipe(
369
+ pullReconciliationMutex.withPermits(1),
331
370
  Effect.tapCauseLogPretty,
332
- Effect.catchAllCause((cause) => clientSession.shutdown(Exit.failCause(cause))),
371
+ Effect.catchCause((cause) => clientSession.shutdown(Exit.failCause(cause))),
333
372
  ),
334
373
  ),
335
374
  Stream.runDrain,
@@ -337,49 +376,210 @@ export const makeClientSessionSyncProcessor = ({
337
376
  Effect.interruptible,
338
377
  Effect.withSpan('client-session-sync-processor:pull'),
339
378
  Effect.tapCauseLogPretty,
340
- Effect.forkScoped,
341
379
  )
380
+ yield* FiberHandle.run(pullingHandle, backgroundPulling)
381
+ }).pipe(Effect.withSpan('client-session-sync-processor:boot'))
382
+
383
+ const runShutdown = Effect.fn('client-session-sync-processor:shutdown')(function* (
384
+ exit: Exit.Exit<unknown, unknown>,
385
+ ) {
386
+ if (Exit.isFailure(exit) === true) {
387
+ if (pullingFiberHandle !== undefined) yield* FiberHandle.clear(pullingFiberHandle)
388
+ yield* pullReconciliationMutex.withPermits(1)(TxQueue.end(leaderPushQueue))
389
+ if (leaderPushingFiberHandle !== undefined) yield* FiberHandle.clear(leaderPushingFiberHandle)
390
+ return
391
+ }
392
+
393
+ yield* pullReconciliationMutex.withPermits(1)(
394
+ Effect.gen(function* () {
395
+ if (pullingFiberHandle !== undefined) yield* FiberHandle.clear(pullingFiberHandle)
396
+ yield* TxQueue.end(leaderPushQueue)
397
+ yield* Deferred.succeed(drainStartedSignal, undefined)
398
+ }),
399
+ )
400
+ if (leaderPushingFiberHandle !== undefined) {
401
+ if (unresolvedRejection === undefined) {
402
+ yield* FiberHandle.awaitEmpty(leaderPushingFiberHandle)
403
+ } else {
404
+ yield* FiberHandle.clear(leaderPushingFiberHandle)
405
+ }
406
+ }
407
+ if (terminalPushCause !== undefined) return yield* Effect.failCause(terminalPushCause)
408
+ if (unresolvedRejection !== undefined) return yield* Effect.die(unresolvedRejection.error)
342
409
  })
343
410
 
411
+ const shutdown: ClientSessionSyncProcessor['shutdown'] = (exit) =>
412
+ Effect.suspend(() => {
413
+ if (shutdownStarted === true) return Deferred.await(shutdownDone)
414
+ shutdownStarted = true
415
+ return runShutdown(exit).pipe(
416
+ Effect.exit,
417
+ Effect.tap((shutdownExit) => Deferred.done(shutdownDone, shutdownExit)),
418
+ Effect.forkDetach,
419
+ Effect.andThen(Deferred.await(shutdownDone)),
420
+ )
421
+ })
422
+
423
+ const encodeEvents: ClientSessionSyncProcessor['encodeEvents'] = Effect.fn(
424
+ 'client-session-sync-processor:encode-events',
425
+ )(function* (events) {
426
+ let baseEventSequenceNumber = syncStateRef.current.localHead
427
+ return yield* Effect.forEach(events, ({ name, args }) =>
428
+ Effect.gen(function* () {
429
+ const eventDef = yield* Effect.fromNullishOr(schema.eventsDefsMap.get(name)).pipe(Effect.orDieDebugger)
430
+ const nextNumPair = EventSequenceNumber.Client.nextPair({
431
+ seqNum: baseEventSequenceNumber,
432
+ isClientOnly: eventDef.options.clientOnly,
433
+ rebaseGeneration: baseEventSequenceNumber.rebaseGeneration,
434
+ })
435
+ baseEventSequenceNumber = nextNumPair.seqNum
436
+ // Encoding known-valid domain data: an encode failure is an invariant violation (a defect),
437
+ // so `Effect.orDie` is the correct modeling — it keeps the typed error channel narrow.
438
+ const encoded = yield* Schema.encodeUnknownEffect(eventSchema)({
439
+ name,
440
+ // Client-document events expose SessionIdSymbol as an input placeholder, but encoded events are persisted
441
+ // and replayed by concrete id. Resolve during schema encoding so commit never mutates the caller's event.
442
+ args: resolveSessionIdSymbolInEventArgs(args, clientSession.sessionId),
443
+ ...nextNumPair,
444
+ clientId: clientSession.clientId,
445
+ sessionId: clientSession.sessionId,
446
+ }).pipe(Effect.orDie)
447
+ return new LiveStoreEvent.Client.EncodedWithMeta(encoded)
448
+ }),
449
+ )
450
+ })
451
+
452
+ const materializeEvents: ClientSessionSyncProcessor['materializeEvents'] = Effect.fn(
453
+ 'client-session-sync-processor:materialize-events',
454
+ )(function* (events) {
455
+ const writeTables = new Set<string>()
456
+ for (const event of events) {
457
+ const {
458
+ writeTables: newWriteTables,
459
+ sessionChangeset,
460
+ materializerHash,
461
+ } = yield* materializeEvent(event, {
462
+ materializerHashLeader: Option.none(),
463
+ })
464
+ for (const table of newWriteTables) {
465
+ writeTables.add(table)
466
+ }
467
+ event.meta.sessionChangeset = sessionChangeset
468
+ event.meta.materializerHashSession = materializerHash
469
+ }
470
+ return { writeTables }
471
+ })
472
+
473
+ const push: ClientSessionSyncProcessor['push'] = Effect.fn('client-session-sync-processor:push')(
474
+ function* (encodedEvents) {
475
+ if (shutdownStarted === true) {
476
+ return yield* Effect.die(
477
+ new Error('Cannot push events after the client session sync processor starts shutting down'),
478
+ )
479
+ }
480
+
481
+ const mergeResult = yield* SyncState.merge({
482
+ syncState: syncStateRef.current,
483
+ payload: { _tag: 'local-push', newEvents: encodedEvents },
484
+ isClientOnlyEvent,
485
+ isEqualEvent: LiveStoreEvent.Client.isEqualEncoded,
486
+ }).pipe(
487
+ Effect.filterMapOrElse(Filter.tagged<typeof SyncState.MergeResult.Type>()('advance'), () =>
488
+ Effect.die(new Error('Expected advance from local-push merge')),
489
+ ),
490
+ )
491
+
492
+ yield* Effect.annotateCurrentSpan({
493
+ batchSize: encodedEvents.length,
494
+ mergeResultTag: mergeResult._tag,
495
+ eventCounts: encodedEvents.reduce<Record<string, number>>((acc, event) => {
496
+ acc[event.name] = (acc[event.name] ?? 0) + 1
497
+ return acc
498
+ }, {}),
499
+ ...(TRACE_VERBOSE === true ? { mergeResult: jsonStringify(mergeResult) } : {}),
500
+ })
501
+
502
+ syncStateRef.current = mergeResult.newSyncState
503
+ yield* Queue.offer(syncStateUpdateQueue, mergeResult.newSyncState)
504
+ const rejectedEvents = yield* TxQueue.offerAll(leaderPushQueue, mergeResult.newEvents)
505
+ if (rejectedEvents.length > 0) {
506
+ return yield* Effect.die(new Error('Leader push queue closed while accepting events'))
507
+ }
508
+ },
509
+ )
510
+
511
+ const debugInfo = {
512
+ rebaseCount: 0,
513
+ advanceCount: 0,
514
+ rejectCount: 0,
515
+ }
516
+
344
517
  return {
345
- push,
346
518
  boot,
519
+ shutdown,
520
+ encodeEvents,
521
+ materializeEvents,
522
+ push,
347
523
  syncState: Subscribable.make({
348
- get: Effect.gen(function* () {
349
- const syncState = syncStateRef.current
350
- if (syncStateRef === undefined) return shouldNeverHappen('Not initialized')
351
- return syncState
352
- }),
524
+ get: Effect.sync(() => syncStateRef.current),
353
525
  changes: Stream.fromQueue(syncStateUpdateQueue),
354
526
  }),
355
527
  debug: {
528
+ awaitDrainStarted: Deferred.await(drainStartedSignal),
529
+ awaitRejection: Deferred.await(rejectionObserved),
356
530
  print: () =>
357
531
  Effect.gen(function* () {
358
532
  console.log('debugInfo', debugInfo)
359
533
  console.log('syncState', syncStateRef.current)
360
- const pushQueueSize = yield* BucketQueue.size(leaderPushQueue)
361
- console.log('pushQueueSize', pushQueueSize)
362
- const pushQueueItems = yield* BucketQueue.peekAll(leaderPushQueue)
534
+ const pushQueueItems = yield* snapshotTxQueue(leaderPushQueue)
535
+ console.log('pushQueueSize', pushQueueItems.length)
363
536
  console.log(
364
537
  'pushQueueItems',
365
538
  pushQueueItems.map((_) => _.toJSON()),
366
539
  )
367
- }).pipe(Effect.provide(runtime), Effect.runSync),
540
+ }).pipe(Effect.runSync),
368
541
  debugInfo: () => debugInfo,
369
542
  },
370
543
  } satisfies ClientSessionSyncProcessor
371
- }
544
+ })
545
+
546
+ const snapshotTxQueue = <A, E>(queue: TxQueue.TxQueue<A, E>): Effect.Effect<ReadonlyArray<A>, E> =>
547
+ Effect.tx(
548
+ Effect.gen(function* () {
549
+ if ((yield* TxQueue.isOpen(queue)) === false) return []
550
+
551
+ const items = yield* TxQueue.clear(queue)
552
+ yield* TxQueue.offerAll(queue, items)
553
+ return items
554
+ }),
555
+ )
556
+
557
+ const isRejectedBatchRecovered = (
558
+ rejectedEvents: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>,
559
+ pendingEvents: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>,
560
+ ): boolean =>
561
+ rejectedEvents.every(
562
+ (rejectedEvent) =>
563
+ pendingEvents.some((pendingEvent) => LiveStoreEvent.Client.isEqualEncoded(pendingEvent, rejectedEvent)) === false,
564
+ )
372
565
 
373
566
  export interface ClientSessionSyncProcessor {
374
- push: (
375
- batch: ReadonlyArray<LiveStoreEvent.PartialAnyDecoded>,
567
+ boot: Effect.Effect<void, never, Scope.Scope>
568
+ shutdown: (exit: Exit.Exit<unknown, unknown>) => Effect.Effect<void>
569
+ encodeEvents: (
570
+ events: ReadonlyArray<LiveStoreEvent.Input.Decoded>,
571
+ ) => Effect.Effect<ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>>
572
+ push: (events: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>) => Effect.Effect<void>
573
+ materializeEvents: (
574
+ events: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>,
376
575
  ) => Effect.Effect<{ writeTables: Set<string> }, MaterializeError>
377
- boot: Effect.Effect<void, UnexpectedError, Scope.Scope>
378
576
  /**
379
577
  * Only used for debugging / observability.
380
578
  */
381
579
  syncState: Subscribable.Subscribable<SyncState.SyncState>
382
580
  debug: {
581
+ awaitDrainStarted: Effect.Effect<void>
582
+ awaitRejection: Effect.Effect<void>
383
583
  print: () => void
384
584
  debugInfo: () => {
385
585
  rebaseCount: number
@@ -388,17 +588,11 @@ export interface ClientSessionSyncProcessor {
388
588
  }
389
589
  }
390
590
 
391
- // TODO turn this into a build-time "macro" so all simulation snippets are removed for production builds
392
- const SIMULATION_ENABLED = true
393
-
394
- // Warning: High values for the simulation params can lead to very long test runs since those get multiplied with the number of events
395
- export const ClientSessionSyncProcessorSimulationParams = Schema.Struct({
396
- pull: Schema.Struct({
397
- '1_before_leader_push_fiber_interrupt': Schema.Int.pipe(Schema.between(0, 15)),
398
- '2_before_leader_push_queue_clear': Schema.Int.pipe(Schema.between(0, 15)),
399
- '3_before_rebase_rollback': Schema.Int.pipe(Schema.between(0, 15)),
400
- '4_before_leader_push_queue_offer': Schema.Int.pipe(Schema.between(0, 15)),
401
- '5_before_leader_push_fiber_run': Schema.Int.pipe(Schema.between(0, 15)),
402
- }),
403
- })
404
- type ClientSessionSyncProcessorSimulationParams = typeof ClientSessionSyncProcessorSimulationParams.Type
591
+ /**
592
+ * Injection points inside the rebase critical section where a test-only barrier may be awaited.
593
+ * Named for the step they precede so the deterministic no-loss tests can target the exact window.
594
+ */
595
+ export type RebaseBarrierPoint =
596
+ | 'before_leader_push_fiber_interrupt'
597
+ | 'before_queue_reconcile'
598
+ | 'before_leader_push_fiber_run'