@livestore/common 0.4.0 → 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 (421) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/ClientSessionLeaderThreadProxy.d.ts +1 -1
  3. package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
  4. package/dist/MaterializationJournal.d.ts +44 -0
  5. package/dist/MaterializationJournal.d.ts.map +1 -0
  6. package/dist/MaterializationJournal.js +112 -0
  7. package/dist/MaterializationJournal.js.map +1 -0
  8. package/dist/StateHead.d.ts +26 -0
  9. package/dist/StateHead.d.ts.map +1 -0
  10. package/dist/StateHead.js +50 -0
  11. package/dist/StateHead.js.map +1 -0
  12. package/dist/WorkerTransportError.d.ts +3 -3
  13. package/dist/WorkerTransportError.d.ts.map +1 -1
  14. package/dist/WorkerTransportError.js +3 -3
  15. package/dist/WorkerTransportError.js.map +1 -1
  16. package/dist/__tests__/fixture.d.ts +1 -1
  17. package/dist/__tests__/fixture.d.ts.map +1 -1
  18. package/dist/__tests__/fixture.js +3 -3
  19. package/dist/__tests__/fixture.js.map +1 -1
  20. package/dist/adapter-types.d.ts +29 -57
  21. package/dist/adapter-types.d.ts.map +1 -1
  22. package/dist/adapter-types.js +20 -37
  23. package/dist/adapter-types.js.map +1 -1
  24. package/dist/debug-info.d.ts +56 -56
  25. package/dist/debug-info.d.ts.map +1 -1
  26. package/dist/debug-info.js +28 -41
  27. package/dist/debug-info.js.map +1 -1
  28. package/dist/defs.d.ts +9 -9
  29. package/dist/defs.js +2 -2
  30. package/dist/devtools/devtools-compatibility.test.js +1 -0
  31. package/dist/devtools/devtools-compatibility.test.js.map +1 -1
  32. package/dist/devtools/devtools-messages-client-session.d.ts +520 -332
  33. package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -1
  34. package/dist/devtools/devtools-messages-client-session.js +76 -74
  35. package/dist/devtools/devtools-messages-client-session.js.map +1 -1
  36. package/dist/devtools/devtools-messages-common.d.ts +27 -27
  37. package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
  38. package/dist/devtools/devtools-messages-common.js +5 -5
  39. package/dist/devtools/devtools-messages-common.js.map +1 -1
  40. package/dist/devtools/devtools-messages-leader.d.ts +405 -343
  41. package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
  42. package/dist/devtools/devtools-messages-leader.js +101 -89
  43. package/dist/devtools/devtools-messages-leader.js.map +1 -1
  44. package/dist/devtools/devtools-sessioninfo.d.ts +18 -19
  45. package/dist/devtools/devtools-sessioninfo.d.ts.map +1 -1
  46. package/dist/devtools/devtools-sessioninfo.js +8 -5
  47. package/dist/devtools/devtools-sessioninfo.js.map +1 -1
  48. package/dist/devtools/mod.d.ts +4 -5
  49. package/dist/devtools/mod.d.ts.map +1 -1
  50. package/dist/devtools/mod.js +9 -5
  51. package/dist/devtools/mod.js.map +1 -1
  52. package/dist/errors.d.ts +45 -69
  53. package/dist/errors.d.ts.map +1 -1
  54. package/dist/errors.js +18 -16
  55. package/dist/errors.js.map +1 -1
  56. package/dist/index.d.ts +4 -1
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +4 -1
  59. package/dist/index.js.map +1 -1
  60. package/dist/leader-thread/LeaderSyncProcessor.d.ts +66 -18
  61. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
  62. package/dist/leader-thread/LeaderSyncProcessor.js +519 -461
  63. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  64. package/dist/leader-thread/RejectedPushError.d.ts +67 -36
  65. package/dist/leader-thread/RejectedPushError.d.ts.map +1 -1
  66. package/dist/leader-thread/RejectedPushError.js +24 -5
  67. package/dist/leader-thread/RejectedPushError.js.map +1 -1
  68. package/dist/leader-thread/connection.d.ts +1 -0
  69. package/dist/leader-thread/connection.d.ts.map +1 -1
  70. package/dist/leader-thread/connection.js +9 -8
  71. package/dist/leader-thread/connection.js.map +1 -1
  72. package/dist/leader-thread/eventlog.d.ts +4 -4
  73. package/dist/leader-thread/eventlog.d.ts.map +1 -1
  74. package/dist/leader-thread/eventlog.js +6 -6
  75. package/dist/leader-thread/eventlog.js.map +1 -1
  76. package/dist/leader-thread/leader-worker-devtools.d.ts +1 -1
  77. package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
  78. package/dist/leader-thread/leader-worker-devtools.js +33 -17
  79. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  80. package/dist/leader-thread/make-leader-thread-layer.d.ts +9 -5
  81. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  82. package/dist/leader-thread/make-leader-thread-layer.js +25 -35
  83. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  84. package/dist/leader-thread/make-leader-thread-layer.test.js +7 -7
  85. package/dist/leader-thread/make-leader-thread-layer.test.js.map +1 -1
  86. package/dist/leader-thread/materialize-event.d.ts +2 -1
  87. package/dist/leader-thread/materialize-event.d.ts.map +1 -1
  88. package/dist/leader-thread/materialize-event.js +4 -0
  89. package/dist/leader-thread/materialize-event.js.map +1 -1
  90. package/dist/leader-thread/recreate-db.d.ts.map +1 -1
  91. package/dist/leader-thread/recreate-db.js +18 -39
  92. package/dist/leader-thread/recreate-db.js.map +1 -1
  93. package/dist/leader-thread/shutdown-channel.d.ts +2 -6
  94. package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
  95. package/dist/leader-thread/shutdown-channel.js +2 -3
  96. package/dist/leader-thread/shutdown-channel.js.map +1 -1
  97. package/dist/leader-thread/stream-events.d.ts +4 -7
  98. package/dist/leader-thread/stream-events.d.ts.map +1 -1
  99. package/dist/leader-thread/stream-events.js +13 -16
  100. package/dist/leader-thread/stream-events.js.map +1 -1
  101. package/dist/leader-thread/types.d.ts +32 -69
  102. package/dist/leader-thread/types.d.ts.map +1 -1
  103. package/dist/leader-thread/types.js +5 -5
  104. package/dist/leader-thread/types.js.map +1 -1
  105. package/dist/logging.d.ts +5 -30
  106. package/dist/logging.d.ts.map +1 -1
  107. package/dist/logging.js +1 -32
  108. package/dist/logging.js.map +1 -1
  109. package/dist/make-client-session.d.ts +1 -2
  110. package/dist/make-client-session.d.ts.map +1 -1
  111. package/dist/make-client-session.js +3 -3
  112. package/dist/make-client-session.js.map +1 -1
  113. package/dist/materializer-helper.d.ts +0 -1
  114. package/dist/materializer-helper.d.ts.map +1 -1
  115. package/dist/materializer-helper.js +7 -35
  116. package/dist/materializer-helper.js.map +1 -1
  117. package/dist/otel.d.ts +2 -3
  118. package/dist/otel.d.ts.map +1 -1
  119. package/dist/otel.js +2 -7
  120. package/dist/otel.js.map +1 -1
  121. package/dist/rematerialize-from-eventlog.d.ts.map +1 -1
  122. package/dist/rematerialize-from-eventlog.js +28 -26
  123. package/dist/rematerialize-from-eventlog.js.map +1 -1
  124. package/dist/schema/EventDef/define.d.ts +4 -4
  125. package/dist/schema/EventDef/define.d.ts.map +1 -1
  126. package/dist/schema/EventDef/define.js +5 -5
  127. package/dist/schema/EventDef/define.js.map +1 -1
  128. package/dist/schema/EventDef/define.test.d.ts +2 -0
  129. package/dist/schema/EventDef/define.test.d.ts.map +1 -0
  130. package/dist/schema/EventDef/define.test.js +23 -0
  131. package/dist/schema/EventDef/define.test.js.map +1 -0
  132. package/dist/schema/EventDef/deprecated.d.ts +8 -13
  133. package/dist/schema/EventDef/deprecated.d.ts.map +1 -1
  134. package/dist/schema/EventDef/deprecated.js +12 -13
  135. package/dist/schema/EventDef/deprecated.js.map +1 -1
  136. package/dist/schema/EventDef/deprecated.test.js +1 -1
  137. package/dist/schema/EventDef/deprecated.test.js.map +1 -1
  138. package/dist/schema/EventDef/event-def.d.ts +1 -1
  139. package/dist/schema/EventDef/event-def.d.ts.map +1 -1
  140. package/dist/schema/EventSequenceNumber/client.d.ts +12 -12
  141. package/dist/schema/EventSequenceNumber/client.d.ts.map +1 -1
  142. package/dist/schema/EventSequenceNumber/client.js +8 -9
  143. package/dist/schema/EventSequenceNumber/client.js.map +1 -1
  144. package/dist/schema/EventSequenceNumber/global.d.ts +2 -2
  145. package/dist/schema/EventSequenceNumber/global.d.ts.map +1 -1
  146. package/dist/schema/EventSequenceNumber/global.js +1 -1
  147. package/dist/schema/EventSequenceNumber/global.js.map +1 -1
  148. package/dist/schema/EventSequenceNumber.test.js +5 -5
  149. package/dist/schema/EventSequenceNumber.test.js.map +1 -1
  150. package/dist/schema/LiveStoreEvent/client.d.ts +50 -179
  151. package/dist/schema/LiveStoreEvent/client.d.ts.map +1 -1
  152. package/dist/schema/LiveStoreEvent/client.js +32 -29
  153. package/dist/schema/LiveStoreEvent/client.js.map +1 -1
  154. package/dist/schema/LiveStoreEvent/client.test.js +11 -2
  155. package/dist/schema/LiveStoreEvent/client.test.js.map +1 -1
  156. package/dist/schema/LiveStoreEvent/for-event-def.d.ts +7 -7
  157. package/dist/schema/LiveStoreEvent/for-event-def.d.ts.map +1 -1
  158. package/dist/schema/LiveStoreEvent/global.d.ts +6 -6
  159. package/dist/schema/LiveStoreEvent/global.d.ts.map +1 -1
  160. package/dist/schema/LiveStoreEvent/global.js +1 -1
  161. package/dist/schema/LiveStoreEvent/global.js.map +1 -1
  162. package/dist/schema/LiveStoreEvent/input.d.ts +5 -5
  163. package/dist/schema/LiveStoreEvent/input.d.ts.map +1 -1
  164. package/dist/schema/LiveStoreEvent/input.js +3 -3
  165. package/dist/schema/LiveStoreEvent/input.js.map +1 -1
  166. package/dist/schema/mod.d.ts +1 -0
  167. package/dist/schema/mod.d.ts.map +1 -1
  168. package/dist/schema/mod.js +1 -0
  169. package/dist/schema/mod.js.map +1 -1
  170. package/dist/schema/schema.d.ts.map +1 -1
  171. package/dist/schema/schema.js +3 -2
  172. package/dist/schema/schema.js.map +1 -1
  173. package/dist/schema/state/sqlite/client-document-def.d.ts +12 -17
  174. package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
  175. package/dist/schema/state/sqlite/client-document-def.js +23 -20
  176. package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
  177. package/dist/schema/state/sqlite/client-document-def.test.js +29 -8
  178. package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -1
  179. package/dist/schema/state/sqlite/column-annotations.d.ts +13 -13
  180. package/dist/schema/state/sqlite/column-annotations.d.ts.map +1 -1
  181. package/dist/schema/state/sqlite/column-annotations.js +13 -15
  182. package/dist/schema/state/sqlite/column-annotations.js.map +1 -1
  183. package/dist/schema/state/sqlite/column-annotations.test.js +19 -43
  184. package/dist/schema/state/sqlite/column-annotations.test.js.map +1 -1
  185. package/dist/schema/state/sqlite/column-def.d.ts +1 -1
  186. package/dist/schema/state/sqlite/column-def.d.ts.map +1 -1
  187. package/dist/schema/state/sqlite/column-def.js +57 -31
  188. package/dist/schema/state/sqlite/column-def.js.map +1 -1
  189. package/dist/schema/state/sqlite/column-def.test.js +118 -72
  190. package/dist/schema/state/sqlite/column-def.test.js.map +1 -1
  191. package/dist/schema/state/sqlite/column-spec.js.map +1 -1
  192. package/dist/schema/state/sqlite/column-spec.test.js +2 -2
  193. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +2 -2
  194. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -1
  195. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +13 -4
  196. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -1
  197. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts +9 -9
  198. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts.map +1 -1
  199. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +8 -13
  200. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -1
  201. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js +27 -16
  202. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js.map +1 -1
  203. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts +13 -11
  204. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -1
  205. package/dist/schema/state/sqlite/db-schema/dsl/mod.js +5 -3
  206. package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
  207. package/dist/schema/state/sqlite/mod.d.ts +1 -3
  208. package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
  209. package/dist/schema/state/sqlite/mod.js +1 -1
  210. package/dist/schema/state/sqlite/mod.js.map +1 -1
  211. package/dist/schema/state/sqlite/persistence.d.ts +11 -0
  212. package/dist/schema/state/sqlite/persistence.d.ts.map +1 -0
  213. package/dist/schema/state/sqlite/persistence.js +10 -0
  214. package/dist/schema/state/sqlite/persistence.js.map +1 -0
  215. package/dist/schema/state/sqlite/query-builder/api.d.ts +7 -7
  216. package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
  217. package/dist/schema/state/sqlite/query-builder/astToSql.d.ts.map +1 -1
  218. package/dist/schema/state/sqlite/query-builder/astToSql.js +27 -23
  219. package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -1
  220. package/dist/schema/state/sqlite/query-builder/impl.d.ts +3 -1
  221. package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
  222. package/dist/schema/state/sqlite/query-builder/impl.js +16 -14
  223. package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
  224. package/dist/schema/state/sqlite/query-builder/impl.test.js +362 -596
  225. package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
  226. package/dist/schema/state/sqlite/schema-helpers.js +1 -1
  227. package/dist/schema/state/sqlite/schema-helpers.js.map +1 -1
  228. package/dist/schema/state/sqlite/system-tables/eventlog-tables.d.ts +102 -134
  229. package/dist/schema/state/sqlite/system-tables/eventlog-tables.d.ts.map +1 -1
  230. package/dist/schema/state/sqlite/system-tables/eventlog-tables.js +2 -2
  231. package/dist/schema/state/sqlite/system-tables/eventlog-tables.js.map +1 -1
  232. package/dist/schema/state/sqlite/system-tables/state-tables.d.ts +328 -106
  233. package/dist/schema/state/sqlite/system-tables/state-tables.d.ts.map +1 -1
  234. package/dist/schema/state/sqlite/system-tables/state-tables.js +33 -1
  235. package/dist/schema/state/sqlite/system-tables/state-tables.js.map +1 -1
  236. package/dist/schema/state/sqlite/table-def.d.ts +16 -19
  237. package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
  238. package/dist/schema/state/sqlite/table-def.js +53 -3
  239. package/dist/schema/state/sqlite/table-def.js.map +1 -1
  240. package/dist/schema/state/sqlite/table-def.test.js +153 -36
  241. package/dist/schema/state/sqlite/table-def.test.js.map +1 -1
  242. package/dist/schema/unknown-events.d.ts.map +1 -1
  243. package/dist/schema/unknown-events.js +2 -1
  244. package/dist/schema/unknown-events.js.map +1 -1
  245. package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js +2 -2
  246. package/dist/session-id-symbol.d.ts +29 -0
  247. package/dist/session-id-symbol.d.ts.map +1 -0
  248. package/dist/session-id-symbol.js +44 -0
  249. package/dist/session-id-symbol.js.map +1 -0
  250. package/dist/sql-queries/sql-queries.d.ts.map +1 -1
  251. package/dist/sql-queries/sql-queries.js +8 -10
  252. package/dist/sql-queries/sql-queries.js.map +1 -1
  253. package/dist/sql-queries/types.d.ts +6 -7
  254. package/dist/sql-queries/types.d.ts.map +1 -1
  255. package/dist/sql-queries/types.js.map +1 -1
  256. package/dist/sqlite-db-helper.d.ts +21 -1
  257. package/dist/sqlite-db-helper.d.ts.map +1 -1
  258. package/dist/sqlite-db-helper.js +37 -2
  259. package/dist/sqlite-db-helper.js.map +1 -1
  260. package/dist/sqlite-types.d.ts +3 -6
  261. package/dist/sqlite-types.d.ts.map +1 -1
  262. package/dist/sqlite-types.js +3 -3
  263. package/dist/sqlite-types.js.map +1 -1
  264. package/dist/sync/ClientSessionSyncProcessor.d.ts +38 -16
  265. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  266. package/dist/sync/ClientSessionSyncProcessor.js +196 -66
  267. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  268. package/dist/sync/errors.d.ts +12 -18
  269. package/dist/sync/errors.d.ts.map +1 -1
  270. package/dist/sync/errors.js +2 -2
  271. package/dist/sync/errors.js.map +1 -1
  272. package/dist/sync/mock-sync-backend.d.ts +2 -3
  273. package/dist/sync/mock-sync-backend.d.ts.map +1 -1
  274. package/dist/sync/mock-sync-backend.js +54 -33
  275. package/dist/sync/mock-sync-backend.js.map +1 -1
  276. package/dist/sync/mock-sync-backend.test.d.ts +2 -0
  277. package/dist/sync/mock-sync-backend.test.d.ts.map +1 -0
  278. package/dist/sync/mock-sync-backend.test.js +49 -0
  279. package/dist/sync/mock-sync-backend.test.js.map +1 -0
  280. package/dist/sync/next/history-dag-common.d.ts +4 -0
  281. package/dist/sync/next/history-dag-common.d.ts.map +1 -1
  282. package/dist/sync/next/history-dag-common.js +52 -31
  283. package/dist/sync/next/history-dag-common.js.map +1 -1
  284. package/dist/sync/next/history-dag.d.ts.map +1 -1
  285. package/dist/sync/next/history-dag.js +2 -1
  286. package/dist/sync/next/history-dag.js.map +1 -1
  287. package/dist/sync/next/test/compact-events.calculator.test.js +2 -2
  288. package/dist/sync/next/test/event-fixtures.d.ts +7 -7
  289. package/dist/sync/next/test/event-fixtures.js +1 -1
  290. package/dist/sync/next/test/event-fixtures.js.map +1 -1
  291. package/dist/sync/next/test/history-dag-common.test.d.ts +2 -0
  292. package/dist/sync/next/test/history-dag-common.test.d.ts.map +1 -0
  293. package/dist/sync/next/test/history-dag-common.test.js +73 -0
  294. package/dist/sync/next/test/history-dag-common.test.js.map +1 -0
  295. package/dist/sync/sync-backend-kv.d.ts.map +1 -1
  296. package/dist/sync/sync-backend-kv.js +4 -2
  297. package/dist/sync/sync-backend-kv.js.map +1 -1
  298. package/dist/sync/sync-backend.d.ts +15 -15
  299. package/dist/sync/sync-backend.d.ts.map +1 -1
  300. package/dist/sync/sync-backend.js +9 -5
  301. package/dist/sync/sync-backend.js.map +1 -1
  302. package/dist/sync/sync.d.ts +1 -1
  303. package/dist/sync/sync.d.ts.map +1 -1
  304. package/dist/sync/syncstate.d.ts +97 -173
  305. package/dist/sync/syncstate.d.ts.map +1 -1
  306. package/dist/sync/syncstate.js +34 -37
  307. package/dist/sync/syncstate.js.map +1 -1
  308. package/dist/sync/syncstate.test.js +100 -61
  309. package/dist/sync/syncstate.test.js.map +1 -1
  310. package/dist/sync/transport-chunking.d.ts +15 -16
  311. package/dist/sync/transport-chunking.d.ts.map +1 -1
  312. package/dist/sync/transport-chunking.js +23 -32
  313. package/dist/sync/transport-chunking.js.map +1 -1
  314. package/dist/util.d.ts +11 -4
  315. package/dist/util.d.ts.map +1 -1
  316. package/dist/util.js +19 -2
  317. package/dist/util.js.map +1 -1
  318. package/dist/version.d.ts.map +1 -1
  319. package/dist/version.js.map +1 -1
  320. package/package.json +19 -37
  321. package/src/ClientSessionLeaderThreadProxy.ts +1 -1
  322. package/src/MaterializationJournal.ts +184 -0
  323. package/src/StateHead.ts +80 -0
  324. package/src/WorkerTransportError.ts +4 -4
  325. package/src/__tests__/fixture.ts +3 -3
  326. package/src/adapter-types.ts +24 -61
  327. package/src/debug-info.ts +50 -70
  328. package/src/defs.ts +2 -2
  329. package/src/devtools/devtools-compatibility.test.ts +1 -0
  330. package/src/devtools/devtools-messages-client-session.ts +56 -65
  331. package/src/devtools/devtools-messages-common.ts +12 -11
  332. package/src/devtools/devtools-messages-leader.ts +71 -69
  333. package/src/devtools/devtools-sessioninfo.ts +13 -11
  334. package/src/devtools/mod.ts +4 -5
  335. package/src/errors.ts +46 -34
  336. package/src/index.ts +8 -1
  337. package/src/leader-thread/LeaderSyncProcessor.ts +794 -691
  338. package/src/leader-thread/RejectedPushError.ts +40 -27
  339. package/src/leader-thread/connection.ts +12 -8
  340. package/src/leader-thread/eventlog.ts +7 -7
  341. package/src/leader-thread/leader-worker-devtools.ts +37 -19
  342. package/src/leader-thread/make-leader-thread-layer.test.ts +7 -7
  343. package/src/leader-thread/make-leader-thread-layer.ts +56 -53
  344. package/src/leader-thread/materialize-event.ts +13 -8
  345. package/src/leader-thread/recreate-db.ts +24 -59
  346. package/src/leader-thread/shutdown-channel.ts +3 -14
  347. package/src/leader-thread/stream-events.ts +104 -101
  348. package/src/leader-thread/types.ts +24 -62
  349. package/src/logging.ts +5 -52
  350. package/src/make-client-session.ts +6 -6
  351. package/src/materializer-helper.ts +8 -37
  352. package/src/otel.ts +5 -14
  353. package/src/rematerialize-from-eventlog.ts +97 -94
  354. package/src/schema/EventDef/define.test.ts +28 -0
  355. package/src/schema/EventDef/define.ts +20 -19
  356. package/src/schema/EventDef/deprecated.test.ts +1 -8
  357. package/src/schema/EventDef/deprecated.ts +17 -20
  358. package/src/schema/EventDef/event-def.ts +2 -4
  359. package/src/schema/EventSequenceNumber/client.ts +8 -9
  360. package/src/schema/EventSequenceNumber/global.ts +1 -1
  361. package/src/schema/EventSequenceNumber.test.ts +5 -5
  362. package/src/schema/LiveStoreEvent/client.test.ts +17 -4
  363. package/src/schema/LiveStoreEvent/client.ts +36 -35
  364. package/src/schema/LiveStoreEvent/for-event-def.ts +7 -7
  365. package/src/schema/LiveStoreEvent/global.ts +1 -1
  366. package/src/schema/LiveStoreEvent/input.ts +6 -6
  367. package/src/schema/mod.ts +1 -0
  368. package/src/schema/schema.ts +3 -2
  369. package/src/schema/state/sqlite/client-document-def.test.ts +32 -13
  370. package/src/schema/state/sqlite/client-document-def.ts +95 -80
  371. package/src/schema/state/sqlite/column-annotations.test.ts +26 -45
  372. package/src/schema/state/sqlite/column-annotations.ts +26 -30
  373. package/src/schema/state/sqlite/column-def.test.ts +132 -77
  374. package/src/schema/state/sqlite/column-def.ts +72 -37
  375. package/src/schema/state/sqlite/column-spec.test.ts +2 -2
  376. package/src/schema/state/sqlite/column-spec.ts +3 -2
  377. package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +15 -5
  378. package/src/schema/state/sqlite/db-schema/dsl/__snapshots__/field-defs.test.ts.snap +123 -63
  379. package/src/schema/state/sqlite/db-schema/dsl/field-defs.test.ts +35 -16
  380. package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +25 -31
  381. package/src/schema/state/sqlite/db-schema/dsl/mod.ts +35 -21
  382. package/src/schema/state/sqlite/mod.ts +2 -4
  383. package/src/schema/state/sqlite/persistence.ts +11 -0
  384. package/src/schema/state/sqlite/query-builder/api.ts +9 -9
  385. package/src/schema/state/sqlite/query-builder/astToSql.ts +31 -26
  386. package/src/schema/state/sqlite/query-builder/impl.test.ts +433 -669
  387. package/src/schema/state/sqlite/query-builder/impl.ts +19 -15
  388. package/src/schema/state/sqlite/schema-helpers.ts +1 -1
  389. package/src/schema/state/sqlite/system-tables/eventlog-tables.ts +2 -2
  390. package/src/schema/state/sqlite/system-tables/state-tables.ts +41 -1
  391. package/src/schema/state/sqlite/table-def.test.ts +204 -66
  392. package/src/schema/state/sqlite/table-def.ts +96 -45
  393. package/src/schema/unknown-events.ts +3 -2
  394. package/src/schema-management/__tests__/migrations-autoincrement-quoting.test.ts +2 -2
  395. package/src/session-id-symbol.ts +59 -0
  396. package/src/sql-queries/sql-queries.ts +8 -12
  397. package/src/sql-queries/types.ts +6 -7
  398. package/src/sqlite-db-helper.ts +64 -2
  399. package/src/sqlite-types.ts +5 -8
  400. package/src/sync/ClientSessionSyncProcessor.ts +303 -119
  401. package/src/sync/errors.ts +14 -12
  402. package/src/sync/mock-sync-backend.test.ts +78 -0
  403. package/src/sync/mock-sync-backend.ts +93 -47
  404. package/src/sync/next/history-dag-common.ts +60 -34
  405. package/src/sync/next/history-dag.ts +4 -1
  406. package/src/sync/next/test/compact-events.calculator.test.ts +2 -2
  407. package/src/sync/next/test/event-fixtures.ts +1 -1
  408. package/src/sync/next/test/history-dag-common.test.ts +87 -0
  409. package/src/sync/sync-backend-kv.ts +4 -2
  410. package/src/sync/sync-backend.ts +15 -15
  411. package/src/sync/sync.ts +1 -1
  412. package/src/sync/syncstate.test.ts +191 -140
  413. package/src/sync/syncstate.ts +63 -53
  414. package/src/sync/transport-chunking.ts +34 -39
  415. package/src/util.ts +18 -9
  416. package/src/version.ts +1 -2
  417. package/dist/sql-queries/misc.d.ts +0 -2
  418. package/dist/sql-queries/misc.d.ts.map +0 -1
  419. package/dist/sql-queries/misc.js +0 -2
  420. package/dist/sql-queries/misc.js.map +0 -1
  421. package/src/sql-queries/misc.ts +0 -2
