@livestore/common 0.3.0-dev.4 → 0.3.0-dev.41

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 (470) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/__tests__/fixture.d.ts +83 -221
  3. package/dist/__tests__/fixture.d.ts.map +1 -1
  4. package/dist/__tests__/fixture.js +33 -11
  5. package/dist/__tests__/fixture.js.map +1 -1
  6. package/dist/adapter-types.d.ts +132 -75
  7. package/dist/adapter-types.d.ts.map +1 -1
  8. package/dist/adapter-types.js +36 -7
  9. package/dist/adapter-types.js.map +1 -1
  10. package/dist/bounded-collections.d.ts.map +1 -1
  11. package/dist/debug-info.d.ts +1 -1
  12. package/dist/debug-info.d.ts.map +1 -1
  13. package/dist/debug-info.js +1 -0
  14. package/dist/debug-info.js.map +1 -1
  15. package/dist/devtools/devtools-messages-client-session.d.ts +390 -0
  16. package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -0
  17. package/dist/devtools/devtools-messages-client-session.js +97 -0
  18. package/dist/devtools/devtools-messages-client-session.js.map +1 -0
  19. package/dist/devtools/devtools-messages-common.d.ts +68 -0
  20. package/dist/devtools/devtools-messages-common.d.ts.map +1 -0
  21. package/dist/devtools/devtools-messages-common.js +60 -0
  22. package/dist/devtools/devtools-messages-common.js.map +1 -0
  23. package/dist/devtools/devtools-messages-leader.d.ts +394 -0
  24. package/dist/devtools/devtools-messages-leader.d.ts.map +1 -0
  25. package/dist/devtools/devtools-messages-leader.js +147 -0
  26. package/dist/devtools/devtools-messages-leader.js.map +1 -0
  27. package/dist/devtools/devtools-messages.d.ts +3 -592
  28. package/dist/devtools/devtools-messages.d.ts.map +1 -1
  29. package/dist/devtools/devtools-messages.js +3 -171
  30. package/dist/devtools/devtools-messages.js.map +1 -1
  31. package/dist/devtools/devtools-sessioninfo.d.ts +32 -0
  32. package/dist/devtools/devtools-sessioninfo.d.ts.map +1 -0
  33. package/dist/devtools/devtools-sessioninfo.js +36 -0
  34. package/dist/devtools/devtools-sessioninfo.js.map +1 -0
  35. package/dist/devtools/mod.d.ts +55 -0
  36. package/dist/devtools/mod.d.ts.map +1 -0
  37. package/dist/devtools/mod.js +33 -0
  38. package/dist/devtools/mod.js.map +1 -0
  39. package/dist/index.d.ts +7 -13
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +7 -9
  42. package/dist/index.js.map +1 -1
  43. package/dist/leader-thread/LeaderSyncProcessor.d.ts +62 -0
  44. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -0
  45. package/dist/leader-thread/LeaderSyncProcessor.js +593 -0
  46. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -0
  47. package/dist/leader-thread/connection.d.ts +34 -6
  48. package/dist/leader-thread/connection.d.ts.map +1 -1
  49. package/dist/leader-thread/connection.js +22 -7
  50. package/dist/leader-thread/connection.js.map +1 -1
  51. package/dist/leader-thread/eventlog.d.ts +27 -0
  52. package/dist/leader-thread/eventlog.d.ts.map +1 -0
  53. package/dist/leader-thread/eventlog.js +119 -0
  54. package/dist/leader-thread/eventlog.js.map +1 -0
  55. package/dist/leader-thread/leader-worker-devtools.d.ts +1 -1
  56. package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
  57. package/dist/leader-thread/leader-worker-devtools.js +165 -134
  58. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  59. package/dist/leader-thread/make-leader-thread-layer.d.ts +26 -12
  60. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  61. package/dist/leader-thread/make-leader-thread-layer.js +76 -48
  62. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  63. package/dist/leader-thread/materialize-event.d.ts +16 -0
  64. package/dist/leader-thread/materialize-event.d.ts.map +1 -0
  65. package/dist/leader-thread/materialize-event.js +105 -0
  66. package/dist/leader-thread/materialize-event.js.map +1 -0
  67. package/dist/leader-thread/mod.d.ts +1 -1
  68. package/dist/leader-thread/mod.d.ts.map +1 -1
  69. package/dist/leader-thread/mod.js +1 -1
  70. package/dist/leader-thread/mod.js.map +1 -1
  71. package/dist/leader-thread/recreate-db.d.ts +4 -2
  72. package/dist/leader-thread/recreate-db.d.ts.map +1 -1
  73. package/dist/leader-thread/recreate-db.js +33 -31
  74. package/dist/leader-thread/recreate-db.js.map +1 -1
  75. package/dist/leader-thread/shutdown-channel.d.ts +2 -5
  76. package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
  77. package/dist/leader-thread/shutdown-channel.js +2 -4
  78. package/dist/leader-thread/shutdown-channel.js.map +1 -1
  79. package/dist/leader-thread/types.d.ts +89 -40
  80. package/dist/leader-thread/types.d.ts.map +1 -1
  81. package/dist/leader-thread/types.js +1 -3
  82. package/dist/leader-thread/types.js.map +1 -1
  83. package/dist/make-client-session.d.ts +21 -0
  84. package/dist/make-client-session.d.ts.map +1 -0
  85. package/dist/make-client-session.js +51 -0
  86. package/dist/make-client-session.js.map +1 -0
  87. package/dist/materializer-helper.d.ts +23 -0
  88. package/dist/materializer-helper.d.ts.map +1 -0
  89. package/dist/materializer-helper.js +84 -0
  90. package/dist/materializer-helper.js.map +1 -0
  91. package/dist/otel.d.ts +2 -0
  92. package/dist/otel.d.ts.map +1 -1
  93. package/dist/otel.js +5 -0
  94. package/dist/otel.js.map +1 -1
  95. package/dist/rematerialize-from-eventlog.d.ts +14 -0
  96. package/dist/rematerialize-from-eventlog.d.ts.map +1 -0
  97. package/dist/rematerialize-from-eventlog.js +64 -0
  98. package/dist/rematerialize-from-eventlog.js.map +1 -0
  99. package/dist/schema/EventDef.d.ts +146 -0
  100. package/dist/schema/EventDef.d.ts.map +1 -0
  101. package/dist/schema/EventDef.js +58 -0
  102. package/dist/schema/EventDef.js.map +1 -0
  103. package/dist/schema/EventId.d.ts +35 -15
  104. package/dist/schema/EventId.d.ts.map +1 -1
  105. package/dist/schema/EventId.js +57 -11
  106. package/dist/schema/EventId.js.map +1 -1
  107. package/dist/schema/EventId.test.d.ts +2 -0
  108. package/dist/schema/EventId.test.d.ts.map +1 -0
  109. package/dist/schema/EventId.test.js +11 -0
  110. package/dist/schema/EventId.test.js.map +1 -0
  111. package/dist/schema/LiveStoreEvent.d.ts +255 -0
  112. package/dist/schema/LiveStoreEvent.d.ts.map +1 -0
  113. package/dist/schema/LiveStoreEvent.js +118 -0
  114. package/dist/schema/LiveStoreEvent.js.map +1 -0
  115. package/dist/schema/events.d.ts +2 -0
  116. package/dist/schema/events.d.ts.map +1 -0
  117. package/dist/schema/events.js +2 -0
  118. package/dist/schema/events.js.map +1 -0
  119. package/dist/schema/mod.d.ts +7 -5
  120. package/dist/schema/mod.d.ts.map +1 -1
  121. package/dist/schema/mod.js +7 -5
  122. package/dist/schema/mod.js.map +1 -1
  123. package/dist/schema/schema.d.ts +48 -30
  124. package/dist/schema/schema.d.ts.map +1 -1
  125. package/dist/schema/schema.js +36 -43
  126. package/dist/schema/schema.js.map +1 -1
  127. package/dist/schema/state/mod.d.ts +3 -0
  128. package/dist/schema/state/mod.d.ts.map +1 -0
  129. package/dist/schema/state/mod.js +3 -0
  130. package/dist/schema/state/mod.js.map +1 -0
  131. package/dist/schema/state/sqlite/client-document-def.d.ts +223 -0
  132. package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -0
  133. package/dist/schema/state/sqlite/client-document-def.js +170 -0
  134. package/dist/schema/state/sqlite/client-document-def.js.map +1 -0
  135. package/dist/schema/state/sqlite/client-document-def.test.d.ts +2 -0
  136. package/dist/schema/state/sqlite/client-document-def.test.d.ts.map +1 -0
  137. package/dist/schema/state/sqlite/client-document-def.test.js +201 -0
  138. package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -0
  139. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts +69 -0
  140. package/dist/schema/state/sqlite/db-schema/ast/sqlite.d.ts.map +1 -0
  141. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +71 -0
  142. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -0
  143. package/dist/schema/state/sqlite/db-schema/ast/validate.d.ts +3 -0
  144. package/dist/schema/state/sqlite/db-schema/ast/validate.d.ts.map +1 -0
  145. package/dist/schema/state/sqlite/db-schema/ast/validate.js +12 -0
  146. package/dist/schema/state/sqlite/db-schema/ast/validate.js.map +1 -0
  147. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts +90 -0
  148. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts.map +1 -0
  149. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +87 -0
  150. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -0
  151. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.d.ts +2 -0
  152. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.d.ts.map +1 -0
  153. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js +29 -0
  154. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js.map +1 -0
  155. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts +90 -0
  156. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -0
  157. package/dist/schema/state/sqlite/db-schema/dsl/mod.js +41 -0
  158. package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -0
  159. package/dist/schema/state/sqlite/db-schema/hash.d.ts +2 -0
  160. package/dist/schema/state/sqlite/db-schema/hash.d.ts.map +1 -0
  161. package/dist/schema/state/sqlite/db-schema/hash.js +14 -0
  162. package/dist/schema/state/sqlite/db-schema/hash.js.map +1 -0
  163. package/dist/schema/state/sqlite/db-schema/mod.d.ts +3 -0
  164. package/dist/schema/state/sqlite/db-schema/mod.d.ts.map +1 -0
  165. package/dist/schema/state/sqlite/db-schema/mod.js +3 -0
  166. package/dist/schema/state/sqlite/db-schema/mod.js.map +1 -0
  167. package/dist/schema/state/sqlite/mod.d.ts +17 -0
  168. package/dist/schema/state/sqlite/mod.d.ts.map +1 -0
  169. package/dist/schema/state/sqlite/mod.js +41 -0
  170. package/dist/schema/state/sqlite/mod.js.map +1 -0
  171. package/dist/schema/state/sqlite/query-builder/api.d.ts +294 -0
  172. package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -0
  173. package/dist/schema/state/sqlite/query-builder/api.js +6 -0
  174. package/dist/schema/state/sqlite/query-builder/api.js.map +1 -0
  175. package/dist/schema/state/sqlite/query-builder/astToSql.d.ts +7 -0
  176. package/dist/schema/state/sqlite/query-builder/astToSql.d.ts.map +1 -0
  177. package/dist/schema/state/sqlite/query-builder/astToSql.js +190 -0
  178. package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -0
  179. package/dist/schema/state/sqlite/query-builder/impl.d.ts +7 -0
  180. package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -0
  181. package/dist/schema/state/sqlite/query-builder/impl.js +286 -0
  182. package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -0
  183. package/dist/schema/state/sqlite/query-builder/impl.test.d.ts +87 -0
  184. package/dist/schema/state/sqlite/query-builder/impl.test.d.ts.map +1 -0
  185. package/dist/schema/state/sqlite/query-builder/impl.test.js +554 -0
  186. package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -0
  187. package/dist/{query-builder → schema/state/sqlite/query-builder}/mod.d.ts +7 -0
  188. package/dist/schema/state/sqlite/query-builder/mod.d.ts.map +1 -0
  189. package/dist/{query-builder → schema/state/sqlite/query-builder}/mod.js +7 -0
  190. package/dist/schema/state/sqlite/query-builder/mod.js.map +1 -0
  191. package/dist/schema/state/sqlite/schema-helpers.d.ts.map +1 -0
  192. package/dist/schema/{schema-helpers.js → state/sqlite/schema-helpers.js} +1 -1
  193. package/dist/schema/state/sqlite/schema-helpers.js.map +1 -0
  194. package/dist/schema/state/sqlite/system-tables.d.ts +574 -0
  195. package/dist/schema/state/sqlite/system-tables.d.ts.map +1 -0
  196. package/dist/schema/state/sqlite/system-tables.js +87 -0
  197. package/dist/schema/state/sqlite/system-tables.js.map +1 -0
  198. package/dist/schema/state/sqlite/table-def.d.ts +84 -0
  199. package/dist/schema/state/sqlite/table-def.d.ts.map +1 -0
  200. package/dist/schema/state/sqlite/table-def.js +36 -0
  201. package/dist/schema/state/sqlite/table-def.js.map +1 -0
  202. package/dist/schema-management/common.d.ts +7 -7
  203. package/dist/schema-management/common.d.ts.map +1 -1
  204. package/dist/schema-management/common.js.map +1 -1
  205. package/dist/schema-management/migrations.d.ts +6 -6
  206. package/dist/schema-management/migrations.d.ts.map +1 -1
  207. package/dist/schema-management/migrations.js +33 -24
  208. package/dist/schema-management/migrations.js.map +1 -1
  209. package/dist/schema-management/validate-schema.d.ts +8 -0
  210. package/dist/schema-management/validate-schema.d.ts.map +1 -0
  211. package/dist/schema-management/validate-schema.js +39 -0
  212. package/dist/schema-management/validate-schema.js.map +1 -0
  213. package/dist/sql-queries/misc.d.ts.map +1 -1
  214. package/dist/sql-queries/sql-queries.d.ts +1 -1
  215. package/dist/sql-queries/sql-queries.d.ts.map +1 -1
  216. package/dist/sql-queries/sql-queries.js.map +1 -1
  217. package/dist/sql-queries/sql-query-builder.d.ts +1 -1
  218. package/dist/sql-queries/sql-query-builder.d.ts.map +1 -1
  219. package/dist/sql-queries/sql-query-builder.js.map +1 -1
  220. package/dist/sql-queries/types.d.ts +2 -1
  221. package/dist/sql-queries/types.d.ts.map +1 -1
  222. package/dist/sql-queries/types.js.map +1 -1
  223. package/dist/sync/ClientSessionSyncProcessor.d.ts +66 -0
  224. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -0
  225. package/dist/sync/ClientSessionSyncProcessor.js +209 -0
  226. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -0
  227. package/dist/sync/index.d.ts +1 -1
  228. package/dist/sync/index.d.ts.map +1 -1
  229. package/dist/sync/index.js +1 -1
  230. package/dist/sync/index.js.map +1 -1
  231. package/dist/sync/next/compact-events.d.ts.map +1 -1
  232. package/dist/sync/next/facts.d.ts +19 -19
  233. package/dist/sync/next/facts.d.ts.map +1 -1
  234. package/dist/sync/next/facts.js +3 -3
  235. package/dist/sync/next/facts.js.map +1 -1
  236. package/dist/sync/next/history-dag-common.d.ts +6 -7
  237. package/dist/sync/next/history-dag-common.d.ts.map +1 -1
  238. package/dist/sync/next/history-dag-common.js +4 -2
  239. package/dist/sync/next/history-dag-common.js.map +1 -1
  240. package/dist/sync/next/history-dag.d.ts.map +1 -1
  241. package/dist/sync/next/history-dag.js +2 -2
  242. package/dist/sync/next/history-dag.js.map +1 -1
  243. package/dist/sync/next/rebase-events.d.ts +10 -8
  244. package/dist/sync/next/rebase-events.d.ts.map +1 -1
  245. package/dist/sync/next/rebase-events.js +11 -8
  246. package/dist/sync/next/rebase-events.js.map +1 -1
  247. package/dist/sync/next/test/compact-events.calculator.test.js +38 -33
  248. package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
  249. package/dist/sync/next/test/compact-events.test.js +76 -76
  250. package/dist/sync/next/test/compact-events.test.js.map +1 -1
  251. package/dist/sync/next/test/{mutation-fixtures.d.ts → event-fixtures.d.ts} +25 -25
  252. package/dist/sync/next/test/event-fixtures.d.ts.map +1 -0
  253. package/dist/sync/next/test/{mutation-fixtures.js → event-fixtures.js} +67 -36
  254. package/dist/sync/next/test/event-fixtures.js.map +1 -0
  255. package/dist/sync/next/test/mod.d.ts +1 -1
  256. package/dist/sync/next/test/mod.d.ts.map +1 -1
  257. package/dist/sync/next/test/mod.js +1 -1
  258. package/dist/sync/next/test/mod.js.map +1 -1
  259. package/dist/sync/sync.d.ts +55 -20
  260. package/dist/sync/sync.d.ts.map +1 -1
  261. package/dist/sync/sync.js +7 -3
  262. package/dist/sync/sync.js.map +1 -1
  263. package/dist/sync/syncstate.d.ts +213 -82
  264. package/dist/sync/syncstate.d.ts.map +1 -1
  265. package/dist/sync/syncstate.js +319 -120
  266. package/dist/sync/syncstate.js.map +1 -1
  267. package/dist/sync/syncstate.test.js +295 -275
  268. package/dist/sync/syncstate.test.js.map +1 -1
  269. package/dist/sync/validate-push-payload.d.ts +2 -2
  270. package/dist/sync/validate-push-payload.d.ts.map +1 -1
  271. package/dist/sync/validate-push-payload.js +2 -2
  272. package/dist/sync/validate-push-payload.js.map +1 -1
  273. package/dist/util.d.ts +2 -2
  274. package/dist/util.d.ts.map +1 -1
  275. package/dist/version.d.ts +1 -1
  276. package/dist/version.d.ts.map +1 -1
  277. package/dist/version.js +1 -1
  278. package/dist/version.js.map +1 -1
  279. package/package.json +13 -6
  280. package/src/__tests__/fixture.ts +36 -15
  281. package/src/adapter-types.ts +116 -83
  282. package/src/debug-info.ts +1 -0
  283. package/src/devtools/devtools-messages-client-session.ts +142 -0
  284. package/src/devtools/devtools-messages-common.ts +115 -0
  285. package/src/devtools/devtools-messages-leader.ts +191 -0
  286. package/src/devtools/devtools-messages.ts +3 -243
  287. package/src/devtools/devtools-sessioninfo.ts +101 -0
  288. package/src/devtools/mod.ts +59 -0
  289. package/src/index.ts +7 -15
  290. package/src/leader-thread/LeaderSyncProcessor.ts +933 -0
  291. package/src/leader-thread/connection.ts +54 -9
  292. package/src/leader-thread/eventlog.ts +194 -0
  293. package/src/leader-thread/leader-worker-devtools.ts +235 -191
  294. package/src/leader-thread/make-leader-thread-layer.ts +138 -78
  295. package/src/leader-thread/materialize-event.ts +169 -0
  296. package/src/leader-thread/mod.ts +1 -1
  297. package/src/leader-thread/recreate-db.ts +38 -39
  298. package/src/leader-thread/shutdown-channel.ts +2 -4
  299. package/src/leader-thread/types.ts +98 -53
  300. package/src/make-client-session.ts +119 -0
  301. package/src/materializer-helper.ts +135 -0
  302. package/src/otel.ts +8 -0
  303. package/src/rematerialize-from-eventlog.ts +117 -0
  304. package/src/schema/EventDef.ts +227 -0
  305. package/src/schema/EventId.test.ts +12 -0
  306. package/src/schema/EventId.ts +75 -15
  307. package/src/schema/LiveStoreEvent.ts +239 -0
  308. package/src/schema/events.ts +1 -0
  309. package/src/schema/mod.ts +7 -5
  310. package/src/schema/schema.ts +85 -81
  311. package/src/schema/state/mod.ts +2 -0
  312. package/src/schema/state/sqlite/client-document-def.test.ts +238 -0
  313. package/src/schema/state/sqlite/client-document-def.ts +444 -0
  314. package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +142 -0
  315. package/src/schema/state/sqlite/db-schema/ast/validate.ts +13 -0
  316. package/src/schema/state/sqlite/db-schema/dsl/__snapshots__/field-defs.test.ts.snap +206 -0
  317. package/src/schema/state/sqlite/db-schema/dsl/field-defs.test.ts +35 -0
  318. package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +242 -0
  319. package/src/schema/state/sqlite/db-schema/dsl/mod.ts +222 -0
  320. package/src/schema/state/sqlite/db-schema/hash.ts +14 -0
  321. package/src/schema/state/sqlite/db-schema/mod.ts +2 -0
  322. package/src/schema/state/sqlite/mod.ts +73 -0
  323. package/src/schema/state/sqlite/query-builder/api.ts +440 -0
  324. package/src/schema/state/sqlite/query-builder/astToSql.ts +232 -0
  325. package/src/schema/state/sqlite/query-builder/impl.test.ts +608 -0
  326. package/src/schema/state/sqlite/query-builder/impl.ts +350 -0
  327. package/src/{query-builder → schema/state/sqlite/query-builder}/mod.ts +7 -0
  328. package/src/schema/{schema-helpers.ts → state/sqlite/schema-helpers.ts} +1 -1
  329. package/src/schema/state/sqlite/system-tables.ts +116 -0
  330. package/src/schema/state/sqlite/table-def.ts +197 -0
  331. package/src/schema-management/common.ts +7 -7
  332. package/src/schema-management/migrations.ts +43 -37
  333. package/src/schema-management/validate-schema.ts +61 -0
  334. package/src/sql-queries/sql-queries.ts +1 -1
  335. package/src/sql-queries/sql-query-builder.ts +1 -2
  336. package/src/sql-queries/types.ts +3 -1
  337. package/src/sync/ClientSessionSyncProcessor.ts +332 -0
  338. package/src/sync/index.ts +1 -1
  339. package/src/sync/next/facts.ts +32 -33
  340. package/src/sync/next/history-dag-common.ts +9 -5
  341. package/src/sync/next/history-dag.ts +2 -2
  342. package/src/sync/next/rebase-events.ts +22 -16
  343. package/src/sync/next/test/compact-events.calculator.test.ts +45 -45
  344. package/src/sync/next/test/compact-events.test.ts +78 -78
  345. package/src/sync/next/test/event-fixtures.ts +219 -0
  346. package/src/sync/next/test/mod.ts +1 -1
  347. package/src/sync/sync.ts +51 -19
  348. package/src/sync/syncstate.test.ts +335 -308
  349. package/src/sync/syncstate.ts +394 -212
  350. package/src/sync/validate-push-payload.ts +7 -4
  351. package/src/version.ts +1 -1
  352. package/dist/derived-mutations.d.ts +0 -109
  353. package/dist/derived-mutations.d.ts.map +0 -1
  354. package/dist/derived-mutations.js +0 -54
  355. package/dist/derived-mutations.js.map +0 -1
  356. package/dist/derived-mutations.test.d.ts +0 -2
  357. package/dist/derived-mutations.test.d.ts.map +0 -1
  358. package/dist/derived-mutations.test.js +0 -93
  359. package/dist/derived-mutations.test.js.map +0 -1
  360. package/dist/devtools/devtools-bridge.d.ts +0 -12
  361. package/dist/devtools/devtools-bridge.d.ts.map +0 -1
  362. package/dist/devtools/devtools-bridge.js +0 -2
  363. package/dist/devtools/devtools-bridge.js.map +0 -1
  364. package/dist/devtools/devtools-window-message.d.ts +0 -29
  365. package/dist/devtools/devtools-window-message.d.ts.map +0 -1
  366. package/dist/devtools/devtools-window-message.js +0 -33
  367. package/dist/devtools/devtools-window-message.js.map +0 -1
  368. package/dist/devtools/index.d.ts +0 -42
  369. package/dist/devtools/index.d.ts.map +0 -1
  370. package/dist/devtools/index.js +0 -48
  371. package/dist/devtools/index.js.map +0 -1
  372. package/dist/init-singleton-tables.d.ts +0 -4
  373. package/dist/init-singleton-tables.d.ts.map +0 -1
  374. package/dist/init-singleton-tables.js +0 -16
  375. package/dist/init-singleton-tables.js.map +0 -1
  376. package/dist/leader-thread/apply-mutation.d.ts +0 -8
  377. package/dist/leader-thread/apply-mutation.d.ts.map +0 -1
  378. package/dist/leader-thread/apply-mutation.js +0 -95
  379. package/dist/leader-thread/apply-mutation.js.map +0 -1
  380. package/dist/leader-thread/leader-sync-processor.d.ts +0 -47
  381. package/dist/leader-thread/leader-sync-processor.d.ts.map +0 -1
  382. package/dist/leader-thread/leader-sync-processor.js +0 -422
  383. package/dist/leader-thread/leader-sync-processor.js.map +0 -1
  384. package/dist/leader-thread/mutationlog.d.ts +0 -23
  385. package/dist/leader-thread/mutationlog.d.ts.map +0 -1
  386. package/dist/leader-thread/mutationlog.js +0 -27
  387. package/dist/leader-thread/mutationlog.js.map +0 -1
  388. package/dist/leader-thread/pull-queue-set.d.ts +0 -7
  389. package/dist/leader-thread/pull-queue-set.d.ts.map +0 -1
  390. package/dist/leader-thread/pull-queue-set.js +0 -39
  391. package/dist/leader-thread/pull-queue-set.js.map +0 -1
  392. package/dist/mutation.d.ts +0 -13
  393. package/dist/mutation.d.ts.map +0 -1
  394. package/dist/mutation.js +0 -57
  395. package/dist/mutation.js.map +0 -1
  396. package/dist/query-builder/api.d.ts +0 -190
  397. package/dist/query-builder/api.d.ts.map +0 -1
  398. package/dist/query-builder/api.js +0 -8
  399. package/dist/query-builder/api.js.map +0 -1
  400. package/dist/query-builder/impl.d.ts +0 -12
  401. package/dist/query-builder/impl.d.ts.map +0 -1
  402. package/dist/query-builder/impl.js +0 -244
  403. package/dist/query-builder/impl.js.map +0 -1
  404. package/dist/query-builder/impl.test.d.ts +0 -2
  405. package/dist/query-builder/impl.test.d.ts.map +0 -1
  406. package/dist/query-builder/impl.test.js +0 -212
  407. package/dist/query-builder/impl.test.js.map +0 -1
  408. package/dist/query-builder/mod.d.ts.map +0 -1
  409. package/dist/query-builder/mod.js.map +0 -1
  410. package/dist/query-info.d.ts +0 -38
  411. package/dist/query-info.d.ts.map +0 -1
  412. package/dist/query-info.js +0 -7
  413. package/dist/query-info.js.map +0 -1
  414. package/dist/rehydrate-from-mutationlog.d.ts +0 -14
  415. package/dist/rehydrate-from-mutationlog.d.ts.map +0 -1
  416. package/dist/rehydrate-from-mutationlog.js +0 -72
  417. package/dist/rehydrate-from-mutationlog.js.map +0 -1
  418. package/dist/schema/MutationEvent.d.ts +0 -191
  419. package/dist/schema/MutationEvent.d.ts.map +0 -1
  420. package/dist/schema/MutationEvent.js +0 -56
  421. package/dist/schema/MutationEvent.js.map +0 -1
  422. package/dist/schema/mutations.d.ts +0 -107
  423. package/dist/schema/mutations.d.ts.map +0 -1
  424. package/dist/schema/mutations.js +0 -42
  425. package/dist/schema/mutations.js.map +0 -1
  426. package/dist/schema/schema-helpers.d.ts.map +0 -1
  427. package/dist/schema/schema-helpers.js.map +0 -1
  428. package/dist/schema/system-tables.d.ts +0 -399
  429. package/dist/schema/system-tables.d.ts.map +0 -1
  430. package/dist/schema/system-tables.js +0 -51
  431. package/dist/schema/system-tables.js.map +0 -1
  432. package/dist/schema/table-def.d.ts +0 -156
  433. package/dist/schema/table-def.d.ts.map +0 -1
  434. package/dist/schema/table-def.js +0 -79
  435. package/dist/schema/table-def.js.map +0 -1
  436. package/dist/schema-management/validate-mutation-defs.d.ts +0 -8
  437. package/dist/schema-management/validate-mutation-defs.d.ts.map +0 -1
  438. package/dist/schema-management/validate-mutation-defs.js +0 -39
  439. package/dist/schema-management/validate-mutation-defs.js.map +0 -1
  440. package/dist/sync/client-session-sync-processor.d.ts +0 -45
  441. package/dist/sync/client-session-sync-processor.d.ts.map +0 -1
  442. package/dist/sync/client-session-sync-processor.js +0 -131
  443. package/dist/sync/client-session-sync-processor.js.map +0 -1
  444. package/dist/sync/next/test/mutation-fixtures.d.ts.map +0 -1
  445. package/dist/sync/next/test/mutation-fixtures.js.map +0 -1
  446. package/src/derived-mutations.test.ts +0 -101
  447. package/src/derived-mutations.ts +0 -166
  448. package/src/devtools/devtools-bridge.ts +0 -13
  449. package/src/devtools/devtools-window-message.ts +0 -27
  450. package/src/devtools/index.ts +0 -48
  451. package/src/init-singleton-tables.ts +0 -24
  452. package/src/leader-thread/apply-mutation.ts +0 -143
  453. package/src/leader-thread/leader-sync-processor.ts +0 -666
  454. package/src/leader-thread/mutationlog.ts +0 -42
  455. package/src/leader-thread/pull-queue-set.ts +0 -58
  456. package/src/mutation.ts +0 -81
  457. package/src/query-builder/api.ts +0 -289
  458. package/src/query-builder/impl.test.ts +0 -239
  459. package/src/query-builder/impl.ts +0 -285
  460. package/src/query-info.ts +0 -78
  461. package/src/rehydrate-from-mutationlog.ts +0 -127
  462. package/src/schema/MutationEvent.ts +0 -161
  463. package/src/schema/mutations.ts +0 -192
  464. package/src/schema/system-tables.ts +0 -97
  465. package/src/schema/table-def.ts +0 -343
  466. package/src/schema-management/validate-mutation-defs.ts +0 -63
  467. package/src/sync/client-session-sync-processor.ts +0 -207
  468. package/src/sync/next/test/mutation-fixtures.ts +0 -231
  469. package/tsconfig.json +0 -11
  470. /package/dist/schema/{schema-helpers.d.ts → state/sqlite/schema-helpers.d.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import type { SynchronousDatabase } from '../adapter-types.js'
1
+ import type { SqliteDb } from '../adapter-types.js'
2
2
  import type { ParamsObject } from '../util.js'
3
3
  import { prepareBindValues } from '../util.js'
4
4
 
@@ -6,7 +6,7 @@ import { prepareBindValues } from '../util.js'
6
6
  // will require proper scope-aware cleanup etc (for testing and apps with multiple LiveStore instances)
7
7
  // const cachedStmts = new Map<string, PreparedStatement>()
8
8
 
9
- export const dbExecute = (db: SynchronousDatabase, queryStr: string, bindValues?: ParamsObject) => {
9
+ export const dbExecute = (db: SqliteDb, queryStr: string, bindValues?: ParamsObject) => {
10
10
  // let stmt = cachedStmts.get(queryStr)
11
11
  // if (!stmt) {
12
12
  const stmt = db.prepare(queryStr)
@@ -20,7 +20,7 @@ export const dbExecute = (db: SynchronousDatabase, queryStr: string, bindValues?
20
20
  stmt.finalize()
21
21
  }
22
22
 
23
- export const dbSelect = <T>(db: SynchronousDatabase, queryStr: string, bindValues?: ParamsObject) => {
23
+ export const dbSelect = <T>(db: SqliteDb, queryStr: string, bindValues?: ParamsObject) => {
24
24
  // let stmt = cachedStmts.get(queryStr)
25
25
  // if (!stmt) {
26
26
  const stmt = db.prepare(queryStr)
@@ -33,12 +33,12 @@ export const dbSelect = <T>(db: SynchronousDatabase, queryStr: string, bindValue
33
33
  }
34
34
 
35
35
  export interface SchemaManager {
36
- getMutationDefInfos: () => ReadonlyArray<MutationDefInfo>
36
+ getEventDefInfos: () => ReadonlyArray<EventDefInfo>
37
37
 
38
- setMutationDefInfo: (mutationDefInfo: MutationDefInfo) => void
38
+ setEventDefInfo: (eventDefInfo: EventDefInfo) => void
39
39
  }
40
40
 
41
- export type MutationDefInfo = {
42
- mutationName: string
41
+ export type EventDefInfo = {
42
+ eventName: string
43
43
  schemaHash: number
44
44
  }
@@ -1,42 +1,41 @@
1
- import { SqliteAst, SqliteDsl } from '@livestore/db-schema'
2
1
  import { memoizeByStringifyArgs } from '@livestore/utils'
3
2
  import { Effect, Schema as EffectSchema } from '@livestore/utils/effect'
4
3
 
5
- import type { SynchronousDatabase } from '../adapter-types.js'
4
+ import type { MigrationsReport, MigrationsReportEntry, SqliteDb, UnexpectedError } from '../adapter-types.js'
6
5
  import type { LiveStoreSchema } from '../schema/mod.js'
7
- import type { SchemaMetaRow, SchemaMutationsMetaRow } from '../schema/system-tables.js'
6
+ import { SqliteAst, SqliteDsl } from '../schema/state/sqlite/db-schema/mod.js'
7
+ import type { SchemaEventDefsMetaRow, SchemaMetaRow } from '../schema/state/sqlite/system-tables.js'
8
8
  import {
9
+ isStateSystemTable,
10
+ SCHEMA_EVENT_DEFS_META_TABLE,
9
11
  SCHEMA_META_TABLE,
10
- SCHEMA_MUTATIONS_META_TABLE,
11
- schemaMetaTable,
12
- schemaMutationsMetaTable,
13
- systemTables,
14
- } from '../schema/system-tables.js'
12
+ schemaEventDefsMetaTable,
13
+ stateSystemTables,
14
+ } from '../schema/state/sqlite/system-tables.js'
15
15
  import { sql } from '../util.js'
16
16
  import type { SchemaManager } from './common.js'
17
17
  import { dbExecute, dbSelect } from './common.js'
18
- import { validateSchema } from './validate-mutation-defs.js'
18
+ import { validateSchema } from './validate-schema.js'
19
19
 
20
20
  const getMemoizedTimestamp = memoizeByStringifyArgs(() => new Date().toISOString())
21
21
 
22
- export const makeSchemaManager = (db: SynchronousDatabase): Effect.Effect<SchemaManager> =>
22
+ export const makeSchemaManager = (db: SqliteDb): Effect.Effect<SchemaManager> =>
23
23
  Effect.gen(function* () {
24
24
  yield* migrateTable({
25
25
  db,
26
- tableAst: schemaMutationsMetaTable.sqliteDef.ast,
26
+ tableAst: schemaEventDefsMetaTable.sqliteDef.ast,
27
27
  behaviour: 'create-if-not-exists',
28
28
  })
29
29
 
30
30
  return {
31
- getMutationDefInfos: () =>
32
- dbSelect<SchemaMutationsMetaRow>(db, sql`SELECT * FROM ${SCHEMA_MUTATIONS_META_TABLE}`),
31
+ getEventDefInfos: () => dbSelect<SchemaEventDefsMetaRow>(db, sql`SELECT * FROM ${SCHEMA_EVENT_DEFS_META_TABLE}`),
33
32
 
34
- setMutationDefInfo: (info) => {
33
+ setEventDefInfo: (info) => {
35
34
  dbExecute(
36
35
  db,
37
- sql`INSERT OR REPLACE INTO ${SCHEMA_MUTATIONS_META_TABLE} (mutationName, schemaHash, updatedAt) VALUES ($mutationName, $schemaHash, $updatedAt)`,
36
+ sql`INSERT OR REPLACE INTO ${SCHEMA_EVENT_DEFS_META_TABLE} (eventName, schemaHash, updatedAt) VALUES ($eventName, $schemaHash, $updatedAt)`,
38
37
  {
39
- mutationName: info.mutationName,
38
+ eventName: info.eventName,
40
39
  schemaHash: info.schemaHash,
41
40
  updatedAt: new Date().toISOString(),
42
41
  },
@@ -51,16 +50,18 @@ export const migrateDb = ({
51
50
  schema,
52
51
  onProgress,
53
52
  }: {
54
- db: SynchronousDatabase
53
+ db: SqliteDb
55
54
  schema: LiveStoreSchema
56
55
  onProgress?: (opts: { done: number; total: number }) => Effect.Effect<void>
57
- }) =>
56
+ }): Effect.Effect<MigrationsReport, UnexpectedError> =>
58
57
  Effect.gen(function* () {
59
- yield* migrateTable({
60
- db,
61
- tableAst: schemaMetaTable.sqliteDef.ast,
62
- behaviour: 'create-if-not-exists',
63
- })
58
+ for (const tableDef of stateSystemTables) {
59
+ yield* migrateTable({
60
+ db,
61
+ tableAst: tableDef.sqliteDef.ast,
62
+ behaviour: 'create-if-not-exists',
63
+ })
64
+ }
64
65
 
65
66
  // TODO enforce that migrating tables isn't allowed once the store is running
66
67
 
@@ -73,14 +74,15 @@ export const migrateDb = ({
73
74
  schemaMetaRows.map(({ tableName, schemaHash }) => [tableName, schemaHash]),
74
75
  )
75
76
 
76
- const tableDefs = new Set([
77
+ const tableDefs = [
77
78
  // NOTE it's important the `SCHEMA_META_TABLE` comes first since we're writing to it below
78
- ...systemTables,
79
- ...Array.from(schema.tables.values()).filter((_) => _.sqliteDef.name !== SCHEMA_META_TABLE),
80
- ])
79
+ ...stateSystemTables,
80
+ ...Array.from(schema.state.sqlite.tables.values()).filter((_) => !isStateSystemTable(_.sqliteDef.name)),
81
+ ]
81
82
 
82
83
  const tablesToMigrate = new Set<{ tableAst: SqliteAst.Table; schemaHash: number }>()
83
84
 
85
+ const migrationsReportEntries: MigrationsReportEntry[] = []
84
86
  for (const tableDef of tableDefs) {
85
87
  const tableAst = tableDef.sqliteDef.ast
86
88
  const tableName = tableAst.name
@@ -90,9 +92,10 @@ export const migrateDb = ({
90
92
  if (schemaHash !== dbSchemaHash) {
91
93
  tablesToMigrate.add({ tableAst, schemaHash })
92
94
 
93
- console.log(
94
- `Schema hash mismatch for table '${tableName}' (DB: ${dbSchemaHash}, expected: ${schemaHash}), migrating table...`,
95
- )
95
+ migrationsReportEntries.push({
96
+ tableName,
97
+ hashes: { expected: schemaHash, actual: dbSchemaHash },
98
+ })
96
99
  }
97
100
  }
98
101
 
@@ -107,6 +110,8 @@ export const migrateDb = ({
107
110
  yield* onProgress({ done: processedTables, total: tablesCount })
108
111
  }
109
112
  }
113
+
114
+ return { migrations: migrationsReportEntries }
110
115
  })
111
116
 
112
117
  export const migrateTable = ({
@@ -116,7 +121,7 @@ export const migrateTable = ({
116
121
  behaviour,
117
122
  skipMetaTable = false,
118
123
  }: {
119
- db: SynchronousDatabase
124
+ db: SqliteDb
120
125
  tableAst: SqliteAst.Table
121
126
  schemaHash?: number
122
127
  behaviour: 'drop-and-recreate' | 'create-if-not-exists'
@@ -129,10 +134,10 @@ export const migrateTable = ({
129
134
 
130
135
  if (behaviour === 'drop-and-recreate') {
131
136
  // TODO need to possibly handle cascading deletes due to foreign keys
132
- dbExecute(db, sql`drop table if exists ${tableName}`)
133
- dbExecute(db, sql`create table if not exists ${tableName} (${columnSpec}) strict`)
137
+ dbExecute(db, sql`drop table if exists '${tableName}'`)
138
+ dbExecute(db, sql`create table if not exists '${tableName}' (${columnSpec}) strict`)
134
139
  } else if (behaviour === 'create-if-not-exists') {
135
- dbExecute(db, sql`create table if not exists ${tableName} (${columnSpec}) strict`)
140
+ dbExecute(db, sql`create table if not exists '${tableName}' (${columnSpec}) strict`)
136
141
  }
137
142
 
138
143
  for (const index of tableAst.indexes) {
@@ -162,11 +167,11 @@ export const migrateTable = ({
162
167
 
163
168
  const createIndexFromDefinition = (tableName: string, index: SqliteAst.Index) => {
164
169
  const uniqueStr = index.unique ? 'UNIQUE' : ''
165
- return sql`create ${uniqueStr} index if not exists ${index.name} on ${tableName} (${index.columns.join(', ')})`
170
+ return sql`create ${uniqueStr} index if not exists '${index.name}' on '${tableName}' (${index.columns.join(', ')})`
166
171
  }
167
172
 
168
173
  export const makeColumnSpec = (tableAst: SqliteAst.Table) => {
169
- const primaryKeys = tableAst.columns.filter((_) => _.primaryKey).map((_) => _.name)
174
+ const primaryKeys = tableAst.columns.filter((_) => _.primaryKey).map((_) => `'${_.name}'`)
170
175
  const columnDefStrs = tableAst.columns.map(toSqliteColumnSpec)
171
176
  if (primaryKeys.length > 0) {
172
177
  columnDefStrs.push(`PRIMARY KEY (${primaryKeys.join(', ')})`)
@@ -182,6 +187,7 @@ const toSqliteColumnSpec = (column: SqliteAst.Column) => {
182
187
  const defaultValueStr = (() => {
183
188
  if (column.default._tag === 'None') return ''
184
189
 
190
+ if (column.default.value === null) return 'default null'
185
191
  if (SqliteDsl.isSqlDefaultValue(column.default.value)) return `default ${column.default.value.sql}`
186
192
 
187
193
  const encodeValue = EffectSchema.encodeSync(column.schema)
@@ -191,5 +197,5 @@ const toSqliteColumnSpec = (column: SqliteAst.Column) => {
191
197
  return `default ${encodedDefaultValue}`
192
198
  })()
193
199
 
194
- return `${column.name} ${columnTypeStr} ${nullableStr} ${defaultValueStr}`
200
+ return `'${column.name}' ${columnTypeStr} ${nullableStr} ${defaultValueStr}`
195
201
  }
@@ -0,0 +1,61 @@
1
+ import { Effect, Schema } from '@livestore/utils/effect'
2
+
3
+ import { UnexpectedError } from '../adapter-types.js'
4
+ import type { EventDef } from '../schema/EventDef.js'
5
+ import type { LiveStoreSchema } from '../schema/mod.js'
6
+ import type { EventDefInfo, SchemaManager } from './common.js'
7
+
8
+ export const validateSchema = (schema: LiveStoreSchema, schemaManager: SchemaManager) =>
9
+ Effect.gen(function* () {
10
+ // Validate mutation definitions
11
+ const registeredEventDefInfos = schemaManager.getEventDefInfos()
12
+
13
+ const missingEventDefs = registeredEventDefInfos.filter(
14
+ (registeredEventDefInfo) => !schema.eventsDefsMap.has(registeredEventDefInfo.eventName),
15
+ )
16
+
17
+ if (missingEventDefs.length > 0) {
18
+ yield* new UnexpectedError({
19
+ cause: `Missing mutation definitions: ${missingEventDefs.map((info) => info.eventName).join(', ')}`,
20
+ })
21
+ }
22
+
23
+ for (const [, eventDef] of schema.eventsDefsMap) {
24
+ const registeredEventDefInfo = registeredEventDefInfos.find((info) => info.eventName === eventDef.name)
25
+
26
+ validateEventDef(eventDef, schemaManager, registeredEventDefInfo)
27
+ }
28
+
29
+ // Validate table schemas
30
+ })
31
+
32
+ export const validateEventDef = (
33
+ eventDef: EventDef.AnyWithoutFn,
34
+ schemaManager: SchemaManager,
35
+ registeredEventDefInfo: EventDefInfo | undefined,
36
+ ) => {
37
+ const schemaHash = Schema.hash(eventDef.schema)
38
+
39
+ if (registeredEventDefInfo === undefined) {
40
+ schemaManager.setEventDefInfo({
41
+ schemaHash,
42
+ eventName: eventDef.name,
43
+ })
44
+
45
+ return
46
+ }
47
+
48
+ if (schemaHash === registeredEventDefInfo.schemaHash) return
49
+
50
+ // TODO bring back some form of schema compatibility check (see https://github.com/livestorejs/livestore/issues/69)
51
+ // const newSchemaIsCompatibleWithOldSchema = Schema.isSubType(jsonSchemaDefFromMgmtStore, eventDef.schema)
52
+
53
+ // if (!newSchemaIsCompatibleWithOldSchema) {
54
+ // shouldNeverHappen(`Schema for mutation ${eventDef.name} has changed in an incompatible way`)
55
+ // }
56
+
57
+ schemaManager.setEventDefInfo({
58
+ schemaHash,
59
+ eventName: eventDef.name,
60
+ })
61
+ }
@@ -1,7 +1,7 @@
1
- import type { SqliteDsl } from '@livestore/db-schema'
2
1
  import { shouldNeverHappen } from '@livestore/utils'
3
2
  import { pipe, ReadonlyArray, Schema, TreeFormatter } from '@livestore/utils/effect'
4
3
 
4
+ import type { SqliteDsl } from '../schema/state/sqlite/db-schema/mod.js'
5
5
  import { sql } from '../util.js'
6
6
  import { objectEntries } from './misc.js'
7
7
  import * as ClientTypes from './types.js'
@@ -1,5 +1,4 @@
1
- import type { SqliteDsl } from '@livestore/db-schema'
2
-
1
+ import type { SqliteDsl } from '../schema/state/sqlite/db-schema/mod.js'
3
2
  import type { BindValues } from './sql-queries.js'
4
3
  import * as SqlQueries from './sql-queries.js'
5
4
  import type * as ClientTypes from './types.js'
@@ -1,6 +1,8 @@
1
- import type { Prettify, SqliteDsl } from '@livestore/db-schema'
1
+ import type { Prettify } from '@livestore/utils'
2
2
  import type { Schema } from '@livestore/utils/effect'
3
3
 
4
+ import type { SqliteDsl } from '../schema/state/sqlite/db-schema/mod.js'
5
+
4
6
  export type DecodedValuesForTableAll<TSchema extends SqliteDsl.DbSchema, TTableName extends keyof TSchema> = {
5
7
  [K in keyof GetColumns<TSchema, TTableName>]: Schema.Schema.Type<GetColumn<TSchema, TTableName, K>['schema']>
6
8
  }
@@ -0,0 +1,332 @@
1
+ /// <reference lib="dom" />
2
+ import { LS_DEV, shouldNeverHappen, TRACE_VERBOSE } from '@livestore/utils'
3
+ import type { Runtime, Scope } from '@livestore/utils/effect'
4
+ import { BucketQueue, Effect, FiberHandle, Queue, Schema, Stream, Subscribable } from '@livestore/utils/effect'
5
+ import * as otel from '@opentelemetry/api'
6
+
7
+ import type { ClientSession, UnexpectedError } from '../adapter-types.js'
8
+ import * as EventId from '../schema/EventId.js'
9
+ import * as LiveStoreEvent from '../schema/LiveStoreEvent.js'
10
+ import { getEventDef, type LiveStoreSchema, SystemTables } from '../schema/mod.js'
11
+ import { sql } from '../util.js'
12
+ import * as SyncState from './syncstate.js'
13
+
14
+ /**
15
+ * Rebase behaviour:
16
+ * - We continously pull events from the leader and apply them to the local store.
17
+ * - If there was a race condition (i.e. the leader and client session have both advacned),
18
+ * we'll need to rebase the local pending events on top of the leader's head.
19
+ * - The goal is to never block the UI, so we'll interrupt rebasing if a new events is pushed by the client session.
20
+ * - We also want to avoid "backwards-jumping" in the UI, so we'll transactionally apply state changes during a rebase.
21
+ * - We might need to make the rebase behaviour configurable e.g. to let users manually trigger a rebase
22
+ *
23
+ * Longer term we should evalutate whether we can unify the ClientSessionSyncProcessor with the LeaderSyncProcessor.
24
+ */
25
+ export const makeClientSessionSyncProcessor = ({
26
+ schema,
27
+ clientSession,
28
+ runtime,
29
+ materializeEvent,
30
+ rollback,
31
+ refreshTables,
32
+ span,
33
+ params,
34
+ confirmUnsavedChanges,
35
+ }: {
36
+ schema: LiveStoreSchema
37
+ clientSession: ClientSession
38
+ runtime: Runtime.Runtime<Scope.Scope>
39
+ materializeEvent: (
40
+ eventDecoded: LiveStoreEvent.PartialAnyDecoded,
41
+ options: { otelContext: otel.Context; withChangeset: boolean },
42
+ ) => {
43
+ writeTables: Set<string>
44
+ sessionChangeset: { _tag: 'sessionChangeset'; data: Uint8Array; debug: any } | { _tag: 'no-op' } | { _tag: 'unset' }
45
+ }
46
+ rollback: (changeset: Uint8Array) => void
47
+ refreshTables: (tables: Set<string>) => void
48
+ span: otel.Span
49
+ params: {
50
+ leaderPushBatchSize: number
51
+ }
52
+ /**
53
+ * Currently only used in the web adapter:
54
+ * If true, registers a beforeunload event listener to confirm unsaved changes.
55
+ */
56
+ confirmUnsavedChanges: boolean
57
+ }): ClientSessionSyncProcessor => {
58
+ const eventSchema = LiveStoreEvent.makeEventDefSchemaMemo(schema)
59
+
60
+ const syncStateRef = {
61
+ // The initial state is identical to the leader's initial state
62
+ current: new SyncState.SyncState({
63
+ localHead: clientSession.leaderThread.initialState.leaderHead,
64
+ upstreamHead: clientSession.leaderThread.initialState.leaderHead,
65
+ // Given we're starting with the leader's snapshot, we don't have any pending events intially
66
+ pending: [],
67
+ }),
68
+ }
69
+
70
+ const syncStateUpdateQueue = Queue.unbounded<SyncState.SyncState>().pipe(Effect.runSync)
71
+ const isClientEvent = (eventEncoded: LiveStoreEvent.EncodedWithMeta) =>
72
+ getEventDef(schema, eventEncoded.name).eventDef.options.clientOnly
73
+
74
+ /** We're queuing push requests to reduce the number of messages sent to the leader by batching them */
75
+ const leaderPushQueue = BucketQueue.make<LiveStoreEvent.EncodedWithMeta>().pipe(Effect.runSync)
76
+
77
+ const push: ClientSessionSyncProcessor['push'] = (batch, { otelContext }) => {
78
+ // TODO validate batch
79
+
80
+ let baseEventId = syncStateRef.current.localHead
81
+ const encodedEventDefs = batch.map(({ name, args }) => {
82
+ const eventDef = getEventDef(schema, name)
83
+ const nextIdPair = EventId.nextPair(baseEventId, eventDef.eventDef.options.clientOnly)
84
+ baseEventId = nextIdPair.id
85
+ return new LiveStoreEvent.EncodedWithMeta(
86
+ Schema.encodeUnknownSync(eventSchema)({
87
+ name,
88
+ args,
89
+ ...nextIdPair,
90
+ clientId: clientSession.clientId,
91
+ sessionId: clientSession.sessionId,
92
+ }),
93
+ )
94
+ })
95
+
96
+ const mergeResult = SyncState.merge({
97
+ syncState: syncStateRef.current,
98
+ payload: { _tag: 'local-push', newEvents: encodedEventDefs },
99
+ isClientEvent,
100
+ isEqualEvent: LiveStoreEvent.isEqualEncoded,
101
+ })
102
+
103
+ if (mergeResult._tag === 'unexpected-error') {
104
+ return shouldNeverHappen('Unexpected error in client-session-sync-processor', mergeResult.cause)
105
+ }
106
+
107
+ span.addEvent('local-push', {
108
+ batchSize: encodedEventDefs.length,
109
+ mergeResult: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
110
+ })
111
+
112
+ if (mergeResult._tag !== 'advance') {
113
+ return shouldNeverHappen(`Expected advance, got ${mergeResult._tag}`)
114
+ }
115
+
116
+ syncStateRef.current = mergeResult.newSyncState
117
+ syncStateUpdateQueue.offer(mergeResult.newSyncState).pipe(Effect.runSync)
118
+
119
+ const writeTables = new Set<string>()
120
+ for (const event of mergeResult.newEvents) {
121
+ // TODO avoid encoding and decoding here again
122
+ const decodedEventDef = Schema.decodeSync(eventSchema)(event)
123
+ const res = materializeEvent(decodedEventDef, { otelContext, withChangeset: true })
124
+ for (const table of res.writeTables) {
125
+ writeTables.add(table)
126
+ }
127
+ event.meta.sessionChangeset = res.sessionChangeset
128
+ }
129
+
130
+ // console.debug('pushToLeader', encodedEventDefs.length, ...encodedEventDefs.map((_) => _.toJSON()))
131
+ BucketQueue.offerAll(leaderPushQueue, encodedEventDefs).pipe(Effect.runSync)
132
+
133
+ return { writeTables }
134
+ }
135
+
136
+ const debugInfo = {
137
+ rebaseCount: 0,
138
+ advanceCount: 0,
139
+ rejectCount: 0,
140
+ }
141
+
142
+ const otelContext = otel.trace.setSpan(otel.context.active(), span)
143
+
144
+ const boot: ClientSessionSyncProcessor['boot'] = Effect.gen(function* () {
145
+ // eslint-disable-next-line unicorn/prefer-global-this
146
+ if (confirmUnsavedChanges && typeof window !== 'undefined' && typeof window.addEventListener === 'function') {
147
+ const onBeforeUnload = (event: BeforeUnloadEvent) => {
148
+ if (syncStateRef.current.pending.length > 0) {
149
+ // Trigger the default browser dialog
150
+ event.preventDefault()
151
+ }
152
+ }
153
+
154
+ yield* Effect.acquireRelease(
155
+ Effect.sync(() => window.addEventListener('beforeunload', onBeforeUnload)),
156
+ () => Effect.sync(() => window.removeEventListener('beforeunload', onBeforeUnload)),
157
+ )
158
+ }
159
+
160
+ const leaderPushingFiberHandle = yield* FiberHandle.make()
161
+
162
+ const backgroundLeaderPushing = Effect.gen(function* () {
163
+ const batch = yield* BucketQueue.takeBetween(leaderPushQueue, 1, params.leaderPushBatchSize)
164
+ yield* clientSession.leaderThread.events.push(batch).pipe(
165
+ Effect.catchTag('LeaderAheadError', () => {
166
+ debugInfo.rejectCount++
167
+ return BucketQueue.clear(leaderPushQueue)
168
+ }),
169
+ )
170
+ }).pipe(Effect.forever, Effect.interruptible, Effect.tapCauseLogPretty)
171
+
172
+ yield* FiberHandle.run(leaderPushingFiberHandle, backgroundLeaderPushing)
173
+
174
+ const getMergeCounter = () =>
175
+ clientSession.sqliteDb.select<{ mergeCounter: number }>(
176
+ sql`SELECT mergeCounter FROM ${SystemTables.LEADER_MERGE_COUNTER_TABLE} WHERE id = 0`,
177
+ )[0]?.mergeCounter ?? 0
178
+
179
+ // NOTE We need to lazily call `.pull` as we want the cursor to be updated
180
+ yield* Stream.suspend(() =>
181
+ clientSession.leaderThread.events.pull({
182
+ cursor: { mergeCounter: getMergeCounter(), eventId: syncStateRef.current.localHead },
183
+ }),
184
+ ).pipe(
185
+ Stream.tap(({ payload, mergeCounter: leaderMergeCounter }) =>
186
+ Effect.gen(function* () {
187
+ // yield* Effect.logDebug('ClientSessionSyncProcessor:pull', payload)
188
+
189
+ if (clientSession.devtools.enabled) {
190
+ yield* clientSession.devtools.pullLatch.await
191
+ }
192
+
193
+ const mergeResult = SyncState.merge({
194
+ syncState: syncStateRef.current,
195
+ payload,
196
+ isClientEvent,
197
+ isEqualEvent: LiveStoreEvent.isEqualEncoded,
198
+ })
199
+
200
+ if (mergeResult._tag === 'unexpected-error') {
201
+ return yield* Effect.fail(mergeResult.cause)
202
+ } else if (mergeResult._tag === 'reject') {
203
+ return shouldNeverHappen('Unexpected reject in client-session-sync-processor', mergeResult)
204
+ }
205
+
206
+ syncStateRef.current = mergeResult.newSyncState
207
+ syncStateUpdateQueue.offer(mergeResult.newSyncState).pipe(Effect.runSync)
208
+
209
+ if (mergeResult._tag === 'rebase') {
210
+ span.addEvent('merge:pull:rebase', {
211
+ payloadTag: payload._tag,
212
+ payload: TRACE_VERBOSE ? JSON.stringify(payload) : undefined,
213
+ newEventsCount: mergeResult.newEvents.length,
214
+ rollbackCount: mergeResult.rollbackEvents.length,
215
+ res: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
216
+ leaderMergeCounter,
217
+ })
218
+
219
+ debugInfo.rebaseCount++
220
+
221
+ yield* FiberHandle.clear(leaderPushingFiberHandle)
222
+
223
+ // Reset the leader push queue since we're rebasing and will push again
224
+ yield* BucketQueue.clear(leaderPushQueue)
225
+
226
+ yield* FiberHandle.run(leaderPushingFiberHandle, backgroundLeaderPushing)
227
+
228
+ if (LS_DEV) {
229
+ Effect.logDebug(
230
+ 'merge:pull:rebase: rollback',
231
+ mergeResult.rollbackEvents.length,
232
+ ...mergeResult.rollbackEvents.slice(0, 10).map((_) => _.toJSON()),
233
+ { leaderMergeCounter },
234
+ ).pipe(Effect.provide(runtime), Effect.runSync)
235
+ }
236
+
237
+ for (let i = mergeResult.rollbackEvents.length - 1; i >= 0; i--) {
238
+ const event = mergeResult.rollbackEvents[i]!
239
+ if (event.meta.sessionChangeset._tag !== 'no-op' && event.meta.sessionChangeset._tag !== 'unset') {
240
+ rollback(event.meta.sessionChangeset.data)
241
+ event.meta.sessionChangeset = { _tag: 'unset' }
242
+ }
243
+ }
244
+
245
+ yield* BucketQueue.offerAll(leaderPushQueue, mergeResult.newSyncState.pending)
246
+ } else {
247
+ span.addEvent('merge:pull:advance', {
248
+ payloadTag: payload._tag,
249
+ payload: TRACE_VERBOSE ? JSON.stringify(payload) : undefined,
250
+ newEventsCount: mergeResult.newEvents.length,
251
+ res: TRACE_VERBOSE ? JSON.stringify(mergeResult) : undefined,
252
+ leaderMergeCounter,
253
+ })
254
+
255
+ debugInfo.advanceCount++
256
+ }
257
+
258
+ if (mergeResult.newEvents.length === 0) return
259
+
260
+ const writeTables = new Set<string>()
261
+ for (const event of mergeResult.newEvents) {
262
+ // TODO apply changeset if available (will require tracking of write tables as well)
263
+ const decodedEventDef = Schema.decodeSync(eventSchema)(event)
264
+ const res = materializeEvent(decodedEventDef, { otelContext, withChangeset: true })
265
+ for (const table of res.writeTables) {
266
+ writeTables.add(table)
267
+ }
268
+
269
+ event.meta.sessionChangeset = res.sessionChangeset
270
+ }
271
+
272
+ refreshTables(writeTables)
273
+ }).pipe(
274
+ Effect.tapCauseLogPretty,
275
+ Effect.catchAllCause((cause) => Effect.sync(() => clientSession.shutdown(cause))),
276
+ ),
277
+ ),
278
+ Stream.runDrain,
279
+ Effect.forever, // NOTE Whenever the leader changes, we need to re-start the stream
280
+ Effect.interruptible,
281
+ Effect.withSpan('client-session-sync-processor:pull'),
282
+ Effect.tapCauseLogPretty,
283
+ Effect.forkScoped,
284
+ )
285
+ })
286
+
287
+ return {
288
+ push,
289
+ boot,
290
+ syncState: Subscribable.make({
291
+ get: Effect.gen(function* () {
292
+ const syncState = syncStateRef.current
293
+ if (syncStateRef === undefined) return shouldNeverHappen('Not initialized')
294
+ return syncState
295
+ }),
296
+ changes: Stream.fromQueue(syncStateUpdateQueue),
297
+ }),
298
+ debug: {
299
+ print: () =>
300
+ Effect.gen(function* () {
301
+ console.log('debugInfo', debugInfo)
302
+ console.log('syncState', syncStateRef.current)
303
+ const pushQueueSize = yield* BucketQueue.size(leaderPushQueue)
304
+ console.log('pushQueueSize', pushQueueSize)
305
+ const pushQueueItems = yield* BucketQueue.peekAll(leaderPushQueue)
306
+ console.log(
307
+ 'pushQueueItems',
308
+ pushQueueItems.map((_) => _.toJSON()),
309
+ )
310
+ }).pipe(Effect.provide(runtime), Effect.runSync),
311
+ debugInfo: () => debugInfo,
312
+ },
313
+ } satisfies ClientSessionSyncProcessor
314
+ }
315
+
316
+ export interface ClientSessionSyncProcessor {
317
+ push: (
318
+ batch: ReadonlyArray<LiveStoreEvent.PartialAnyDecoded>,
319
+ options: { otelContext: otel.Context },
320
+ ) => {
321
+ writeTables: Set<string>
322
+ }
323
+ boot: Effect.Effect<void, UnexpectedError, Scope.Scope>
324
+ syncState: Subscribable.Subscribable<SyncState.SyncState>
325
+ debug: {
326
+ print: () => void
327
+ debugInfo: () => {
328
+ rebaseCount: number
329
+ advanceCount: number
330
+ }
331
+ }
332
+ }
package/src/sync/index.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export * from './sync.js'
2
2
  export * from './validate-push-payload.js'
3
- export * from './client-session-sync-processor.js'
3
+ export * from './ClientSessionSyncProcessor.js'