@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
@@ -2,14 +2,15 @@ import { Schema } from '@livestore/utils/effect'
2
2
 
3
3
  import { EventSequenceNumber } from '../schema/mod.ts'
4
4
 
5
- export class IsOfflineError extends Schema.TaggedError<IsOfflineError>(
6
- '~@livestore/common/IsOfflineError',
7
- )('IsOfflineError', {
8
- cause: Schema.Defect,
9
- }) {}
5
+ export class IsOfflineError extends Schema.TaggedError<IsOfflineError>('~@livestore/common/IsOfflineError')(
6
+ 'IsOfflineError',
7
+ {
8
+ cause: Schema.Defect(),
9
+ },
10
+ ) {}
10
11
 
11
12
  /** Unique ID generated by the backend when its created. Used to check whether the backend identity has changed. */
12
- export const BackendId = Schema.String.annotations({ title: '@livestore/sync-cf:BackendId' })
13
+ export const BackendId = Schema.String.annotate({ title: '@livestore/sync-cf:BackendId' })
13
14
 
14
15
  export class BackendIdMismatchError extends Schema.TaggedError<BackendIdMismatchError>(
15
16
  '~@livestore/common/BackendIdMismatchError',
@@ -18,9 +19,10 @@ export class BackendIdMismatchError extends Schema.TaggedError<BackendIdMismatch
18
19
  received: BackendId,
19
20
  }) {}
20
21
 
21
- export class ServerAheadError extends Schema.TaggedError<ServerAheadError>(
22
- '~@livestore/common/ServerAheadError',
23
- )('ServerAheadError', {
24
- minimumExpectedNum: EventSequenceNumber.Global.Schema,
25
- providedNum: EventSequenceNumber.Global.Schema,
26
- }) {}
22
+ export class ServerAheadError extends Schema.TaggedError<ServerAheadError>('~@livestore/common/ServerAheadError')(
23
+ 'ServerAheadError',
24
+ {
25
+ minimumExpectedNum: EventSequenceNumber.Global.Schema,
26
+ providedNum: EventSequenceNumber.Global.Schema,
27
+ },
28
+ ) {}
@@ -0,0 +1,78 @@
1
+ import { Vitest } from '@livestore/utils-dev/node-vitest'
2
+ import { Effect, Fiber, Option, Stream } from '@livestore/utils/effect'
3
+
4
+ import { EventSequenceNumber, type LiveStoreEvent } from '../schema/mod.ts'
5
+ import { makeMockSyncBackend } from './mock-sync-backend.ts'
6
+
7
+ Vitest.describe('makeMockSyncBackend', () => {
8
+ Vitest.live('broadcasts live events to every backend connection', () =>
9
+ Effect.gen(function* () {
10
+ const mockBackend = yield* makeMockSyncBackend({ startConnected: true })
11
+ const backendA = yield* mockBackend.makeSyncBackend
12
+ const backendB = yield* mockBackend.makeSyncBackend
13
+ const nextBatch = (backend: typeof backendA) =>
14
+ backend.pull(Option.none(), { live: true }).pipe(
15
+ Stream.filter((item) => item.batch.length > 0),
16
+ Stream.runFirstUnsafe,
17
+ )
18
+
19
+ const pullA = yield* nextBatch(backendA).pipe(Effect.forkScoped)
20
+ const pullB = yield* nextBatch(backendB).pipe(Effect.forkScoped)
21
+ const event = makeEvent(1)
22
+
23
+ yield* mockBackend.advance(event)
24
+
25
+ Vitest.expect((yield* Fiber.join(pullA)).batch.map((item) => item.eventEncoded)).toEqual([event])
26
+ Vitest.expect((yield* Fiber.join(pullB)).batch.map((item) => item.eventEncoded)).toEqual([event])
27
+ }),
28
+ )
29
+
30
+ Vitest.live('seeds a new backend connection with existing live events', () =>
31
+ Effect.gen(function* () {
32
+ const mockBackend = yield* makeMockSyncBackend({ startConnected: true })
33
+ const event = makeEvent(1)
34
+ yield* mockBackend.advance(event)
35
+
36
+ const backend = yield* mockBackend.makeSyncBackend
37
+ const item = yield* backend.pull(Option.none(), { live: true }).pipe(
38
+ Stream.filter((item) => item.batch.length > 0),
39
+ Stream.runFirstUnsafe,
40
+ )
41
+
42
+ Vitest.expect(item.batch.map((entry) => entry.eventEncoded)).toEqual([event])
43
+ }),
44
+ )
45
+
46
+ Vitest.live('filters seeded live events against the requested cursor', () =>
47
+ Effect.gen(function* () {
48
+ const mockBackend = yield* makeMockSyncBackend({ startConnected: true })
49
+ const existing = makeEvent(1)
50
+ const next = makeEvent(2)
51
+ yield* mockBackend.advance(existing)
52
+
53
+ const backend = yield* mockBackend.makeSyncBackend
54
+ const cursor = Option.some({
55
+ eventSequenceNumber: existing.seqNum,
56
+ metadata: Option.none(),
57
+ })
58
+ const pull = yield* backend.pull(cursor, { live: true }).pipe(
59
+ Stream.filter((item) => item.batch.length > 0),
60
+ Stream.runFirstUnsafe,
61
+ Effect.forkScoped,
62
+ )
63
+
64
+ yield* mockBackend.advance(next)
65
+
66
+ Vitest.expect((yield* Fiber.join(pull)).batch.map((entry) => entry.eventEncoded)).toEqual([next])
67
+ }),
68
+ )
69
+ })
70
+
71
+ const makeEvent = (seqNum: number): LiveStoreEvent.Global.Encoded => ({
72
+ name: 'v1.TestEvent',
73
+ args: { id: `event-${seqNum}` },
74
+ seqNum: EventSequenceNumber.Global.make(seqNum),
75
+ parentSeqNum: EventSequenceNumber.Global.make(seqNum - 1),
76
+ clientId: 'client-a',
77
+ sessionId: 'session-a',
78
+ })
@@ -1,9 +1,18 @@
1
- import type { Schema, Scope } from '@livestore/utils/effect'
2
- import { Effect, Mailbox, Option, Queue, Ref, Stream, SubscriptionRef } from '@livestore/utils/effect'
1
+ import {
2
+ type Schema,
3
+ type Scope,
4
+ Effect,
5
+ Option,
6
+ Queue,
7
+ Ref,
8
+ Semaphore,
9
+ Stream,
10
+ SubscriptionRef,
11
+ } from '@livestore/utils/effect'
3
12
 