@@ -1,28 +1,34 @@
1
1
  /// <reference lib="dom" />
2
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
12
  Schema,
13
+ Semaphore,
11
14
  type Scope,
12
15
  Stream,
13
16
  Subscribable,
17
+ TxQueue,
14
18
  } from '@livestore/utils/effect'
15
19
 
16
20
  import type { ClientSession } from '../adapter-types.ts'
17
- import type { MaterializeError } from '../errors.ts'
21
+ import { MaterializeError } from '../errors.ts'
18
22
  import { isRejectedPushError } from '../leader-thread/RejectedPushError.ts'
19
23
  import * as EventSequenceNumber from '../schema/EventSequenceNumber/mod.ts'
20
24
  import * as LiveStoreEvent from '../schema/LiveStoreEvent/mod.ts'
21
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
 
24
30
  /** Serialize value to JSON string for trace attributes */
25
- const jsonStringify = Schema.encodeSync(Schema.parseJson())
31
+ const jsonStringify = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown))
26
32
 
27
33
  /**
28
34
  * Rebase behaviour:
@@ -52,7 +58,7 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
52
58
  clientSession: ClientSession
53
59
  materializeEvent: (
54
60
  eventEncoded: LiveStoreEvent.Client.EncodedWithMeta,
55
- options: { withChangeset: boolean; materializerHashLeader: Option.Option<number> },
61
+ options: { materializerHashLeader: Option.Option<number> },
56
62
  ) => Effect.Effect<
57
63
  {
58
64
  writeTables: Set<string>
@@ -68,20 +74,25 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
68
74
  refreshTables: (tables: Set<string>) => void
69
75
  params: {
70
76
  leaderPushBatchSize: number
71
- 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>>>
72
84
  }
73
85
  /**
74
86
  * Currently only used in the web adapter:
75
87
  * If true, registers a beforeunload event listener to confirm unsaved changes.
76
88
  */
77
89
  confirmUnsavedChanges: boolean
78
- }): Effect.fn.Return<ClientSessionSyncProcessor> {
90
+ }) {
91
+ const stateHead = yield* StateHead.StateHead
79
92
  const eventSchema = LiveStoreEvent.Client.makeSchemaMemo(schema)
80
93
 
81
- const simSleep = <TKey extends keyof ClientSessionSyncProcessorSimulationParams>(
82
- key: TKey,
83
- key2: keyof ClientSessionSyncProcessorSimulationParams[TKey],
84
- ) => Effect.sleep((params.simulation?.[key]?.[key2] ?? 0) as number)
94
+ const rebaseBarrier = (point: RebaseBarrierPoint): Effect.Effect<void> =>
95
+ params.rebaseBarriers?.[point] ?? Effect.void
85
96
 
86
97
  const syncStateRef = {
87
98
  // The initial state is identical to the leader's initial state
@@ -95,13 +106,44 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
95
106
 
96
107
  /** Only used for debugging / observability / testing, it's not relied upon for correctness of the sync processor. */
97
108
  const syncStateUpdateQueue = yield* Queue.unbounded<SyncState.SyncState>()
98
- const isClientEvent = (eventEncoded: LiveStoreEvent.Client.EncodedWithMeta) =>
109
+ const isClientOnlyEvent = (eventEncoded: LiveStoreEvent.Client.EncodedWithMeta) =>
99
110
  schema.eventsDefsMap.get(eventEncoded.name)?.options.clientOnly ?? false
100
111
 
101
112
  /** We're queuing push requests to reduce the number of messages sent to the leader by batching them */
102
- const leaderPushQueue = yield* BucketQueue.make<LiveStoreEvent.Client.EncodedWithMeta>()
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>
132
+ }
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
+ )
103
145
 