4
13
  import { UnknownError } from '../errors.ts'
5
14
  import { EventSequenceNumber, type LiveStoreEvent } from '../schema/mod.ts'
6
- import { type BackendIdMismatchError, type ServerAheadError } from './errors.ts'
15
+ import type { BackendIdMismatchError, ServerAheadError } from './errors.ts'
7
16
  import * as SyncBackend from './sync-backend.ts'
8
17
  import { validatePushPayload } from './validate-push-payload.ts'
9
18
 
@@ -40,7 +49,7 @@ export const makeMockSyncBackend = (
40
49
  ): Effect.Effect<MockSyncBackend, UnknownError, Scope.Scope> =>
41
50
  Effect.gen(function* () {
42
51
  const span = yield* Effect.currentSpan.pipe(Effect.orDie)
43
- const semaphore = yield* Effect.makeSemaphore(1)
52
+ const semaphore = yield* Semaphore.make(1)
44
53
 
45
54
  // State refs
46
55
  const syncHeadRef = yield* Ref.make(EventSequenceNumber.Client.ROOT.global)
@@ -48,12 +57,22 @@ export const makeMockSyncBackend = (
48
57
  const syncIsConnectedRef = yield* SubscriptionRef.make(options?.startConnected ?? false)
49
58
 
50
59
  // Queues for streaming
51
- const syncPullQueue = yield* Queue.unbounded<LiveStoreEvent.Global.Encoded>()
52
- const pushedEventsQueue = yield* Mailbox.make<LiveStoreEvent.Global.Encoded>()
60
+ const syncPullQueues = new Set<Queue.Queue<LiveStoreEvent.Global.Encoded>>()
61
+ const pushedEventsQueue = yield* Queue.unbounded<LiveStoreEvent.Global.Encoded>()
62
+
63
+ yield* Effect.addFinalizer(() =>
64
+ Effect.gen(function* () {
65
+ for (const queue of syncPullQueues) yield* Queue.shutdown(queue)
66
+ syncPullQueues.clear()
67
+ }),
68
+ )
53
69
 
54
70
  // Failure simulation state
55
71
  const failPushRef = yield* Ref.make<
56
- FailureState<UnknownError | ServerAheadError | BackendIdMismatchError, [ReadonlyArray<LiveStoreEvent.Global.Encoded>]>
72
+ FailureState<
73
+ UnknownError | ServerAheadError | BackendIdMismatchError,
74
+ [ReadonlyArray<LiveStoreEvent.Global.Encoded>]
75
+ >
57
76
  >({ remaining: 0, error: undefined })
58
77
  const failPullRef = yield* Ref.make<FailureState<UnknownError | BackendIdMismatchError, []>>({
59
78
  remaining: 0,
@@ -85,27 +104,12 @@ export const makeMockSyncBackend = (
85
104
 
86
105
  const pullNonLive = (cursor: Option.Option<{ eventSequenceNumber: EventSequenceNumber.Global.Type }>) =>
87
106
  Effect.gen(function* () {
88
- const lastSeen = Option.match(cursor, {
89
- onNone: () => EventSequenceNumber.Client.ROOT.global,
90
- onSome: (_) => _.eventSequenceNumber,
91
- })
107
+ const lastSeen = cursorPosition(cursor)
92
108
  const allEvents = yield* Ref.get(allEventsRef)
93
- const slice = allEvents.filter((e) => e.seqNum > lastSeen)
94
-
95
- // Split into chunks with remaining count for pageInfo
96
- const chunks: Array<{ events: LiveStoreEvent.Global.Encoded[]; remaining: number }> = []
97
- for (let i = 0; i < slice.length; i += nonLiveChunkSize) {
98
- const end = Math.min(i + nonLiveChunkSize, slice.length)
99
- chunks.push({
100
- events: slice.slice(i, end),
101
- remaining: Math.max(slice.length - end, 0),
102
- })
103
- }
104
- // Always return at least one empty chunk
105
- if (chunks.length === 0) {
106
- chunks.push({ events: [], remaining: 0 })
107
- }
108
- return chunks
109
+ return chunkEvents(
110
+ allEvents.filter((event) => event.seqNum > lastSeen),
111
+ nonLiveChunkSize,
112
+ )
109
113
  }).pipe(
110
114
  Effect.map((chunks) =>
111
115
  Stream.fromIterable(chunks).pipe(
@@ -119,18 +123,38 @@ export const makeMockSyncBackend = (
119
123
  Stream.flatten(),
120
124
  )
121
125
 
122
- const pullLive = Stream.concat(
123
- Stream.make(SyncBackend.pullResItemEmpty()),
124
- Stream.fromQueue(syncPullQueue).pipe(
125
- Stream.chunks,
126
- Stream.map((chunk) => ({
127
- batch: [...chunk].map((eventEncoded) => ({ eventEncoded, metadata: Option.none() })),
128
- pageInfo: SyncBackend.pageInfoNoMore,
129
- })),
130
- ),
131
- )
132
-
133
126
  const makeSyncBackend = Effect.gen(function* () {
127
+ // Every backend connection needs its own live cursor. A shared queue would
128
+ // load-balance events across Clients instead of broadcasting the log.
129
+ const syncPullQueue = yield* Effect.acquireRelease(
130
+ Queue.unbounded<LiveStoreEvent.Global.Encoded>(),
131
+ Queue.shutdown,
132
+ )
133
+ yield* semaphore.withPermits(1)(
134
+ Effect.gen(function* () {
135
+ const existingEvents = yield* Ref.get(allEventsRef)
136
+ syncPullQueues.add(syncPullQueue)
137
+ yield* Queue.offerAll(syncPullQueue, existingEvents)
138
+ }),
139
+ )
140
+ yield* Effect.addFinalizer(() => Effect.sync(() => syncPullQueues.delete(syncPullQueue)))
141
+
142
+ const pullLive = (cursor: Option.Option<{ eventSequenceNumber: EventSequenceNumber.Global.Type }>) => {
143
+ const lastSeen = cursorPosition(cursor)
144
+ return Stream.concat(
145
+ Stream.make(SyncBackend.pullResItemEmpty()),
146
+ Stream.fromQueue(syncPullQueue).pipe(
147
+ Stream.chunks,
148
+ Stream.map((chunk) => ({
149
+ batch: [...chunk]
150
+ .filter((eventEncoded) => eventEncoded.seqNum > lastSeen)
151
+ .map((eventEncoded) => ({ eventEncoded, metadata: Option.none() })),
152
+ pageInfo: SyncBackend.pageInfoNoMore,
153
+ })),
154
+ ),
155
+ )
156
+ }
157
+
134
158
  // TODO consider making offline state actively error pull/push.
135
159
  // Currently, offline only reflects in `isConnected`, while operations still succeed,
136
160
  // mirroring how some real providers behave during transient disconnects.
@@ -145,7 +169,7 @@ export const makeMockSyncBackend = (
145
169
  new UnknownError({ cause: new Error('MockSyncBackend: simulated pull failure') }),
146
170
  ),
147
171
  ).pipe(
148
- Stream.flatMap(() => (pullOptions?.live === true ? pullLive : pullNonLive(cursor))),
172
+ Stream.flatMap(() => (pullOptions?.live === true ? pullLive(cursor) : pullNonLive(cursor))),
149
173
  Stream.withSpan('MockSyncBackend:pull', { parent: span }),
150
174
  ),
151
175
  push: (batch) =>
@@ -161,8 +185,11 @@ export const makeMockSyncBackend = (
161
185
 
162
186
  yield* Effect.sleep(10).pipe(Effect.withSpan('MockSyncBackend:push:sleep')) // Simulate network latency
163
187
 
164
- yield* pushedEventsQueue.offerAll(batch)
165
- yield* syncPullQueue.offerAll(batch)
188
+ yield* Queue.offerAll(pushedEventsQueue, batch)
189
+ yield* Effect.forEach(syncPullQueues, (queue) => Queue.offerAll(queue, batch), {
190
+ concurrency: 'unbounded',
191
+ discard: true,
192
+ })
166
193
  yield* Ref.update(allEventsRef, (events) => events.concat(batch))
167
194
  yield* Ref.set(syncHeadRef, batch.at(-1)!.seqNum)
168
195
  }).pipe(
@@ -187,7 +214,10 @@ export const makeMockSyncBackend = (
187
214
  Effect.gen(function* () {
188
215
  yield* Ref.set(syncHeadRef, batch.at(-1)!.seqNum)
189
216
  yield* Ref.update(allEventsRef, (events) => events.concat(batch))
190
- yield* syncPullQueue.offerAll(batch)
217
+ yield* Effect.forEach(syncPullQueues, (queue) => Queue.offerAll(queue, batch), {
218
+ concurrency: 'unbounded',
219
+ discard: true,
220
+ })
191
221
  }).pipe(
192
222
  Effect.withSpan('MockSyncBackend:advance', {
193
223
  parent: span,
@@ -203,13 +233,11 @@ export const makeMockSyncBackend = (
203
233
  ) => Effect.Effect<never, UnknownError | ServerAheadError | BackendIdMismatchError>,
204
234
  ) => Ref.set(failPushRef, { remaining: count, error })
205
235
 
206
- const failNextPulls = (
207
- count: number,
208
- error?: () => Effect.Effect<never, UnknownError | BackendIdMismatchError>,
209
- ) => Ref.set(failPullRef, { remaining: count, error })
236
+ const failNextPulls = (count: number, error?: () => Effect.Effect<never, UnknownError | BackendIdMismatchError>) =>
237
+ Ref.set(failPullRef, { remaining: count, error })
210
238
 
211
239
  return {
212
- pushedEvents: Mailbox.toStream(pushedEventsQueue),
240
+ pushedEvents: Stream.fromQueue(pushedEventsQueue),
213
241
  connect: SubscriptionRef.set(syncIsConnectedRef, true),
214
242
  disconnect: SubscriptionRef.set(syncIsConnectedRef, false),
215
243
  makeSyncBackend,
@@ -224,3 +252,21 @@ interface FailureState<E, Args extends unknown[]> {
224
252
  remaining: number
225
253
  error: ((...args: Args) => Effect.Effect<never, E>) | undefined
226
254
  }
255
+
256
+ const cursorPosition = (
257
+ cursor: Option.Option<{ eventSequenceNumber: EventSequenceNumber.Global.Type }>,
258
+ ): EventSequenceNumber.Global.Type =>
259
+ Option.match(cursor, {
260
+ onNone: () => EventSequenceNumber.Client.ROOT.global,
261
+ onSome: (_) => _.eventSequenceNumber,
262
+ })
263
+
264
+ const chunkEvents = (events: ReadonlyArray<LiveStoreEvent.Global.Encoded>, chunkSize: number) => {
265
+ const chunks: Array<{ events: ReadonlyArray<LiveStoreEvent.Global.Encoded>; remaining: number }> = []
266
+ for (let index = 0; index < events.length; index += chunkSize) {
267
+ const end = Math.min(index + chunkSize, events.length)
268
+ chunks.push({ events: events.slice(index, end), remaining: Math.max(events.length - end, 0) })
269
+ }
270
+ if (chunks.length === 0) chunks.push({ events: [], remaining: 0 })
271
+ return chunks
272
+ }
@@ -1,5 +1,5 @@
1
1
  import { shouldNeverHappen } from '@livestore/utils'
2
- import { Graph } from '@livestore/utils/effect'
2
+ import { Graph, Option } from '@livestore/utils/effect'
3
3
 
4
4
  import type { EventDefFactsGroup } from '../../schema/EventDef/mod.ts'
5
5
  import * as EventSequenceNumber from '../../schema/EventSequenceNumber/mod.ts'
@@ -60,6 +60,8 @@ export class HistoryDag {
60
60
  private readonly options: HistoryDagOptions
61
61
  private readonly idToIndex: Map<string, Graph.NodeIndex>
62
62
  private readonly indexToId: Map<Graph.NodeIndex, string>
63
+ private readonly incomingEdges: Map<Graph.NodeIndex, Set<Graph.EdgeIndex>>
64
+ private readonly outgoingEdges: Map<Graph.NodeIndex, Set<Graph.EdgeIndex>>
63
65
  private readonly graph: Graph.MutableDirectedGraph<HistoryDagNode, HistoryDagEdgeAttributes>
64
66
 
65
67
  private constructor({
@@ -77,6 +79,8 @@ export class HistoryDag {
77
79
  this.options = { ...defaultOptions, ...options }
78
80
  this.idToIndex = idToIndex !== undefined ? new Map(idToIndex) : new Map()
79
81
  this.indexToId = indexToId !== undefined ? new Map(indexToId) : new Map()
82
+ this.incomingEdges = new Map()
83
+ this.outgoingEdges = new Map()
80
84
  }
81
85
 
82
86
  static create(options?: Partial<HistoryDagOptions>): HistoryDag {
@@ -88,11 +92,11 @@ export class HistoryDag {
88
92
  const clone = HistoryDag.create(this.options)
89
93
 
90
94
  for (const [id, index] of this.idToIndex) {
91
- const node = this.graph.nodes.get(index) ?? shouldNeverHappen(`HistoryDag.copy missing node for ${id}`)
95
+ const node = this.nodeAt(index, `HistoryDag.copy missing node for ${id}`)
92
96
  clone.addNode(id, cloneHistoryDagNode(node))
93
97
  }
94
98
 
95
- for (const edge of this.graph.edges.values()) {
99
+ for (const [, edge] of Graph.edges(this.graph)) {
96
100
  const sourceId = this.indexToId.get(edge.source) ?? shouldNeverHappen('HistoryDag.copy missing source id')
97
101
  const targetId = this.indexToId.get(edge.target) ?? shouldNeverHappen('HistoryDag.copy missing target id')
98
102
  clone.addEdge(sourceId, targetId, { ...edge.data })
@@ -117,6 +121,8 @@ export class HistoryDag {
117
121
  const nodeIndex = Graph.addNode(this.graph, attributes)
118
122
  this.idToIndex.set(id, nodeIndex)
119
123
  this.indexToId.set(nodeIndex, id)
124
+ this.incomingEdges.set(nodeIndex, new Set())
125
+ this.outgoingEdges.set(nodeIndex, new Set())
120
126
  }
121
127
 
122
128
  hasNode(id: string): boolean {
@@ -129,8 +135,7 @@ export class HistoryDag {
129
135
  return shouldNeverHappen(`HistoryDag node ${id} not found`)
130
136
  }
131
137
 
132
- const node = this.graph.nodes.get(index)
133
- return node ?? shouldNeverHappen(`HistoryDag node data missing for ${id}`)
138
+ return this.nodeAt(index, `HistoryDag node data missing for ${id}`)
134
139
  }
135
140
 
136
141
  nodes(): IterableIterator<string> {
@@ -140,7 +145,7 @@ export class HistoryDag {
140
145
  nodeEntries(): IterableIterator<{ key: string; attributes: HistoryDagNode }> {
141
146
  return function* (this: HistoryDag) {
142
147
  for (const [id, index] of this.idToIndex) {
143
- const attributes = this.graph.nodes.get(index) ?? shouldNeverHappen(`HistoryDag node data missing for ${id}`)
148
+ const attributes = this.nodeAt(index, `HistoryDag node data missing for ${id}`)
144
149
  yield { key: id, attributes }
145
150
  }
146
151
  }.call(this)
@@ -158,7 +163,15 @@ export class HistoryDag {
158
163
  return shouldNeverHappen(`HistoryDag edge references unknown nodes: ${sourceId} -> ${targetId}`)
159
164
  }
160
165
 
161
- return Graph.addEdge(this.graph, sourceIndex, targetIndex, attributes)
166
+ const edgeIndex = Graph.addEdge(this.graph, sourceIndex, targetIndex, attributes)
167
+ const outgoingEdges = this.outgoingEdges.get(sourceIndex)
168
+ const incomingEdges = this.incomingEdges.get(targetIndex)
169
+ if (outgoingEdges === undefined || incomingEdges === undefined) {
170
+ return shouldNeverHappen(`HistoryDag edge index missing for ${sourceId} -> ${targetId}`)
171
+ }
172
+ outgoingEdges.add(edgeIndex)
173
+ incomingEdges.add(edgeIndex)
174
+ return edgeIndex
162
175
  }
163
176
 
164
177
  edges(sourceId: string, targetId: string): Array<Graph.EdgeIndex> {
@@ -169,14 +182,9 @@ export class HistoryDag {
169
182
  return []
170
183
  }
171
184
 
172
- const adjacency = this.graph.adjacency.get(sourceIndex)
173
- if (adjacency === undefined) {
174
- return []
175
- }
176
-
177
- return adjacency.filter((edgeIndex) => {
178
- const edge = this.graph.edges.get(edgeIndex)
179
- return edge !== undefined && edge.target === targetIndex
185
+ return Array.from(this.outgoingEdges.get(sourceIndex) ?? []).filter((edgeIndex) => {
186
+ const edge = Option.getOrUndefined(Graph.getEdge(this.graph, edgeIndex))
187
+ return edge?.target === targetIndex
180
188
  })
181
189
  }
182
190
 
@@ -185,8 +193,7 @@ export class HistoryDag {
185
193
  if (index === undefined) {
186
194
  return []
187
195
  }
188
- const incoming = this.graph.reverseAdjacency.get(index)
189
- return incoming !== undefined ? [...incoming] : []
196
+ return [...(this.incomingEdges.get(index) ?? [])]
190
197
  }
191
198
 
192
199
  outboundEdgeEntries(id: string): Array<HistoryDagEdgeEntry> {
@@ -195,12 +202,7 @@ export class HistoryDag {
195
202
  return []
196
203
  }
197
204
 
198
- const adjacency = this.graph.adjacency.get(index)
199
- if (adjacency === undefined) {
200
- return []
201
- }
202
-
203
- return adjacency
205
+ return [...(this.outgoingEdges.get(index) ?? [])]
204
206
  .map((edgeIndex) => this.edgeEntry(edgeIndex))
205
207
  .filter((entry): entry is HistoryDagEdgeEntry => entry !== undefined)
206
208
  }
@@ -211,19 +213,13 @@ export class HistoryDag {
211
213
  return []
212
214
  }
213
215
 
214
- const adjacency = this.graph.reverseAdjacency.get(index)
215
- if (adjacency === undefined) {
216
- return []
217
- }
218
-
219
- return adjacency
216
+ return [...(this.incomingEdges.get(index) ?? [])]
220
217
  .map((edgeIndex) => this.edgeEntry(edgeIndex))
221
218
  .filter((entry): entry is HistoryDagEdgeEntry => entry !== undefined)
222
219
  }
223
220
 
224
221
  getEdgeAttributes(edgeIndex: Graph.EdgeIndex): HistoryDagEdgeAttributes {
225
- const edge = this.graph.edges.get(edgeIndex)
226
- return edge?.data ?? shouldNeverHappen(`HistoryDag edge ${edgeIndex} not found`)
222
+ return this.edgeAt(edgeIndex, `HistoryDag edge ${edgeIndex} not found`).data
227
223
  }
228
224
 
229
225
  getEdgeAttribute<TKey extends keyof HistoryDagEdgeAttributes>(
@@ -235,13 +231,13 @@ export class HistoryDag {
235
231
  }
236
232
 
237
233
  source(edgeIndex: Graph.EdgeIndex): string {
238
- const edge = this.graph.edges.get(edgeIndex)
234
+ const edge = Option.getOrUndefined(Graph.getEdge(this.graph, edgeIndex))
239
235
  const sourceId = edge !== undefined ? this.indexToId.get(edge.source) : undefined
240
236
  return sourceId ?? shouldNeverHappen(`HistoryDag edge ${edgeIndex} missing source`)
241
237
  }
242
238
 
243
239
  target(edgeIndex: Graph.EdgeIndex): string {
244
- const edge = this.graph.edges.get(edgeIndex)
240
+ const edge = Option.getOrUndefined(Graph.getEdge(this.graph, edgeIndex))
245
241
  const targetId = edge !== undefined ? this.indexToId.get(edge.target) : undefined
246
242
  return targetId ?? shouldNeverHappen(`HistoryDag edge ${edgeIndex} missing target`)
247
243
  }
@@ -252,9 +248,20 @@ export class HistoryDag {
252
248
  return
253
249
  }
254
250
 
251
+ for (const edgeIndex of this.outgoingEdges.get(index) ?? []) {
252
+ const edge = this.edgeAt(edgeIndex, `HistoryDag edge ${edgeIndex} missing while dropping ${id}`)
253
+ removeEdgeIndex(this.incomingEdges, edge.target, edgeIndex)
254
+ }
255
+ for (const edgeIndex of this.incomingEdges.get(index) ?? []) {
256
+ const edge = this.edgeAt(edgeIndex, `HistoryDag edge ${edgeIndex} missing while dropping ${id}`)
257
+ removeEdgeIndex(this.outgoingEdges, edge.source, edgeIndex)
258
+ }
259
+
255
260
  Graph.removeNode(this.graph, index)
256
261
  this.idToIndex.delete(id)
257
262
  this.indexToId.delete(index)
263
+ this.incomingEdges.delete(index)
264
+ this.outgoingEdges.delete(index)
258
265
  }
259
266
 
260
267
  get size(): number {
@@ -262,7 +269,7 @@ export class HistoryDag {
262
269
  }
263
270
 
264
271
  private edgeEntry(edgeIndex: Graph.EdgeIndex): HistoryDagEdgeEntry | undefined {
265
- const edge = this.graph.edges.get(edgeIndex)
272
+ const edge = Option.getOrUndefined(Graph.getEdge(this.graph, edgeIndex))
266
273
  if (edge === undefined) {
267
274
  return undefined
268
275
  }
@@ -281,6 +288,25 @@ export class HistoryDag {
281
288
  attributes: edge.data,
282
289
  }
283
290
  }
291
+
292
+ private nodeAt(index: Graph.NodeIndex, message: string): HistoryDagNode {
293
+ return Option.getOrElse(Graph.getNode(this.graph, index), () => shouldNeverHappen(message))
294
+ }
295
+
296
+ private edgeAt(edgeIndex: Graph.EdgeIndex, message: string): Graph.Edge<HistoryDagEdgeAttributes> {
297
+ return Option.getOrElse(Graph.getEdge(this.graph, edgeIndex), () => shouldNeverHappen(message))
298
+ }
299
+ }
300
+
301
+ const removeEdgeIndex = (
302
+ edgeIndices: Map<Graph.NodeIndex, Set<Graph.EdgeIndex>>,
303
+ nodeIndex: Graph.NodeIndex,
304
+ edgeIndex: Graph.EdgeIndex,
305
+ ): void => {
306
+ const indices = edgeIndices.get(nodeIndex) ?? shouldNeverHappen(`HistoryDag edge index missing for node ${nodeIndex}`)
307
+ if (indices.delete(edgeIndex) === false) {
308
+ shouldNeverHappen(`HistoryDag edge ${edgeIndex} missing from node ${nodeIndex}`)
309
+ }
284
310
  }
285
311
 
286
312
  export const emptyHistoryDag = (): HistoryDag => HistoryDag.create({ allowSelfLoops: false })
@@ -48,7 +48,10 @@ export const historyDagFromNodes = (dagNodes: HistoryDagNode[], options?: { skip
48
48
  const parentSeqNumStr = dag.source(parentEdge)
49
49
  const parentNode = dag.getNodeAttributes(parentSeqNumStr)
50
50
 
51
- if (parentNode.factsGroup.modifySet.has(factKey) === true || parentNode.factsGroup.modifyUnset.has(factKey) === true) {
51
+ if (
52
+ parentNode.factsGroup.modifySet.has(factKey) === true ||
53
+ parentNode.factsGroup.modifyUnset.has(factKey) === true
54
+ ) {
52
55
  return parentNode
53
56
  }
54
57
 
@@ -27,11 +27,11 @@ const facts = {
27
27
  const eventDefs = {
28
28
  add: defineEvent({
29
29
  name: 'add',
30
- schema: Schema.Struct({ value: Schema.Number }),
30
+ schema: Schema.Struct({ value: Schema.Finite }),
31
31
  }),
32
32
  multiply: defineEvent({
33
33
  name: 'multiply',
34
- schema: Schema.Struct({ value: Schema.Number }),
34
+ schema: Schema.Struct({ value: Schema.Finite }),
35
35
  facts: ({ value }, currentFacts) => ({
36
36
  modify: {
37
37
  set: value === 0 || currentFacts.has(facts.multiplyByZero) === true ? [facts.multiplyByZero] : [],
@@ -146,7 +146,7 @@ export const toEventNodes = (
146
146
  const eventDef = eventDefs[partialEvent.name]!
147
147
  const eventNum = EventSequenceNumber.Client.nextPair({
148
148
  seqNum: currentEventSequenceNumber,
149
- isClient: eventDef.options.clientOnly,
149
+ isClientOnly: eventDef.options.clientOnly,
150
150
  }).seqNum
151
151
  currentEventSequenceNumber = eventNum
152
152
 
@@ -0,0 +1,87 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import * as EventSequenceNumber from '../../../schema/EventSequenceNumber/mod.ts'
4
+ import { HistoryDag, type HistoryDagNode } from '../history-dag-common.ts'
5
+
6
+ const makeNode = (global: number, parentGlobal = global - 1): HistoryDagNode => ({
7
+ seqNum: EventSequenceNumber.Client.Composite.make({ global, client: 0 }),
8
+ parentSeqNum: EventSequenceNumber.Client.Composite.make({ global: parentGlobal, client: 0 }),
9
+ name: `event-${global}`,
10
+ args: {},
11
+ factsGroup: {
12
+ depRead: new Map(),
13
+ depRequire: new Map(),
14
+ modifySet: new Map(),
15
+ modifyUnset: new Map(),
16
+ },
17
+ meta: undefined,
18
+ clientId: 'client-id',
19
+ sessionId: 'session-id',
20
+ })
21
+
22
+ describe('HistoryDag', () => {
23
+ it('preserves parallel edge order and removes incident edges with a node', () => {
24
+ const dag = HistoryDag.create()
25
+ dag.addNode('a', makeNode(1))
26
+ dag.addNode('b', makeNode(2))
27
+ dag.addNode('c', makeNode(3))
28
+
29
+ const parentEdge = dag.addEdge('a', 'b', { type: 'parent' })
30
+ const factsEdge = dag.addEdge('a', 'b', { type: 'facts' })
31
+ const incomingEdge = dag.addEdge('c', 'b', { type: 'facts' })
32
+
33
+ expect(dag.edges('a', 'b')).toEqual([parentEdge, factsEdge])
34
+ expect(dag.inEdges('b')).toEqual([parentEdge, factsEdge, incomingEdge])
35
+ expect(dag.inboundEdgeEntries('b')).toEqual([
36
+ { edge: parentEdge, source: 'a', target: 'b', attributes: { type: 'parent' } },
37
+ { edge: factsEdge, source: 'a', target: 'b', attributes: { type: 'facts' } },
38
+ { edge: incomingEdge, source: 'c', target: 'b', attributes: { type: 'facts' } },
39
+ ])
40
+ expect(dag.outboundEdgeEntries('a')).toEqual([
41
+ { edge: parentEdge, source: 'a', target: 'b', attributes: { type: 'parent' } },
42
+ { edge: factsEdge, source: 'a', target: 'b', attributes: { type: 'facts' } },
43
+ ])
44
+ expect(dag.source(parentEdge)).toBe('a')
45
+ expect(dag.target(parentEdge)).toBe('b')
46
+ expect(dag.getEdgeAttribute(parentEdge, 'type')).toBe('parent')
47
+
48
+ dag.dropNode('b')
49
+
50
+ expect(dag.edges('a', 'b')).toEqual([])
51
+ expect(dag.outboundEdgeEntries('a')).toEqual([])
52
+ expect(dag.outboundEdgeEntries('c')).toEqual([])
53
+ })
54
+
55
+ it('keeps the adjacency indexes consistent for self-loops', () => {
56
+ const dag = HistoryDag.create({ allowSelfLoops: true })
57
+ dag.addNode('a', makeNode(1))
58
+
59
+ const edge = dag.addEdge('a', 'a', { type: 'facts' })
60
+
61
+ expect(dag.edges('a', 'a')).toEqual([edge])
62
+ expect(dag.inEdges('a')).toEqual([edge])
63
+ expect(dag.inboundEdgeEntries('a')).toEqual([{ edge, source: 'a', target: 'a', attributes: { type: 'facts' } }])
64
+
65
+ dag.dropNode('a')
66
+
67
+ expect(dag.size).toBe(0)
68
+ expect(dag.edges('a', 'a')).toEqual([])
69
+ })
70
+
71
+ it('copies nodes, facts, and edges without sharing mutable data', () => {
72
+ const dag = HistoryDag.create()
73
+ const nodeA = makeNode(1)
74
+ nodeA.factsGroup.modifySet = new Map([['key', 'value']])
75
+ dag.addNode('a', nodeA)
76
+ dag.addNode('b', makeNode(2))
77
+ dag.addEdge('a', 'b', { type: 'parent' })
78
+
79
+ const copy = dag.copy()
80
+ const copiedNode = copy.getNodeAttributes('a')
81
+
82
+ expect(copiedNode).not.toBe(nodeA)
83
+ expect(copiedNode.factsGroup.modifySet).not.toBe(nodeA.factsGroup.modifySet)
84
+ expect(copy.outboundEdgeEntries('a')).toMatchObject([{ source: 'a', target: 'b', attributes: { type: 'parent' } }])
85
+ expect(copy.getEdgeAttributes(copy.edges('a', 'b')[0]!)).not.toBe(dag.getEdgeAttributes(dag.edges('a', 'b')[0]!))
86
+ })
87
+ })