104
- const boot: ClientSessionSyncProcessor['boot'] = Effect.fn('client-session-sync-processor:boot')(function* () {
146
+ const boot: ClientSessionSyncProcessor['boot'] = Effect.gen(function* () {
105
147
  if (
106
148
  confirmUnsavedChanges === true &&
107
149
  typeof window !== 'undefined' &&
@@ -120,51 +162,100 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
120
162
  )
121
163
  }
122
164
 
123
- 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
124
169
 
125
- const backgroundLeaderPushing = Effect.gen(function* () {
126
- const batch = yield* BucketQueue.takeBetween(leaderPushQueue, 1, params.leaderPushBatchSize)
127
- yield* clientSession.leaderThread.events.push(batch).pipe(
128
- Effect.catchIf(isRejectedPushError, () => {
129
- debugInfo.rejectCount++
130
- return BucketQueue.clear(leaderPushQueue)
131
- }),
132
- )
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
+ }
133
213
  }).pipe(
134
- Effect.forever,
135
214
  Effect.interruptible,
136
215
  Effect.tapCauseLogPretty,
137
- Effect.catchAllCause((cause) => clientSession.shutdown(Exit.failCause(cause))),
216
+ Effect.catchCause((cause) => {
217
+ if (Cause.hasInterruptsOnly(cause) === true) return Effect.void
218
+
219
+ terminalPushCause ??= cause
220
+ return shutdownStarted === true
221
+ ? Effect.void
222
+ : clientSession.shutdown(Exit.failCause(cause)).pipe(Effect.forkDetach, Effect.asVoid)
223
+ }),
138
224
  )
139
225
 
140
- yield* FiberHandle.run(leaderPushingFiberHandle, backgroundLeaderPushing)
226
+ yield* FiberHandle.run(leaderPushingHandle, backgroundLeaderPushing)
141
227
 
142
228
  // NOTE We need to lazily call `.pull` as we want the cursor to be updated
143
- yield* Stream.suspend(() =>
229
+ const backgroundPulling = Stream.suspend(() =>
144
230
  clientSession.leaderThread.events.pull({ cursor: syncStateRef.current.upstreamHead }),
145
231
  ).pipe(
232
+ Stream.tap(() =>
233
+ clientSession.devtools.enabled === true ? clientSession.devtools.pullLatch.await : Effect.void,
234
+ ),
146
235
  Stream.tap(({ payload }) =>
147
236
  Effect.gen(function* () {
148
237
  // yield* Effect.logDebug('ClientSessionSyncProcessor:pull', payload)
149
238
 
150
- if (clientSession.devtools.enabled === true) {
151
- yield* clientSession.devtools.pullLatch.await
152
- }
153
-
239
+ const rejectionAtPullStart = unresolvedRejection
154
240
  const mergeResult = yield* SyncState.merge({
155
241
  syncState: syncStateRef.current,
156
242
  payload,
157
- isClientEvent,
243
+ isClientOnlyEvent,
158
244
  isEqualEvent: LiveStoreEvent.Client.isEqualEncoded,
159
245
  }).pipe(
160
- Effect.filterOrDieMessage(
246
+ Effect.filterOrElse(
161
247
  (r) => r._tag !== 'reject',
162
- 'Unexpected reject in client-session-sync-processor',
248
+ () => Effect.die(new Error('Unexpected reject in client-session-sync-processor')),
163
249
  ),
164
250
  )
165
251
 
166
252
  syncStateRef.current = mergeResult.newSyncState
167
253
 
254
+ const recoveredRejection =
255
+ rejectionAtPullStart !== undefined &&
256
+ unresolvedRejection === rejectionAtPullStart &&
257
+ isRejectedBatchRecovered(rejectionAtPullStart.events, mergeResult.newSyncState.pending) === true
258
+
168
259
  if (mergeResult._tag === 'rebase') {
169
260
  yield* Effect.spanEvent('merge:pull:rebase', {
170
261
  payloadTag: payload._tag,
@@ -176,16 +267,10 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
176
267
 
177
268
  debugInfo.rebaseCount++
178
269
 
179
- if (SIMULATION_ENABLED === true) yield* simSleep('pull', '1_before_leader_push_fiber_interrupt')
270
+ // Barrier: before we interrupt the in-flight leader-push worker.
271
+ yield* rebaseBarrier('before_leader_push_fiber_interrupt')
180
272
 
181
- yield* FiberHandle.clear(leaderPushingFiberHandle)
182
-
183
- if (SIMULATION_ENABLED === true) yield* simSleep('pull', '2_before_leader_push_queue_clear')
184
-
185
- // Reset the leader push queue since we're rebasing and will push again
186
- yield* BucketQueue.clear(leaderPushQueue)
187
-
188
- if (SIMULATION_ENABLED === true) yield* simSleep('pull', '3_before_rebase_rollback')
273
+ yield* FiberHandle.clear(leaderPushingHandle)
189
274
 
190
275
  if (LS_DEV === true) {
191
276
  yield* Effect.logDebug(
@@ -195,6 +280,8 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
195
280
  )
196
281
  }
197
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.)
198
285
  for (let i = mergeResult.rollbackEvents.length - 1; i >= 0; i--) {
199
286
  const event = mergeResult.rollbackEvents[i]!
200
287
  if (event.meta.sessionChangeset._tag !== 'no-op' && event.meta.sessionChangeset._tag !== 'unset') {
@@ -202,14 +289,37 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
202
289
  event.meta.sessionChangeset = { _tag: 'unset' }
203
290
  }
204
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
+ }
205
297
 
206
- if (SIMULATION_ENABLED === true) yield* simSleep('pull', '4_before_leader_push_queue_offer')
207
-
208
- yield* BucketQueue.offerAll(leaderPushQueue, mergeResult.newSyncState.pending)
209
-
210
- if (SIMULATION_ENABLED === true) yield* simSleep('pull', '5_before_leader_push_fiber_run')
211
-
212
- 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)
213
323
  } else {
214
324
  yield* Effect.spanEvent('merge:pull:advance', {
215
325
  payloadTag: payload._tag,
@@ -219,11 +329,18 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
219
329
  })
220
330
 
221
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
+ }
222
339
  }
223
340
 
224
341
  if (mergeResult.newEvents.length === 0) {
225
342
  // If there are no new events, we need to update the sync state as well
226
- yield* syncStateUpdateQueue.offer(mergeResult.newSyncState)
343
+ yield* Queue.offer(syncStateUpdateQueue, mergeResult.newSyncState)
227
344
  return
228
345
  }
229
346
 
@@ -234,7 +351,6 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
234
351
  sessionChangeset,
235
352
  materializerHash,
236
353
  } = yield* materializeEvent(event, {
237
- withChangeset: true,
238
354
  materializerHashLeader: event.meta.materializerHashLeader,
239
355
  })
240
356
  for (const table of newWriteTables) {
@@ -248,10 +364,11 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
248
364
  refreshTables(writeTables)
249
365
 
250
366
  // We're only triggering the sync state update after all events have been materialized
251
- yield* syncStateUpdateQueue.offer(mergeResult.newSyncState)
367
+ yield* Queue.offer(syncStateUpdateQueue, mergeResult.newSyncState)
252
368
  }).pipe(
369
+ pullReconciliationMutex.withPermits(1),
253
370
  Effect.tapCauseLogPretty,
254
- Effect.catchAllCause((cause) => clientSession.shutdown(Exit.failCause(cause))),
371
+ Effect.catchCause((cause) => clientSession.shutdown(Exit.failCause(cause))),
255
372
  ),
256
373
  ),
257
374
  Stream.runDrain,
@@ -259,39 +376,82 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
259
376
  Effect.interruptible,
260
377
  Effect.withSpan('client-session-sync-processor:pull'),
261
378
  Effect.tapCauseLogPretty,
262
- Effect.forkScoped,
263
379
  )
264
- })()
380
+ yield* FiberHandle.run(pullingHandle, backgroundPulling)
381
+ }).pipe(Effect.withSpan('client-session-sync-processor:boot'))
265
382
 
266
- const encodeEvents: ClientSessionSyncProcessor['encodeEvents'] = Effect.fn('client-session-sync-processor:encode-events')(function* (
267
- events,
383
+ const runShutdown = Effect.fn('client-session-sync-processor:shutdown')(function* (
384
+ exit: Exit.Exit<unknown, unknown>,
268
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)
409
+ })
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) {
269
426
  let baseEventSequenceNumber = syncStateRef.current.localHead
270
427
  return yield* Effect.forEach(events, ({ name, args }) =>
271
428
  Effect.gen(function* () {
272
- const eventDef = yield* Effect.fromNullable(schema.eventsDefsMap.get(name)).pipe(Effect.orDieDebugger)
429
+ const eventDef = yield* Effect.fromNullishOr(schema.eventsDefsMap.get(name)).pipe(Effect.orDieDebugger)
273
430
  const nextNumPair = EventSequenceNumber.Client.nextPair({
274
431
  seqNum: baseEventSequenceNumber,
275
- isClient: eventDef.options.clientOnly,
432
+ isClientOnly: eventDef.options.clientOnly,
276
433
  rebaseGeneration: baseEventSequenceNumber.rebaseGeneration,
277
434
  })
278
435
  baseEventSequenceNumber = nextNumPair.seqNum
279
- return new LiveStoreEvent.Client.EncodedWithMeta(
280
- Schema.encodeUnknownSync(eventSchema)({
281
- name,
282
- args,
283
- ...nextNumPair,
284
- clientId: clientSession.clientId,
285
- sessionId: clientSession.sessionId,
286
- }),
287
- )
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)
288
448
  }),
289
449
  )
290
450
  })
291
451
 
292
- const materializeEvents: ClientSessionSyncProcessor['materializeEvents'] = Effect.fn('client-session-sync-processor:materialize-events')(function* (
293
- events,
294
- ) {
452
+ const materializeEvents: ClientSessionSyncProcessor['materializeEvents'] = Effect.fn(
453
+ 'client-session-sync-processor:materialize-events',
454
+ )(function* (events) {
295
455
  const writeTables = new Set<string>()
296
456
  for (const event of events) {
297
457
  const {
@@ -299,7 +459,6 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
299
459
  sessionChangeset,
300
460
  materializerHash,
301
461
  } = yield* materializeEvent(event, {
302
- withChangeset: true,
303
462
  materializerHashLeader: Option.none(),
304
463
  })
305
464
  for (const table of newWriteTables) {
@@ -311,35 +470,43 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
311
470
  return { writeTables }
312
471
  })
313
472
 
314
- const push: ClientSessionSyncProcessor['push'] = Effect.fn('client-session-sync-processor:push')(function* (
315
- encodedEvents,
316
- ) {
317
- const mergeResult = yield* SyncState.merge({
318
- syncState: syncStateRef.current,
319
- payload: { _tag: 'local-push', newEvents: encodedEvents },
320
- isClientEvent,
321
- isEqualEvent: LiveStoreEvent.Client.isEqualEncoded,
322
- }).pipe(
323
- Effect.filterOrDieMessage(
324
- (r) => r._tag === 'advance',
325
- 'Expected advance from local-push merge',
326
- ),
327
- )
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
+ }
328
480
 
329
- yield* Effect.annotateCurrentSpan({
330
- batchSize: encodedEvents.length,
331
- mergeResultTag: mergeResult._tag,
332
- eventCounts: encodedEvents.reduce<Record<string, number>>((acc, event) => {
333
- acc[event.name] = (acc[event.name] ?? 0) + 1
334
- return acc
335
- }, {}),
336
- ...(TRACE_VERBOSE === true ? { mergeResult: jsonStringify(mergeResult) } : {}),
337
- })
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
+ )
338
491
 
339
- syncStateRef.current = mergeResult.newSyncState
340
- yield* syncStateUpdateQueue.offer(mergeResult.newSyncState)
341
- yield* BucketQueue.offerAll(leaderPushQueue, mergeResult.newEvents)
342
- })
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
+ )
343
510
 
344
511
  const debugInfo = {
345
512
  rebaseCount: 0,
@@ -349,6 +516,7 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
349
516
 
350
517
  return {
351
518
  boot,
519
+ shutdown,
352
520
  encodeEvents,
353
521
  materializeEvents,
354
522
  push,
@@ -357,13 +525,14 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
357
525
  changes: Stream.fromQueue(syncStateUpdateQueue),
358
526
  }),
359
527
  debug: {
528
+ awaitDrainStarted: Deferred.await(drainStartedSignal),
529
+ awaitRejection: Deferred.await(rejectionObserved),
360
530
  print: () =>
361
531
  Effect.gen(function* () {
362
532
  console.log('debugInfo', debugInfo)
363
533
  console.log('syncState', syncStateRef.current)
364
- const pushQueueSize = yield* BucketQueue.size(leaderPushQueue)
365
- console.log('pushQueueSize', pushQueueSize)
366
- const pushQueueItems = yield* BucketQueue.peekAll(leaderPushQueue)
534
+ const pushQueueItems = yield* snapshotTxQueue(leaderPushQueue)
535
+ console.log('pushQueueSize', pushQueueItems.length)
367
536
  console.log(
368
537
  'pushQueueItems',
369
538
  pushQueueItems.map((_) => _.toJSON()),
@@ -374,14 +543,33 @@ export const makeClientSessionSyncProcessor = Effect.fn('makeClientSessionSyncPr
374
543
  } satisfies ClientSessionSyncProcessor
375
544
  })
376
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
+ )
565
+
377
566
  export interface ClientSessionSyncProcessor {
378
567
  boot: Effect.Effect<void, never, Scope.Scope>
568
+ shutdown: (exit: Exit.Exit<unknown, unknown>) => Effect.Effect<void>
379
569
  encodeEvents: (
380
570
  events: ReadonlyArray<LiveStoreEvent.Input.Decoded>,
381
571
  ) => Effect.Effect<ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>>
382
- push: (
383
- events: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>,
384
- ) => Effect.Effect<void>
572
+ push: (events: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>) => Effect.Effect<void>
385
573
  materializeEvents: (
386
574
  events: ReadonlyArray<LiveStoreEvent.Client.EncodedWithMeta>,
387
575
  ) => Effect.Effect<{ writeTables: Set<string> }, MaterializeError>
@@ -390,6 +578,8 @@ export interface ClientSessionSyncProcessor {
390
578
  */
391
579
  syncState: Subscribable.Subscribable<SyncState.SyncState>
392
580
  debug: {
581
+ awaitDrainStarted: Effect.Effect<void>
582
+ awaitRejection: Effect.Effect<void>
393
583
  print: () => void
394
584
  debugInfo: () => {
395
585
  rebaseCount: number
@@ -398,17 +588,11 @@ export interface ClientSessionSyncProcessor {
398
588
  }
399
589
  }
400
590
 
401
- // TODO turn this into a build-time "macro" so all simulation snippets are removed for production builds
402
- const SIMULATION_ENABLED = true
403
-
404
- // Warning: High values for the simulation params can lead to very long test runs since those get multiplied with the number of events
405
- export const ClientSessionSyncProcessorSimulationParams = Schema.Struct({
406
- pull: Schema.Struct({
407
- '1_before_leader_push_fiber_interrupt': Schema.Int.pipe(Schema.between(0, 15)),
408
- '2_before_leader_push_queue_clear': Schema.Int.pipe(Schema.between(0, 15)),
409
- '3_before_rebase_rollback': Schema.Int.pipe(Schema.between(0, 15)),
410
- '4_before_leader_push_queue_offer': Schema.Int.pipe(Schema.between(0, 15)),
411
- '5_before_leader_push_fiber_run': Schema.Int.pipe(Schema.between(0, 15)),
412
- }),
413
- })
414
- 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'