@livestore/common 0.4.0-dev.22 → 0.4.0-dev.24

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 (314) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/ClientSessionLeaderThreadProxy.d.ts +9 -9
  3. package/dist/ClientSessionLeaderThreadProxy.d.ts.map +1 -1
  4. package/dist/WorkerTransportError.d.ts +11 -0
  5. package/dist/WorkerTransportError.d.ts.map +1 -0
  6. package/dist/WorkerTransportError.js +11 -0
  7. package/dist/WorkerTransportError.js.map +1 -0
  8. package/dist/adapter-types.d.ts +3 -3
  9. package/dist/adapter-types.d.ts.map +1 -1
  10. package/dist/adapter-types.js.map +1 -1
  11. package/dist/bounded-collections.d.ts.map +1 -1
  12. package/dist/bounded-collections.js +6 -4
  13. package/dist/bounded-collections.js.map +1 -1
  14. package/dist/debug-info.js +4 -4
  15. package/dist/debug-info.js.map +1 -1
  16. package/dist/devtools/devtools-messages-common.js +1 -1
  17. package/dist/devtools/devtools-messages-common.js.map +1 -1
  18. package/dist/devtools/mod.js +1 -1
  19. package/dist/devtools/mod.js.map +1 -1
  20. package/dist/errors.d.ts +15 -15
  21. package/dist/errors.d.ts.map +1 -1
  22. package/dist/errors.js +11 -11
  23. package/dist/errors.js.map +1 -1
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +2 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/leader-thread/LeaderSyncProcessor.d.ts +20 -6
  29. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
  30. package/dist/leader-thread/LeaderSyncProcessor.js +287 -257
  31. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  32. package/dist/leader-thread/RejectedPushError.d.ts +107 -0
  33. package/dist/leader-thread/RejectedPushError.d.ts.map +1 -0
  34. package/dist/leader-thread/RejectedPushError.js +78 -0
  35. package/dist/leader-thread/RejectedPushError.js.map +1 -0
  36. package/dist/leader-thread/connection.js +1 -1
  37. package/dist/leader-thread/connection.js.map +1 -1
  38. package/dist/leader-thread/eventlog.d.ts.map +1 -1
  39. package/dist/leader-thread/eventlog.js +12 -11
  40. package/dist/leader-thread/eventlog.js.map +1 -1
  41. package/dist/leader-thread/leader-worker-devtools.d.ts +1 -2
  42. package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
  43. package/dist/leader-thread/leader-worker-devtools.js +25 -14
  44. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  45. package/dist/leader-thread/make-leader-thread-layer.d.ts +8 -3
  46. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  47. package/dist/leader-thread/make-leader-thread-layer.js +7 -10
  48. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  49. package/dist/leader-thread/make-leader-thread-layer.test.js +1 -1
  50. package/dist/leader-thread/make-leader-thread-layer.test.js.map +1 -1
  51. package/dist/leader-thread/materialize-event.js +4 -4
  52. package/dist/leader-thread/materialize-event.js.map +1 -1
  53. package/dist/leader-thread/recreate-db.js +1 -1
  54. package/dist/leader-thread/recreate-db.js.map +1 -1
  55. package/dist/leader-thread/shutdown-channel.d.ts +2 -2
  56. package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
  57. package/dist/leader-thread/shutdown-channel.js +2 -2
  58. package/dist/leader-thread/shutdown-channel.js.map +1 -1
  59. package/dist/leader-thread/stream-events.d.ts.map +1 -1
  60. package/dist/leader-thread/stream-events.js +4 -3
  61. package/dist/leader-thread/stream-events.js.map +1 -1
  62. package/dist/leader-thread/types.d.ts +7 -6
  63. package/dist/leader-thread/types.d.ts.map +1 -1
  64. package/dist/leader-thread/types.js.map +1 -1
  65. package/dist/logging.js +4 -4
  66. package/dist/logging.js.map +1 -1
  67. package/dist/make-client-session.js +2 -2
  68. package/dist/make-client-session.js.map +1 -1
  69. package/dist/materializer-helper.js +6 -6
  70. package/dist/materializer-helper.js.map +1 -1
  71. package/dist/otel.d.ts +1 -1
  72. package/dist/otel.d.ts.map +1 -1
  73. package/dist/otel.js +2 -2
  74. package/dist/otel.js.map +1 -1
  75. package/dist/rematerialize-from-eventlog.d.ts +1 -1
  76. package/dist/rematerialize-from-eventlog.d.ts.map +1 -1
  77. package/dist/rematerialize-from-eventlog.js +11 -9
  78. package/dist/rematerialize-from-eventlog.js.map +1 -1
  79. package/dist/schema/EventDef/define.d.ts +2 -2
  80. package/dist/schema/EventDef/define.d.ts.map +1 -1
  81. package/dist/schema/EventDef/define.js +4 -4
  82. package/dist/schema/EventDef/define.js.map +1 -1
  83. package/dist/schema/EventDef/deprecated.js +3 -3
  84. package/dist/schema/EventDef/deprecated.js.map +1 -1
  85. package/dist/schema/EventDef/deprecated.test.js +1 -1
  86. package/dist/schema/EventDef/deprecated.test.js.map +1 -1
  87. package/dist/schema/EventSequenceNumber/client.d.ts.map +1 -1
  88. package/dist/schema/EventSequenceNumber/client.js +11 -11
  89. package/dist/schema/EventSequenceNumber/client.js.map +1 -1
  90. package/dist/schema/EventSequenceNumber.test.js +1 -1
  91. package/dist/schema/EventSequenceNumber.test.js.map +1 -1
  92. package/dist/schema/LiveStoreEvent/client.d.ts +8 -0
  93. package/dist/schema/LiveStoreEvent/client.d.ts.map +1 -1
  94. package/dist/schema/LiveStoreEvent/client.js +15 -3
  95. package/dist/schema/LiveStoreEvent/client.js.map +1 -1
  96. package/dist/schema/LiveStoreEvent/client.test.d.ts +2 -0
  97. package/dist/schema/LiveStoreEvent/client.test.d.ts.map +1 -0
  98. package/dist/schema/LiveStoreEvent/client.test.js +111 -0
  99. package/dist/schema/LiveStoreEvent/client.test.js.map +1 -0
  100. package/dist/schema/schema.d.ts.map +1 -1
  101. package/dist/schema/schema.js +7 -4
  102. package/dist/schema/schema.js.map +1 -1
  103. package/dist/schema/state/sqlite/client-document-def.d.ts.map +1 -1
  104. package/dist/schema/state/sqlite/client-document-def.js +18 -6
  105. package/dist/schema/state/sqlite/client-document-def.js.map +1 -1
  106. package/dist/schema/state/sqlite/client-document-def.test.js +1 -1
  107. package/dist/schema/state/sqlite/client-document-def.test.js.map +1 -1
  108. package/dist/schema/state/sqlite/column-annotations.d.ts.map +1 -1
  109. package/dist/schema/state/sqlite/column-annotations.js +1 -1
  110. package/dist/schema/state/sqlite/column-annotations.js.map +1 -1
  111. package/dist/schema/state/sqlite/column-annotations.test.js +1 -1
  112. package/dist/schema/state/sqlite/column-annotations.test.js.map +1 -1
  113. package/dist/schema/state/sqlite/column-def.d.ts.map +1 -1
  114. package/dist/schema/state/sqlite/column-def.js +36 -34
  115. package/dist/schema/state/sqlite/column-def.js.map +1 -1
  116. package/dist/schema/state/sqlite/column-def.test.js +7 -6
  117. package/dist/schema/state/sqlite/column-def.test.js.map +1 -1
  118. package/dist/schema/state/sqlite/column-spec.d.ts.map +1 -1
  119. package/dist/schema/state/sqlite/column-spec.js +8 -8
  120. package/dist/schema/state/sqlite/column-spec.js.map +1 -1
  121. package/dist/schema/state/sqlite/column-spec.test.js +1 -1
  122. package/dist/schema/state/sqlite/column-spec.test.js.map +1 -1
  123. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js +2 -2
  124. package/dist/schema/state/sqlite/db-schema/ast/sqlite.js.map +1 -1
  125. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts +2 -2
  126. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.d.ts.map +1 -1
  127. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js +11 -2
  128. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.js.map +1 -1
  129. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js +1 -1
  130. package/dist/schema/state/sqlite/db-schema/dsl/field-defs.test.js.map +1 -1
  131. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts +1 -1
  132. package/dist/schema/state/sqlite/db-schema/dsl/mod.d.ts.map +1 -1
  133. package/dist/schema/state/sqlite/db-schema/dsl/mod.js +1 -1
  134. package/dist/schema/state/sqlite/db-schema/dsl/mod.js.map +1 -1
  135. package/dist/schema/state/sqlite/mod.d.ts.map +1 -1
  136. package/dist/schema/state/sqlite/mod.js +3 -5
  137. package/dist/schema/state/sqlite/mod.js.map +1 -1
  138. package/dist/schema/state/sqlite/query-builder/api.d.ts +10 -2
  139. package/dist/schema/state/sqlite/query-builder/api.d.ts.map +1 -1
  140. package/dist/schema/state/sqlite/query-builder/astToSql.js +11 -11
  141. package/dist/schema/state/sqlite/query-builder/astToSql.js.map +1 -1
  142. package/dist/schema/state/sqlite/query-builder/impl.d.ts +1 -1
  143. package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
  144. package/dist/schema/state/sqlite/query-builder/impl.js +28 -14
  145. package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
  146. package/dist/schema/state/sqlite/query-builder/impl.test.js +3 -2
  147. package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
  148. package/dist/schema/state/sqlite/schema-helpers.js +2 -2
  149. package/dist/schema/state/sqlite/schema-helpers.js.map +1 -1
  150. package/dist/schema/state/sqlite/table-def.d.ts +5 -3
  151. package/dist/schema/state/sqlite/table-def.d.ts.map +1 -1
  152. package/dist/schema/state/sqlite/table-def.js +1 -1
  153. package/dist/schema/state/sqlite/table-def.js.map +1 -1
  154. package/dist/schema/state/sqlite/table-def.test.js +57 -4
  155. package/dist/schema/state/sqlite/table-def.test.js.map +1 -1
  156. package/dist/schema/unknown-events.d.ts +1 -1
  157. package/dist/schema/unknown-events.d.ts.map +1 -1
  158. package/dist/schema/unknown-events.js +1 -1
  159. package/dist/schema/unknown-events.js.map +1 -1
  160. package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js +1 -1
  161. package/dist/schema-management/__tests__/migrations-autoincrement-quoting.test.js.map +1 -1
  162. package/dist/schema-management/common.js +2 -2
  163. package/dist/schema-management/common.js.map +1 -1
  164. package/dist/schema-management/migrations.js +1 -1
  165. package/dist/schema-management/migrations.js.map +1 -1
  166. package/dist/sql-queries/sql-queries.js +8 -6
  167. package/dist/sql-queries/sql-queries.js.map +1 -1
  168. package/dist/sql-queries/sql-query-builder.d.ts.map +1 -1
  169. package/dist/sql-queries/sql-query-builder.js.map +1 -1
  170. package/dist/sqlite-db-helper.js +3 -3
  171. package/dist/sqlite-db-helper.js.map +1 -1
  172. package/dist/sqlite-types.d.ts +2 -2
  173. package/dist/sqlite-types.d.ts.map +1 -1
  174. package/dist/sqlite-types.js.map +1 -1
  175. package/dist/sync/ClientSessionSyncProcessor.d.ts +8 -9
  176. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  177. package/dist/sync/ClientSessionSyncProcessor.js +95 -113
  178. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  179. package/dist/sync/errors.d.ts +0 -38
  180. package/dist/sync/errors.d.ts.map +1 -1
  181. package/dist/sync/errors.js +3 -20
  182. package/dist/sync/errors.js.map +1 -1
  183. package/dist/sync/mock-sync-backend.d.ts +5 -3
  184. package/dist/sync/mock-sync-backend.d.ts.map +1 -1
  185. package/dist/sync/mock-sync-backend.js +70 -68
  186. package/dist/sync/mock-sync-backend.js.map +1 -1
  187. package/dist/sync/next/compact-events.js +6 -6
  188. package/dist/sync/next/compact-events.js.map +1 -1
  189. package/dist/sync/next/facts.d.ts.map +1 -1
  190. package/dist/sync/next/facts.js +6 -6
  191. package/dist/sync/next/facts.js.map +1 -1
  192. package/dist/sync/next/history-dag-common.d.ts.map +1 -1
  193. package/dist/sync/next/history-dag-common.js +6 -6
  194. package/dist/sync/next/history-dag-common.js.map +1 -1
  195. package/dist/sync/next/history-dag.js +3 -3
  196. package/dist/sync/next/history-dag.js.map +1 -1
  197. package/dist/sync/next/rebase-events.js +1 -1
  198. package/dist/sync/next/rebase-events.js.map +1 -1
  199. package/dist/sync/next/test/compact-events.calculator.test.js +2 -2
  200. package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
  201. package/dist/sync/next/test/compact-events.test.d.ts.map +1 -1
  202. package/dist/sync/next/test/compact-events.test.js +2 -2
  203. package/dist/sync/next/test/compact-events.test.js.map +1 -1
  204. package/dist/sync/next/test/event-fixtures.d.ts.map +1 -1
  205. package/dist/sync/next/test/event-fixtures.js +2 -2
  206. package/dist/sync/next/test/event-fixtures.js.map +1 -1
  207. package/dist/sync/sync-backend-kv.d.ts.map +1 -1
  208. package/dist/sync/sync-backend-kv.js.map +1 -1
  209. package/dist/sync/sync-backend.d.ts +3 -3
  210. package/dist/sync/sync-backend.d.ts.map +1 -1
  211. package/dist/sync/sync-backend.js +1 -1
  212. package/dist/sync/sync-backend.js.map +1 -1
  213. package/dist/sync/sync.d.ts +20 -0
  214. package/dist/sync/sync.d.ts.map +1 -1
  215. package/dist/sync/syncstate.d.ts +4 -17
  216. package/dist/sync/syncstate.d.ts.map +1 -1
  217. package/dist/sync/syncstate.js +51 -74
  218. package/dist/sync/syncstate.js.map +1 -1
  219. package/dist/sync/syncstate.test.js +148 -96
  220. package/dist/sync/syncstate.test.js.map +1 -1
  221. package/dist/sync/transport-chunking.js +3 -3
  222. package/dist/sync/transport-chunking.js.map +1 -1
  223. package/dist/sync/validate-push-payload.d.ts +2 -2
  224. package/dist/sync/validate-push-payload.d.ts.map +1 -1
  225. package/dist/sync/validate-push-payload.js +4 -6
  226. package/dist/sync/validate-push-payload.js.map +1 -1
  227. package/dist/util.js +2 -2
  228. package/dist/util.js.map +1 -1
  229. package/dist/version.d.ts.map +1 -1
  230. package/dist/version.js +2 -5
  231. package/dist/version.js.map +1 -1
  232. package/package.json +66 -12
  233. package/src/ClientSessionLeaderThreadProxy.ts +9 -9
  234. package/src/WorkerTransportError.ts +12 -0
  235. package/src/adapter-types.ts +9 -3
  236. package/src/bounded-collections.ts +6 -5
  237. package/src/debug-info.ts +4 -4
  238. package/src/devtools/devtools-messages-common.ts +1 -1
  239. package/src/devtools/mod.ts +1 -1
  240. package/src/errors.ts +18 -17
  241. package/src/index.ts +2 -0
  242. package/src/leader-thread/LeaderSyncProcessor.ts +421 -392
  243. package/src/leader-thread/RejectedPushError.ts +106 -0
  244. package/src/leader-thread/connection.ts +1 -1
  245. package/src/leader-thread/eventlog.ts +16 -14
  246. package/src/leader-thread/leader-worker-devtools.ts +96 -66
  247. package/src/leader-thread/make-leader-thread-layer.test.ts +1 -1
  248. package/src/leader-thread/make-leader-thread-layer.ts +33 -31
  249. package/src/leader-thread/materialize-event.ts +4 -4
  250. package/src/leader-thread/recreate-db.ts +1 -1
  251. package/src/leader-thread/shutdown-channel.ts +2 -6
  252. package/src/leader-thread/stream-events.ts +10 -5
  253. package/src/leader-thread/types.ts +7 -6
  254. package/src/logging.ts +4 -4
  255. package/src/make-client-session.ts +2 -2
  256. package/src/materializer-helper.ts +9 -9
  257. package/src/otel.ts +3 -2
  258. package/src/rematerialize-from-eventlog.ts +60 -60
  259. package/src/schema/EventDef/define.ts +6 -6
  260. package/src/schema/EventDef/deprecated.test.ts +2 -1
  261. package/src/schema/EventDef/deprecated.ts +3 -3
  262. package/src/schema/EventSequenceNumber/client.ts +11 -11
  263. package/src/schema/EventSequenceNumber.test.ts +2 -1
  264. package/src/schema/LiveStoreEvent/client.test.ts +129 -0
  265. package/src/schema/LiveStoreEvent/client.ts +17 -3
  266. package/src/schema/schema.ts +9 -4
  267. package/src/schema/state/sqlite/client-document-def.test.ts +2 -1
  268. package/src/schema/state/sqlite/client-document-def.ts +20 -6
  269. package/src/schema/state/sqlite/column-annotations.test.ts +2 -1
  270. package/src/schema/state/sqlite/column-annotations.ts +2 -1
  271. package/src/schema/state/sqlite/column-def.test.ts +8 -6
  272. package/src/schema/state/sqlite/column-def.ts +41 -36
  273. package/src/schema/state/sqlite/column-spec.test.ts +3 -1
  274. package/src/schema/state/sqlite/column-spec.ts +9 -8
  275. package/src/schema/state/sqlite/db-schema/ast/sqlite.ts +2 -2
  276. package/src/schema/state/sqlite/db-schema/dsl/field-defs.test.ts +2 -1
  277. package/src/schema/state/sqlite/db-schema/dsl/field-defs.ts +13 -4
  278. package/src/schema/state/sqlite/db-schema/dsl/mod.ts +3 -3
  279. package/src/schema/state/sqlite/mod.ts +4 -5
  280. package/src/schema/state/sqlite/query-builder/api.ts +12 -5
  281. package/src/schema/state/sqlite/query-builder/astToSql.ts +11 -11
  282. package/src/schema/state/sqlite/query-builder/impl.test.ts +4 -2
  283. package/src/schema/state/sqlite/query-builder/impl.ts +26 -12
  284. package/src/schema/state/sqlite/schema-helpers.ts +2 -2
  285. package/src/schema/state/sqlite/table-def.test.ts +67 -4
  286. package/src/schema/state/sqlite/table-def.ts +8 -15
  287. package/src/schema/unknown-events.ts +2 -2
  288. package/src/schema-management/__tests__/migrations-autoincrement-quoting.test.ts +3 -1
  289. package/src/schema-management/common.ts +2 -2
  290. package/src/schema-management/migrations.ts +1 -1
  291. package/src/sql-queries/sql-queries.ts +10 -6
  292. package/src/sql-queries/sql-query-builder.ts +1 -0
  293. package/src/sqlite-db-helper.ts +3 -3
  294. package/src/sqlite-types.ts +3 -2
  295. package/src/sync/ClientSessionSyncProcessor.ts +148 -152
  296. package/src/sync/errors.ts +10 -22
  297. package/src/sync/mock-sync-backend.ts +139 -97
  298. package/src/sync/next/compact-events.ts +5 -5
  299. package/src/sync/next/facts.ts +7 -6
  300. package/src/sync/next/history-dag-common.ts +9 -6
  301. package/src/sync/next/history-dag.ts +3 -3
  302. package/src/sync/next/rebase-events.ts +1 -1
  303. package/src/sync/next/test/compact-events.calculator.test.ts +3 -2
  304. package/src/sync/next/test/compact-events.test.ts +4 -3
  305. package/src/sync/next/test/event-fixtures.ts +2 -2
  306. package/src/sync/sync-backend-kv.ts +1 -0
  307. package/src/sync/sync-backend.ts +5 -4
  308. package/src/sync/sync.ts +21 -0
  309. package/src/sync/syncstate.test.ts +553 -433
  310. package/src/sync/syncstate.ts +80 -86
  311. package/src/sync/transport-chunking.ts +3 -3
  312. package/src/sync/validate-push-payload.ts +4 -6
  313. package/src/util.ts +2 -2
  314. package/src/version.ts +2 -6
@@ -1,4 +1,6 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { Vitest } from '@livestore/utils-dev/node-vitest'
2
+ import { Cause, Effect, Exit, Schema } from '@livestore/utils/effect'
3
+ import { assert, expect } from 'vitest'
2
4
 
3
5
  import * as EventSequenceNumber from '../schema/EventSequenceNumber/mod.ts'
4
6
  import * as LiveStoreEvent from '../schema/LiveStoreEvent/mod.ts'
@@ -48,8 +50,8 @@ const isEqualEvent = LiveStoreEvent.Client.isEqualEncoded
48
50
 
49
51
  const isClientEvent = (event: LiveStoreEvent.Client.EncodedWithMeta) => (event as TestEvent).isClient
50
52
 
51
- describe('syncstate', () => {
52
- describe('merge', () => {
53
+ Vitest.describe('syncstate', () => {
54
+ Vitest.describe('merge', () => {
53
55
  const merge = ({
54
56
  syncState,
55
57
  payload,
@@ -60,446 +62,564 @@ describe('syncstate', () => {
60
62
  ignoreClientEvents?: boolean
61
63
  }) => SyncState.merge({ syncState, payload, isClientEvent, isEqualEvent, ignoreClientEvents })
62
64
 
63
- describe('upstream-rebase', () => {
64
- it('should rollback until start', () => {
65
- const syncState = new SyncState.SyncState({
66
- pending: [e2_0],
67
- upstreamHead: EventSequenceNumber.Client.ROOT,
68
- localHead: e2_0.seqNum,
69
- })
70
- const e1_0_e2_0 = e1_0.rebase_(e2_0.seqNum, 0)
71
- const e1_1_e2_1 = e1_1.rebase_(e1_0_e2_0.seqNum, 0)
72
- const result = merge({
73
- syncState,
74
- payload: SyncState.PayloadUpstreamRebase.make({
75
- rollbackEvents: [e1_0, e1_1],
76
- newEvents: [e1_0_e2_0, e1_1_e2_1],
77
- }),
78
- })
79
- const e2_0_e3_0 = e2_0.rebase_(e1_0_e2_0.seqNum, 1)
80
- expectRebase(result)
81
- expectEventArraysEqual(result.newSyncState.pending, [e2_0_e3_0])
82
- expect(result.newSyncState.upstreamHead).toMatchObject(e1_1_e2_1.seqNum)
83
- expect(result.newSyncState.localHead).toMatchObject(e2_0_e3_0.seqNum)
84
- expectEventArraysEqual(result.newEvents, [e1_0_e2_0, e1_1_e2_1, e2_0_e3_0])
85
- expectEventArraysEqual(result.rollbackEvents, [e1_0, e1_1, e2_0])
86
- })
87
-
88
- it('should rollback only to specified point', () => {
89
- const syncState = new SyncState.SyncState({
90
- pending: [e2_0],
91
- upstreamHead: EventSequenceNumber.Client.ROOT,
92
- localHead: e2_0.seqNum,
93
- })
94
- const e1_1_e2_0 = e1_1.rebase_(e1_0.seqNum, 0)
95
- const result = merge({
96
- syncState,
97
- payload: SyncState.PayloadUpstreamRebase.make({
98
- newEvents: [e1_1_e2_0],
99
- rollbackEvents: [e1_1],
100
- }),
101
- })
102
- const e2_0_e3_0 = e2_0.rebase_(e1_1_e2_0.seqNum, 1)
103
- expectRebase(result)
104
- expectEventArraysEqual(result.newSyncState.pending, [e2_0_e3_0])
105
- expect(result.newSyncState.upstreamHead).toMatchObject(e1_1_e2_0.seqNum)
106
- expect(result.newSyncState.localHead).toMatchObject(e2_0_e3_0.seqNum)
107
- expectEventArraysEqual(result.newEvents, [e1_1_e2_0, e2_0_e3_0])
108
- expectEventArraysEqual(result.rollbackEvents, [e1_1, e2_0])
109
- })
110
-
111
- it('should work for empty pending', () => {
112
- const syncState = new SyncState.SyncState({
113
- pending: [],
114
- upstreamHead: EventSequenceNumber.Client.ROOT,
115
- localHead: e1_0.seqNum,
116
- })
117
- const result = merge({
118
- syncState,
119
- payload: SyncState.PayloadUpstreamRebase.make({ rollbackEvents: [e1_0], newEvents: [e2_0] }),
120
- })
121
- expectRebase(result)
122
- expectEventArraysEqual(result.newSyncState.pending, [])
123
- expect(result.newSyncState.upstreamHead).toMatchObject(e2_0.seqNum)
124
- expect(result.newSyncState.localHead).toMatchObject(e2_0.seqNum)
125
- expect(result.newEvents).toStrictEqual([e2_0])
126
- })
65
+ Vitest.describe('upstream-rebase', () => {
66
+ Vitest.it.effect('should rollback until start', () =>
67
+ Effect.gen(function* () {
68
+ const syncState = new SyncState.SyncState({
69
+ pending: [e2_0],
70
+ upstreamHead: EventSequenceNumber.Client.ROOT,
71
+ localHead: e2_0.seqNum,
72
+ })
73
+ const e1_0_e2_0 = e1_0.rebase_(e2_0.seqNum, 0)
74
+ const e1_1_e2_1 = e1_1.rebase_(e1_0_e2_0.seqNum, 0)
75
+ const result = yield* merge({
76
+ syncState,
77
+ payload: SyncState.PayloadUpstreamRebase.make({
78
+ rollbackEvents: [e1_0, e1_1],
79
+ newEvents: [e1_0_e2_0, e1_1_e2_1],
80
+ }),
81
+ })
82
+ const e2_0_e3_0 = e2_0.rebase_(e1_0_e2_0.seqNum, 1)
83
+ expectRebase(result)
84
+ expectEventArraysEqual(result.newSyncState.pending, [e2_0_e3_0])
85
+ expect(result.newSyncState.upstreamHead).toMatchObject(e1_1_e2_1.seqNum)
86
+ expect(result.newSyncState.localHead).toMatchObject(e2_0_e3_0.seqNum)
87
+ expectEventArraysEqual(result.newEvents, [e1_0_e2_0, e1_1_e2_1, e2_0_e3_0])
88
+ expectEventArraysEqual(result.rollbackEvents, [e1_0, e1_1, e2_0])
89
+ }),
90
+ )
91
+
92
+ Vitest.it.effect('should rollback only to specified point', () =>
93
+ Effect.gen(function* () {
94
+ const syncState = new SyncState.SyncState({
95
+ pending: [e2_0],
96
+ upstreamHead: EventSequenceNumber.Client.ROOT,
97
+ localHead: e2_0.seqNum,
98
+ })
99
+ const e1_1_e2_0 = e1_1.rebase_(e1_0.seqNum, 0)
100
+ const result = yield* merge({
101
+ syncState,
102
+ payload: SyncState.PayloadUpstreamRebase.make({
103
+ newEvents: [e1_1_e2_0],
104
+ rollbackEvents: [e1_1],
105
+ }),
106
+ })
107
+ const e2_0_e3_0 = e2_0.rebase_(e1_1_e2_0.seqNum, 1)
108
+ expectRebase(result)
109
+ expectEventArraysEqual(result.newSyncState.pending, [e2_0_e3_0])
110
+ expect(result.newSyncState.upstreamHead).toMatchObject(e1_1_e2_0.seqNum)
111
+ expect(result.newSyncState.localHead).toMatchObject(e2_0_e3_0.seqNum)
112
+ expectEventArraysEqual(result.newEvents, [e1_1_e2_0, e2_0_e3_0])
113
+ expectEventArraysEqual(result.rollbackEvents, [e1_1, e2_0])
114
+ }),
115
+ )
116
+
117
+ Vitest.it.effect('should work for empty pending', () =>
118
+ Effect.gen(function* () {
119
+ const syncState = new SyncState.SyncState({
120
+ pending: [],
121
+ upstreamHead: EventSequenceNumber.Client.ROOT,
122
+ localHead: e1_0.seqNum,
123
+ })
124
+ const result = yield* merge({
125
+ syncState,
126
+ payload: SyncState.PayloadUpstreamRebase.make({ rollbackEvents: [e1_0], newEvents: [e2_0] }),
127
+ })
128
+ expectRebase(result)
129
+ expectEventArraysEqual(result.newSyncState.pending, [])
130
+ expect(result.newSyncState.upstreamHead).toMatchObject(e2_0.seqNum)
131
+ expect(result.newSyncState.localHead).toMatchObject(e2_0.seqNum)
132
+ expect(result.newEvents).toStrictEqual([e2_0])
133
+ }),
134
+ )
127
135
  })
128
136
 
129
- describe('upstream-advance: advance', () => {
130
- it('should throw error if newEvents are not sorted in ascending order by event number (client)', () => {
131
- const syncState = new SyncState.SyncState({
132
- pending: [e1_0],
133
- upstreamHead: EventSequenceNumber.Client.ROOT,
134
- localHead: e1_0.seqNum,
135
- })
136
- const result = merge({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e1_1, e1_0] } })
137
- expect(result).toMatchObject({ _tag: 'unknown-error' })
138
- })
139
-
140
- it('should throw error if newEvents are not sorted in ascending order by event number (global)', () => {
141
- const syncState = new SyncState.SyncState({
142
- pending: [e1_0],
143
- upstreamHead: EventSequenceNumber.Client.ROOT,
144
- localHead: e1_0.seqNum,
145
- })
146
- const result = merge({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e2_0, e1_0] } })
147
- expect(result).toMatchObject({ _tag: 'unknown-error' })
148
- })
149
-
150
- it('should throw error if incoming event is < expected upstream head', () => {
151
- const syncState = new SyncState.SyncState({
152
- pending: [],
153
- upstreamHead: e2_0.seqNum,
154
- localHead: e2_0.seqNum,
155
- })
156
- const result = merge({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e1_0] } })
157
- expect(result).toMatchObject({ _tag: 'unknown-error' })
158
- })
159
-
160
- it('should throw error if incoming event is = expected upstream head', () => {
161
- const syncState = new SyncState.SyncState({
162
- pending: [],
163
- upstreamHead: e2_0.seqNum,
164
- localHead: e2_0.seqNum,
165
- })
166
- const result = merge({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e2_0] } })
167
- expect(result).toMatchObject({ _tag: 'unknown-error' })
168
- })
169
-
170
- it('should confirm pending event when receiving matching event', () => {
171
- const syncState = new SyncState.SyncState({
172
- pending: [e1_0],
173
- upstreamHead: EventSequenceNumber.Client.ROOT,
174
- localHead: e1_0.seqNum,
175
- })
176
- const result = merge({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e1_0] } })
177
-
178
- expectAdvance(result)
179
- expectEventArraysEqual(result.newSyncState.pending, [])
180
- expect(result.newSyncState.upstreamHead).toMatchObject(e1_0.seqNum)
181
- expect(result.newSyncState.localHead).toMatchObject(e1_0.seqNum)
182
- expectEventArraysEqual(result.newEvents, [])
183
- expectEventArraysEqual(result.confirmedEvents, [e1_0])
184
- })
185
-
186
- it('should confirm partial pending event when receiving matching event', () => {
187
- const syncState = new SyncState.SyncState({
188
- pending: [e1_0, e2_0],
189
- upstreamHead: EventSequenceNumber.Client.ROOT,
190
- localHead: e2_0.seqNum,
191
- })
192
- const result = merge({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e1_0] } })
193
-
194
- expectAdvance(result)
195
- expectEventArraysEqual(result.newSyncState.pending, [e2_0])
196
- expect(result.newSyncState.upstreamHead).toMatchObject(e1_0.seqNum)
197
- expect(result.newSyncState.localHead).toMatchObject(e2_0.seqNum)
198
- expectEventArraysEqual(result.newEvents, [])
199
- expectEventArraysEqual(result.confirmedEvents, [e1_0])
200
- })
201
-
202
- it('should confirm pending event and add new event', () => {
203
- const syncState = new SyncState.SyncState({
204
- pending: [e1_0],
205
- upstreamHead: EventSequenceNumber.Client.ROOT,
206
- localHead: e1_0.seqNum,
207
- })
208
- const result = merge({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e1_0, e1_1] } })
209
-
210
- expectAdvance(result)
211
- expectEventArraysEqual(result.newSyncState.pending, [])
212
- expect(result.newSyncState.upstreamHead).toMatchObject(e1_1.seqNum)
213
- expect(result.newSyncState.localHead).toMatchObject(e1_1.seqNum)
214
- expect(result.newEvents).toStrictEqual([e1_1])
215
- expectEventArraysEqual(result.confirmedEvents, [e1_0])
216
- })
217
-
218
- it('should confirm pending event and add multiple new events', () => {
219
- const syncState = new SyncState.SyncState({
220
- pending: [e1_1],
221
- upstreamHead: e1_0.seqNum,
222
- localHead: e1_1.seqNum,
223
- })
224
- const result = merge({
225
- syncState,
226
- payload: { _tag: 'upstream-advance', newEvents: [e1_1, e1_2, e1_3, e2_0, e2_1] },
227
- })
228
-
229
- expectAdvance(result)
230
- expectEventArraysEqual(result.newSyncState.pending, [])
231
- expect(result.newSyncState.upstreamHead).toMatchObject(e2_1.seqNum)
232
- expect(result.newSyncState.localHead).toMatchObject(e2_1.seqNum)
233
- expect(result.newEvents).toStrictEqual([e1_2, e1_3, e2_0, e2_1])
234
- expectEventArraysEqual(result.confirmedEvents, [e1_1])
235
- })
236
-
237
- it('should confirm pending global event while keep pending client events', () => {
238
- const syncState = new SyncState.SyncState({
239
- pending: [e1_0, e1_1],
240
- upstreamHead: EventSequenceNumber.Client.ROOT,
241
- localHead: e1_1.seqNum,
242
- })
243
- const result = merge({
244
- syncState,
245
- payload: { _tag: 'upstream-advance', newEvents: [e1_0] },
246
- })
137
+ Vitest.describe('upstream-advance: advance', () => {
138
+ Vitest.it.effect('should die if newEvents are not sorted in ascending order by event number (client)', () =>
139
+ Effect.gen(function* () {
140
+ const syncState = new SyncState.SyncState({
141
+ pending: [e1_0],
142
+ upstreamHead: EventSequenceNumber.Client.ROOT,
143
+ localHead: e1_0.seqNum,
144
+ })
145
+ const exit = yield* merge({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e1_1, e1_0] } }).pipe(
146
+ Effect.exit,
147
+ )
148
+ assert(Exit.isFailure(exit))
149
+ expect(Cause.isDie(exit.cause)).toBe(true)
150
+ }),
151
+ )
152
+
153
+ Vitest.it.effect('should die if newEvents are not sorted in ascending order by event number (global)', () =>
154
+ Effect.gen(function* () {
155
+ const syncState = new SyncState.SyncState({
156
+ pending: [e1_0],
157
+ upstreamHead: EventSequenceNumber.Client.ROOT,
158
+ localHead: e1_0.seqNum,
159
+ })
160
+ const exit = yield* merge({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e2_0, e1_0] } }).pipe(
161
+ Effect.exit,
162
+ )
163
+ assert(Exit.isFailure(exit))
164
+ expect(Cause.isDie(exit.cause)).toBe(true)
165
+ }),
166
+ )
167
+
168
+ Vitest.it.effect('should die if incoming event is < expected upstream head', () =>
169
+ Effect.gen(function* () {
170
+ const syncState = new SyncState.SyncState({
171
+ pending: [],
172
+ upstreamHead: e2_0.seqNum,
173
+ localHead: e2_0.seqNum,
174
+ })
175
+ const exit = yield* merge({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e1_0] } }).pipe(
176
+ Effect.exit,
177
+ )
178
+ assert(Exit.isFailure(exit))
179
+ expect(Cause.isDie(exit.cause)).toBe(true)
180
+ }),
181
+ )
182
+
183
+ Vitest.it.effect('should die if incoming event is = expected upstream head', () =>
184
+ Effect.gen(function* () {
185
+ const syncState = new SyncState.SyncState({
186
+ pending: [],
187
+ upstreamHead: e2_0.seqNum,
188
+ localHead: e2_0.seqNum,
189
+ })
190
+ const exit = yield* merge({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e2_0] } }).pipe(
191
+ Effect.exit,
192
+ )
193
+ assert(Exit.isFailure(exit))
194
+ expect(Cause.isDie(exit.cause)).toBe(true)
195
+ }),
196
+ )
197
+
198
+ Vitest.it.effect('should confirm pending event when receiving matching event', () =>
199
+ Effect.gen(function* () {
200
+ const syncState = new SyncState.SyncState({
201
+ pending: [e1_0],
202
+ upstreamHead: EventSequenceNumber.Client.ROOT,
203
+ localHead: e1_0.seqNum,
204
+ })
205
+ const result = yield* merge({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e1_0] } })
206
+
207
+ expectAdvance(result)
208
+ expectEventArraysEqual(result.newSyncState.pending, [])
209
+ expect(result.newSyncState.upstreamHead).toMatchObject(e1_0.seqNum)
210
+ expect(result.newSyncState.localHead).toMatchObject(e1_0.seqNum)
211
+ expectEventArraysEqual(result.newEvents, [])
212
+ expectEventArraysEqual(result.confirmedEvents, [e1_0])
213
+ }),
214
+ )
215
+
216
+ Vitest.it.effect('should confirm partial pending event when receiving matching event', () =>
217
+ Effect.gen(function* () {
218
+ const syncState = new SyncState.SyncState({
219
+ pending: [e1_0, e2_0],
220
+ upstreamHead: EventSequenceNumber.Client.ROOT,
221
+ localHead: e2_0.seqNum,
222
+ })
223
+ const result = yield* merge({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e1_0] } })
224
+
225
+ expectAdvance(result)
226
+ expectEventArraysEqual(result.newSyncState.pending, [e2_0])
227
+ expect(result.newSyncState.upstreamHead).toMatchObject(e1_0.seqNum)
228
+ expect(result.newSyncState.localHead).toMatchObject(e2_0.seqNum)
229
+ expectEventArraysEqual(result.newEvents, [])
230
+ expectEventArraysEqual(result.confirmedEvents, [e1_0])
231
+ }),
232
+ )
233
+
234
+ Vitest.it.effect('should confirm pending event and add new event', () =>
235
+ Effect.gen(function* () {
236
+ const syncState = new SyncState.SyncState({
237
+ pending: [e1_0],
238
+ upstreamHead: EventSequenceNumber.Client.ROOT,
239
+ localHead: e1_0.seqNum,
240
+ })
241
+ const result = yield* merge({
242
+ syncState,
243
+ payload: { _tag: 'upstream-advance', newEvents: [e1_0, e1_1] },
244
+ })
247
245
 
248
- expectAdvance(result)
249
- expectEventArraysEqual(result.newSyncState.pending, [e1_1])
250
- expect(result.newSyncState.upstreamHead).toMatchObject(e1_0.seqNum)
251
- expect(result.newSyncState.localHead).toMatchObject(e1_1.seqNum)
252
- expectEventArraysEqual(result.newEvents, [])
253
- expectEventArraysEqual(result.confirmedEvents, [e1_0])
254
- })
246
+ expectAdvance(result)
247
+ expectEventArraysEqual(result.newSyncState.pending, [])
248
+ expect(result.newSyncState.upstreamHead).toMatchObject(e1_1.seqNum)
249
+ expect(result.newSyncState.localHead).toMatchObject(e1_1.seqNum)
250
+ expect(result.newEvents).toStrictEqual([e1_1])
251
+ expectEventArraysEqual(result.confirmedEvents, [e1_0])
252
+ }),
253
+ )
254
+
255
+ Vitest.it.effect('should confirm pending event and add multiple new events', () =>
256
+ Effect.gen(function* () {
257
+ const syncState = new SyncState.SyncState({
258
+ pending: [e1_1],
259
+ upstreamHead: e1_0.seqNum,
260
+ localHead: e1_1.seqNum,
261
+ })
262
+ const result = yield* merge({
263
+ syncState,
264
+ payload: { _tag: 'upstream-advance', newEvents: [e1_1, e1_2, e1_3, e2_0, e2_1] },
265
+ })
255
266
 
256
- it('should ignore client events (incoming is subset of pending)', () => {
257
- const syncState = new SyncState.SyncState({
258
- pending: [e0_1, e1_0],
259
- upstreamHead: EventSequenceNumber.Client.ROOT,
260
- localHead: e1_0.seqNum,
261
- })
262
- const result = merge({
263
- syncState,
264
- payload: { _tag: 'upstream-advance', newEvents: [e1_0] },
265
- ignoreClientEvents: true,
266
- })
267
- expectAdvance(result)
268
- expectEventArraysEqual(result.newSyncState.pending, [])
269
- expect(result.newSyncState.upstreamHead).toMatchObject(e1_0.seqNum)
270
- expect(result.newSyncState.localHead).toMatchObject(e1_0.seqNum)
271
- expectEventArraysEqual(result.newEvents, [])
272
- expectEventArraysEqual(result.confirmedEvents, [e0_1, e1_0])
273
- })
267
+ expectAdvance(result)
268
+ expectEventArraysEqual(result.newSyncState.pending, [])
269
+ expect(result.newSyncState.upstreamHead).toMatchObject(e2_1.seqNum)
270
+ expect(result.newSyncState.localHead).toMatchObject(e2_1.seqNum)
271
+ expect(result.newEvents).toStrictEqual([e1_2, e1_3, e2_0, e2_1])
272
+ expectEventArraysEqual(result.confirmedEvents, [e1_1])
273
+ }),
274
+ )
275
+
276
+ Vitest.it.effect('should confirm pending global event while keep pending client events', () =>
277
+ Effect.gen(function* () {
278
+ const syncState = new SyncState.SyncState({
279
+ pending: [e1_0, e1_1],
280
+ upstreamHead: EventSequenceNumber.Client.ROOT,
281
+ localHead: e1_1.seqNum,
282
+ })
283
+ const result = yield* merge({
284
+ syncState,
285
+ payload: { _tag: 'upstream-advance', newEvents: [e1_0] },
286
+ })
274
287
 
275
- it('should ignore client events (incoming is subset of pending case 2)', () => {
276
- const syncState = new SyncState.SyncState({
277
- pending: [e0_1, e1_0, e2_0],
278
- upstreamHead: EventSequenceNumber.Client.ROOT,
279
- localHead: e1_0.seqNum,
280
- })
281
- const result = merge({
282
- syncState,
283
- payload: { _tag: 'upstream-advance', newEvents: [e1_0] },
284
- ignoreClientEvents: true,
285
- })
286
- expectAdvance(result)
287
- expectEventArraysEqual(result.newSyncState.pending, [e2_0])
288
- expect(result.newSyncState.upstreamHead).toMatchObject(e1_0.seqNum)
289
- expect(result.newSyncState.localHead).toMatchObject(e2_0.seqNum)
290
- expectEventArraysEqual(result.newEvents, [])
291
- expectEventArraysEqual(result.confirmedEvents, [e0_1, e1_0])
292
- })
288
+ expectAdvance(result)
289
+ expectEventArraysEqual(result.newSyncState.pending, [e1_1])
290
+ expect(result.newSyncState.upstreamHead).toMatchObject(e1_0.seqNum)
291
+ expect(result.newSyncState.localHead).toMatchObject(e1_1.seqNum)
292
+ expectEventArraysEqual(result.newEvents, [])
293
+ expectEventArraysEqual(result.confirmedEvents, [e1_0])
294
+ }),
295
+ )
296
+
297
+ Vitest.it.effect('should ignore client events (incoming is subset of pending)', () =>
298
+ Effect.gen(function* () {
299
+ const syncState = new SyncState.SyncState({
300
+ pending: [e0_1, e1_0],
301
+ upstreamHead: EventSequenceNumber.Client.ROOT,
302
+ localHead: e1_0.seqNum,
303
+ })
304
+ const result = yield* merge({
305
+ syncState,
306
+ payload: { _tag: 'upstream-advance', newEvents: [e1_0] },
307
+ ignoreClientEvents: true,
308
+ })
309
+ expectAdvance(result)
310
+ expectEventArraysEqual(result.newSyncState.pending, [])
311
+ expect(result.newSyncState.upstreamHead).toMatchObject(e1_0.seqNum)
312
+ expect(result.newSyncState.localHead).toMatchObject(e1_0.seqNum)
313
+ expectEventArraysEqual(result.newEvents, [])
314
+ expectEventArraysEqual(result.confirmedEvents, [e0_1, e1_0])
315
+ }),
316
+ )
317
+
318
+ Vitest.it.effect('should ignore client events (incoming is subset of pending case 2)', () =>
319
+ Effect.gen(function* () {
320
+ const syncState = new SyncState.SyncState({
321
+ pending: [e0_1, e1_0, e2_0],
322
+ upstreamHead: EventSequenceNumber.Client.ROOT,
323
+ localHead: e1_0.seqNum,
324
+ })
325
+ const result = yield* merge({
326
+ syncState,
327
+ payload: { _tag: 'upstream-advance', newEvents: [e1_0] },
328
+ ignoreClientEvents: true,
329
+ })
330
+ expectAdvance(result)
331
+ expectEventArraysEqual(result.newSyncState.pending, [e2_0])
332
+ expect(result.newSyncState.upstreamHead).toMatchObject(e1_0.seqNum)
333
+ expect(result.newSyncState.localHead).toMatchObject(e2_0.seqNum)
334
+ expectEventArraysEqual(result.newEvents, [])
335
+ expectEventArraysEqual(result.confirmedEvents, [e0_1, e1_0])
336
+ }),
337
+ )
338
+
339
+ Vitest.it.effect('should ignore client events (incoming goes beyond pending)', () =>
340
+ Effect.gen(function* () {
341
+ const syncState = new SyncState.SyncState({
342
+ pending: [e0_1, e1_0, e1_1],
343
+ upstreamHead: EventSequenceNumber.Client.ROOT,
344
+ localHead: e1_1.seqNum,
345
+ })
346
+ const result = yield* merge({
347
+ syncState,
348
+ payload: { _tag: 'upstream-advance', newEvents: [e1_0, e2_0] },
349
+ ignoreClientEvents: true,
350
+ })
293
351
 
294
- it('should ignore client events (incoming goes beyond pending)', () => {
295
- const syncState = new SyncState.SyncState({
296
- pending: [e0_1, e1_0, e1_1],
297
- upstreamHead: EventSequenceNumber.Client.ROOT,
298
- localHead: e1_1.seqNum,
299
- })
300
- const result = merge({
301
- syncState,
302
- payload: { _tag: 'upstream-advance', newEvents: [e1_0, e2_0] },
303
- ignoreClientEvents: true,
304
- })
352
+ expectAdvance(result)
353
+ expectEventArraysEqual(result.newSyncState.pending, [])
354
+ expect(result.newSyncState.upstreamHead).toMatchObject(e2_0.seqNum)
355
+ expect(result.newSyncState.localHead).toMatchObject(e2_0.seqNum)
356
+ expect(result.newEvents).toStrictEqual([e2_0])
357
+ expectEventArraysEqual(result.confirmedEvents, [e0_1, e1_0, e1_1])
358
+ }),
359
+ )
360
+
361
+ Vitest.it.effect('should die if incoming event is ≤ local head', () =>
362
+ Effect.gen(function* () {
363
+ const syncState = new SyncState.SyncState({
364
+ pending: [],
365
+ upstreamHead: e2_0.seqNum,
366
+ localHead: e2_0.seqNum,
367
+ })
368
+ const exit = yield* merge({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e1_0] } }).pipe(
369
+ Effect.exit,
370
+ )
371
+ assert(Exit.isFailure(exit))
372
+ expect(Cause.isDie(exit.cause)).toBe(true)
373
+ }),
374
+ )
375
+
376
+ Vitest.it.effect('should advance (not rebase) when pending event has undefined-valued key dropped by JSON wire round-trip', () =>
377
+ Effect.gen(function* () {
378
+ const argsSchema = Schema.Struct({
379
+ id: Schema.String,
380
+ flag: Schema.UndefinedOr(Schema.Boolean),
381
+ })
382
+ const localArgs = Schema.encodeUnknownSync(argsSchema)({ id: 'abc' } as any)
383
+ const wireArgs = JSON.parse(JSON.stringify(localArgs))
384
+
385
+ const localPending = new TestEvent({
386
+ seqNum: e1_0.seqNum,
387
+ parentSeqNum: e1_0.parentSeqNum,
388
+ name: e1_0.name,
389
+ args: localArgs,
390
+ clientId: e1_0.clientId,
391
+ sessionId: e1_0.sessionId,
392
+ })
393
+ const fromUpstream = new TestEvent({
394
+ seqNum: e1_0.seqNum,
395
+ parentSeqNum: e1_0.parentSeqNum,
396
+ name: e1_0.name,
397
+ args: wireArgs,
398
+ clientId: e1_0.clientId,
399
+ sessionId: e1_0.sessionId,
400
+ })
305
401
 
306
- expectAdvance(result)
307
- expectEventArraysEqual(result.newSyncState.pending, [])
308
- expect(result.newSyncState.upstreamHead).toMatchObject(e2_0.seqNum)
309
- expect(result.newSyncState.localHead).toMatchObject(e2_0.seqNum)
310
- expect(result.newEvents).toStrictEqual([e2_0])
311
- expectEventArraysEqual(result.confirmedEvents, [e0_1, e1_0, e1_1])
312
- })
402
+ const syncState = new SyncState.SyncState({
403
+ pending: [localPending],
404
+ upstreamHead: EventSequenceNumber.Client.ROOT,
405
+ localHead: localPending.seqNum,
406
+ })
407
+ const result = yield* merge({
408
+ syncState,
409
+ payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: [fromUpstream] }),
410
+ })
313
411
 
314
- it('should fail if incoming event is ≤ local head', () => {
315
- const syncState = new SyncState.SyncState({
316
- pending: [],
317
- upstreamHead: e2_0.seqNum,
318
- localHead: e2_0.seqNum,
319
- })
320
- const result = merge({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e1_0] } })
321
- expect(result).toMatchObject({ _tag: 'unknown-error' })
322
- })
412
+ expectAdvance(result)
413
+ expect(result.confirmedEvents).toHaveLength(1)
414
+ expect(result.newSyncState.pending).toHaveLength(0)
415
+ }),
416
+ )
323
417
  })
324
418
 
325
- describe('upstream-advance: rebase', () => {
326
- it('should rebase single client event to end', () => {
327
- const syncState = new SyncState.SyncState({
328
- pending: [e1_0],
329
- upstreamHead: EventSequenceNumber.Client.ROOT,
330
- localHead: e1_0.seqNum,
331
- })
332
- const result = merge({ syncState, payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: [e1_1] }) })
333
-
334
- const e1_0_e1_2 = e1_0.rebase_(e1_1.seqNum, 1)
335
-
336
- expectRebase(result)
337
- expectEventArraysEqual(result.newSyncState.pending, [e1_0_e1_2])
338
- expect(result.newSyncState.upstreamHead).toMatchObject(e1_1.seqNum)
339
- expect(result.newSyncState.localHead).toMatchObject(e1_0_e1_2.seqNum)
340
- expectEventArraysEqual(result.rollbackEvents, [e1_0])
341
- expectEventArraysEqual(result.newEvents, [e1_1, e1_0_e1_2])
342
- })
343
-
344
- it('should rebase different event with same id', () => {
345
- const e2_0_b = TestEvent.new({ global: 1, client: 0 }, e1_0.seqNum, '1_0_b', false)
346
- const syncState = new SyncState.SyncState({
347
- pending: [e2_0_b],
348
- upstreamHead: EventSequenceNumber.Client.ROOT,
349
- localHead: e2_0_b.seqNum,
350
- })
351
- const result = merge({ syncState, payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: [e2_0] }) })
352
- const e2_0_e3_0 = e2_0_b.rebase_(e2_0.seqNum, 1)
353
-
354
- expectRebase(result)
355
- expectEventArraysEqual(result.newSyncState.pending, [e2_0_e3_0])
356
- expectEventArraysEqual(result.newEvents, [e2_0, e2_0_e3_0])
357
- expectEventArraysEqual(result.rollbackEvents, [e2_0_b])
358
- expect(result.newSyncState.upstreamHead).toMatchObject(e2_0.seqNum)
359
- expect(result.newSyncState.localHead).toMatchObject(e2_0_e3_0.seqNum)
360
- })
361
-
362
- it('should rebase single client event to end (more incoming events)', () => {
363
- const syncState = new SyncState.SyncState({
364
- pending: [e1_0],
365
- upstreamHead: EventSequenceNumber.Client.ROOT,
366
- localHead: e1_0.seqNum,
367
- })
368
- const result = merge({
369
- syncState,
370
- payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: [e1_1, e1_2, e1_3, e2_0] }),
371
- })
372
-
373
- const e1_0_e3_0 = e1_0.rebase_(e2_0.seqNum, 1)
374
-
375
- expectRebase(result)
376
- expectEventArraysEqual(result.newSyncState.pending, [e1_0_e3_0])
377
- expect(result.newSyncState.upstreamHead).toMatchObject(e2_0.seqNum)
378
- expect(result.newSyncState.localHead).toMatchObject(e1_0_e3_0.seqNum)
379
- })
380
-
381
- it('should only rebase divergent events when first event matches', () => {
382
- const syncState = new SyncState.SyncState({
383
- pending: [e1_0, e1_1],
384
- upstreamHead: EventSequenceNumber.Client.ROOT,
385
- localHead: e1_0.seqNum,
386
- })
387
- const result = merge({
388
- syncState,
389
- payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: [e1_0, e1_2, e1_3, e2_0] }),
390
- })
391
-
392
- const e1_1_e2_1 = e1_1.rebase_(e2_0.seqNum, 1)
393
-
394
- expectRebase(result)
395
- expectEventArraysEqual(result.newSyncState.pending, [e1_1_e2_1])
396
- expectEventArraysEqual(result.rollbackEvents, [e1_1])
397
- expectEventArraysEqual(result.newEvents, [e1_2, e1_3, e2_0, e1_1_e2_1])
398
- expect(result.newSyncState.upstreamHead).toMatchObject(e2_0.seqNum)
399
- expect(result.newSyncState.localHead).toMatchObject(e1_1_e2_1.seqNum)
400
- })
401
-
402
- it('should rebase all client events when incoming chain starts differently', () => {
403
- const syncState = new SyncState.SyncState({
404
- pending: [e1_0, e1_1],
405
- upstreamHead: EventSequenceNumber.Client.ROOT,
406
- localHead: e1_1.seqNum,
407
- })
408
- const result = merge({
409
- syncState,
410
- payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: [e1_1, e1_2, e1_3, e2_0] }),
411
- })
419
+ Vitest.describe('upstream-advance: rebase', () => {
420
+ Vitest.it.effect('should rebase single client event to end', () =>
421
+ Effect.gen(function* () {
422
+ const syncState = new SyncState.SyncState({
423
+ pending: [e1_0],
424
+ upstreamHead: EventSequenceNumber.Client.ROOT,
425
+ localHead: e1_0.seqNum,
426
+ })
427
+ const result = yield* merge({
428
+ syncState,
429
+ payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: [e1_1] }),
430
+ })
412
431
 
413
- const e1_0_e2_1 = e1_0.rebase_(e2_0.seqNum, 1)
414
- const e1_1_e2_2 = e1_1.rebase_(e1_0_e2_1.seqNum, 1)
432
+ const e1_0_e1_2 = e1_0.rebase_(e1_1.seqNum, 1)
433
+
434
+ expectRebase(result)
435
+ expectEventArraysEqual(result.newSyncState.pending, [e1_0_e1_2])
436
+ expect(result.newSyncState.upstreamHead).toMatchObject(e1_1.seqNum)
437
+ expect(result.newSyncState.localHead).toMatchObject(e1_0_e1_2.seqNum)
438
+ expectEventArraysEqual(result.rollbackEvents, [e1_0])
439
+ expectEventArraysEqual(result.newEvents, [e1_1, e1_0_e1_2])
440
+ }),
441
+ )
442
+
443
+ Vitest.it.effect('should rebase different event with same id', () =>
444
+ Effect.gen(function* () {
445
+ const e2_0_b = TestEvent.new({ global: 1, client: 0 }, e1_0.seqNum, '1_0_b', false)
446
+ const syncState = new SyncState.SyncState({
447
+ pending: [e2_0_b],
448
+ upstreamHead: EventSequenceNumber.Client.ROOT,
449
+ localHead: e2_0_b.seqNum,
450
+ })
451
+ const result = yield* merge({
452
+ syncState,
453
+ payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: [e2_0] }),
454
+ })
455
+ const e2_0_e3_0 = e2_0_b.rebase_(e2_0.seqNum, 1)
456
+
457
+ expectRebase(result)
458
+ expectEventArraysEqual(result.newSyncState.pending, [e2_0_e3_0])
459
+ expectEventArraysEqual(result.newEvents, [e2_0, e2_0_e3_0])
460
+ expectEventArraysEqual(result.rollbackEvents, [e2_0_b])
461
+ expect(result.newSyncState.upstreamHead).toMatchObject(e2_0.seqNum)
462
+ expect(result.newSyncState.localHead).toMatchObject(e2_0_e3_0.seqNum)
463
+ }),
464
+ )
465
+
466
+ Vitest.it.effect('should rebase single client event to end (more incoming events)', () =>
467
+ Effect.gen(function* () {
468
+ const syncState = new SyncState.SyncState({
469
+ pending: [e1_0],
470
+ upstreamHead: EventSequenceNumber.Client.ROOT,
471
+ localHead: e1_0.seqNum,
472
+ })
473
+ const result = yield* merge({
474
+ syncState,
475
+ payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: [e1_1, e1_2, e1_3, e2_0] }),
476
+ })
415
477
 
416
- expectRebase(result)
417
- expectEventArraysEqual(result.newSyncState.pending, [e1_0_e2_1, e1_1_e2_2])
418
- expectEventArraysEqual(result.newEvents, [e1_1, e1_2, e1_3, e2_0, e1_0_e2_1, e1_1_e2_2])
419
- expectEventArraysEqual(result.rollbackEvents, [e1_0, e1_1])
420
- expect(result.newSyncState.upstreamHead).toMatchObject(e2_0.seqNum)
421
- expect(result.newSyncState.localHead).toMatchObject(e1_1_e2_2.seqNum)
422
- })
478
+ const e1_0_e3_0 = e1_0.rebase_(e2_0.seqNum, 1)
479
+
480
+ expectRebase(result)
481
+ expectEventArraysEqual(result.newSyncState.pending, [e1_0_e3_0])
482
+ expect(result.newSyncState.upstreamHead).toMatchObject(e2_0.seqNum)
483
+ expect(result.newSyncState.localHead).toMatchObject(e1_0_e3_0.seqNum)
484
+ }),
485
+ )
486
+
487
+ Vitest.it.effect('should only rebase divergent events when first event matches', () =>
488
+ Effect.gen(function* () {
489
+ const syncState = new SyncState.SyncState({
490
+ pending: [e1_0, e1_1],
491
+ upstreamHead: EventSequenceNumber.Client.ROOT,
492
+ localHead: e1_0.seqNum,
493
+ })
494
+ const result = yield* merge({
495
+ syncState,
496
+ payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: [e1_0, e1_2, e1_3, e2_0] }),
497
+ })
423
498
 
424
- describe('local-push', () => {
425
- describe('advance', () => {
426
- it('should advance with new events', () => {
427
- const syncState = new SyncState.SyncState({
428
- pending: [e1_0],
429
- upstreamHead: EventSequenceNumber.Client.ROOT,
430
- localHead: e1_0.seqNum,
431
- })
432
- const result = merge({
433
- syncState,
434
- payload: SyncState.PayloadLocalPush.make({ newEvents: [e1_1, e1_2, e1_3] }),
435
- })
436
-
437
- expectAdvance(result)
438
- expectEventArraysEqual(result.newSyncState.pending, [e1_0, e1_1, e1_2, e1_3])
439
- expect(result.newSyncState.upstreamHead).toMatchObject(EventSequenceNumber.Client.ROOT)
440
- expect(result.newSyncState.localHead).toMatchObject(e1_3.seqNum)
441
- expectEventArraysEqual(result.newEvents, [e1_1, e1_2, e1_3])
442
- expectEventArraysEqual(result.confirmedEvents, [])
499
+ const e1_1_e2_1 = e1_1.rebase_(e2_0.seqNum, 1)
500
+
501
+ expectRebase(result)
502
+ expectEventArraysEqual(result.newSyncState.pending, [e1_1_e2_1])
503
+ expectEventArraysEqual(result.rollbackEvents, [e1_1])
504
+ expectEventArraysEqual(result.newEvents, [e1_2, e1_3, e2_0, e1_1_e2_1])
505
+ expect(result.newSyncState.upstreamHead).toMatchObject(e2_0.seqNum)
506
+ expect(result.newSyncState.localHead).toMatchObject(e1_1_e2_1.seqNum)
507
+ }),
508
+ )
509
+
510
+ Vitest.it.effect('should rebase all client events when incoming chain starts differently', () =>
511
+ Effect.gen(function* () {
512
+ const syncState = new SyncState.SyncState({
513
+ pending: [e1_0, e1_1],
514
+ upstreamHead: EventSequenceNumber.Client.ROOT,
515
+ localHead: e1_1.seqNum,
516
+ })
517
+ const result = yield* merge({
518
+ syncState,
519
+ payload: SyncState.PayloadUpstreamAdvance.make({ newEvents: [e1_1, e1_2, e1_3, e2_0] }),
443
520
  })
444
521
 
522
+ const e1_0_e2_1 = e1_0.rebase_(e2_0.seqNum, 1)
523
+ const e1_1_e2_2 = e1_1.rebase_(e1_0_e2_1.seqNum, 1)
524
+
525
+ expectRebase(result)
526
+ expectEventArraysEqual(result.newSyncState.pending, [e1_0_e2_1, e1_1_e2_2])
527
+ expectEventArraysEqual(result.newEvents, [e1_1, e1_2, e1_3, e2_0, e1_0_e2_1, e1_1_e2_2])
528
+ expectEventArraysEqual(result.rollbackEvents, [e1_0, e1_1])
529
+ expect(result.newSyncState.upstreamHead).toMatchObject(e2_0.seqNum)
530
+ expect(result.newSyncState.localHead).toMatchObject(e1_1_e2_2.seqNum)
531
+ }),
532
+ )
533
+
534
+ Vitest.describe('local-push', () => {
535
+ Vitest.describe('advance', () => {
536
+ Vitest.it.effect('should advance with new events', () =>
537
+ Effect.gen(function* () {
538
+ const syncState = new SyncState.SyncState({
539
+ pending: [e1_0],
540
+ upstreamHead: EventSequenceNumber.Client.ROOT,
541
+ localHead: e1_0.seqNum,
542
+ })
543
+ const result = yield* merge({
544
+ syncState,
545
+ payload: SyncState.PayloadLocalPush.make({ newEvents: [e1_1, e1_2, e1_3] }),
546
+ })
547
+
548
+ expectAdvance(result)
549
+ expectEventArraysEqual(result.newSyncState.pending, [e1_0, e1_1, e1_2, e1_3])
550
+ expect(result.newSyncState.upstreamHead).toMatchObject(EventSequenceNumber.Client.ROOT)
551
+ expect(result.newSyncState.localHead).toMatchObject(e1_3.seqNum)
552
+ expectEventArraysEqual(result.newEvents, [e1_1, e1_2, e1_3])
553
+ expectEventArraysEqual(result.confirmedEvents, [])
554
+ }),
555
+ )
556
+
445
557
  // Leaders can choose to ignore client-only events while still returning them for broadcast.
446
558
  // Ensure pending/local head only reflects events that must be pushed upstream.
447
- it('keeps pending empty when pushing only client-only events that are being ignored', () => {
448
- const syncState = new SyncState.SyncState({
449
- pending: [],
450
- upstreamHead: EventSequenceNumber.Client.ROOT,
451
- localHead: EventSequenceNumber.Client.ROOT,
452
- })
453
-
454
- const result = merge({
455
- syncState,
456
- payload: SyncState.PayloadLocalPush.make({ newEvents: [e0_1] }),
457
- ignoreClientEvents: true,
458
- })
459
-
460
- expectAdvance(result)
461
- expectEventArraysEqual(result.newSyncState.pending, [])
462
- expect(result.newSyncState.upstreamHead).toMatchObject(EventSequenceNumber.Client.ROOT)
463
- expect(result.newSyncState.localHead).toMatchObject(EventSequenceNumber.Client.ROOT)
464
- expectEventArraysEqual(result.newEvents, [e0_1])
465
- })
466
-
467
- it('appends only upstream-bound events to pending when ignoring client-only pushes', () => {
468
- const syncState = new SyncState.SyncState({
469
- pending: [],
470
- upstreamHead: EventSequenceNumber.Client.ROOT,
471
- localHead: EventSequenceNumber.Client.ROOT,
472
- })
473
-
474
- const result = merge({
475
- syncState,
476
- payload: SyncState.PayloadLocalPush.make({ newEvents: [e0_1, e1_0] }),
477
- ignoreClientEvents: true,
478
- })
479
-
480
- expectAdvance(result)
481
- expectEventArraysEqual(result.newSyncState.pending, [e1_0])
482
- expect(result.newSyncState.upstreamHead).toMatchObject(EventSequenceNumber.Client.ROOT)
483
- expect(result.newSyncState.localHead).toMatchObject(e1_0.seqNum)
484
- expectEventArraysEqual(result.newEvents, [e0_1, e1_0])
485
- })
486
- })
487
-
488
- describe('reject', () => {
489
- it('should reject when new events are greater than pending events', () => {
490
- const syncState = new SyncState.SyncState({
491
- pending: [e1_0, e1_1],
492
- upstreamHead: EventSequenceNumber.Client.ROOT,
493
- localHead: e1_1.seqNum,
494
- })
495
- const result = merge({
496
- syncState,
497
- payload: SyncState.PayloadLocalPush.make({ newEvents: [e1_1, e1_2] }),
498
- })
499
-
500
- expectReject(result)
501
- expect(result.expectedMinimumId).toMatchObject(e1_2.seqNum)
502
- })
559
+ Vitest.it.effect('keeps pending empty when pushing only client-only events that are being ignored', () =>
560
+ Effect.gen(function* () {
561
+ const syncState = new SyncState.SyncState({
562
+ pending: [],
563
+ upstreamHead: EventSequenceNumber.Client.ROOT,
564
+ localHead: EventSequenceNumber.Client.ROOT,
565
+ })
566
+
567
+ const result = yield* merge({
568
+ syncState,
569
+ payload: SyncState.PayloadLocalPush.make({ newEvents: [e0_1] }),
570
+ ignoreClientEvents: true,
571
+ })
572
+
573
+ expectAdvance(result)
574
+ expectEventArraysEqual(result.newSyncState.pending, [])
575
+ expect(result.newSyncState.upstreamHead).toMatchObject(EventSequenceNumber.Client.ROOT)
576
+ expect(result.newSyncState.localHead).toMatchObject(EventSequenceNumber.Client.ROOT)
577
+ expectEventArraysEqual(result.newEvents, [e0_1])
578
+ }),
579
+ )
580
+
581
+ Vitest.it.effect(
582
+ 'appends only upstream-bound events to pending when ignoring client-only pushes',
583
+ () =>
584
+ Effect.gen(function* () {
585
+ const syncState = new SyncState.SyncState({
586
+ pending: [],
587
+ upstreamHead: EventSequenceNumber.Client.ROOT,
588
+ localHead: EventSequenceNumber.Client.ROOT,
589
+ })
590
+
591
+ const result = yield* merge({
592
+ syncState,
593
+ payload: SyncState.PayloadLocalPush.make({ newEvents: [e0_1, e1_0] }),
594
+ ignoreClientEvents: true,
595
+ })
596
+
597
+ expectAdvance(result)
598
+ expectEventArraysEqual(result.newSyncState.pending, [e1_0])
599
+ expect(result.newSyncState.upstreamHead).toMatchObject(EventSequenceNumber.Client.ROOT)
600
+ expect(result.newSyncState.localHead).toMatchObject(e1_0.seqNum)
601
+ expectEventArraysEqual(result.newEvents, [e0_1, e1_0])
602
+ }),
603
+ )
604
+ })
605
+
606
+ Vitest.describe('reject', () => {
607
+ Vitest.it.effect('should reject when new events are greater than pending events', () =>
608
+ Effect.gen(function* () {
609
+ const syncState = new SyncState.SyncState({
610
+ pending: [e1_0, e1_1],
611
+ upstreamHead: EventSequenceNumber.Client.ROOT,
612
+ localHead: e1_1.seqNum,
613
+ })
614
+ const result = yield* merge({
615
+ syncState,
616
+ payload: SyncState.PayloadLocalPush.make({ newEvents: [e1_1, e1_2] }),
617
+ })
618
+
619
+ expectReject(result)
620
+ expect(result.expectedMinimumId).toMatchObject(e1_2.seqNum)
621
+ }),
622
+ )
503
623
  })
504
624
  })
505
625
  })
@@ -519,20 +639,20 @@ const expectEventArraysEqual = (
519
639
  })
520
640
  }
521
641
 
522
- function expectAdvance(
642
+ const expectAdvance: (
523
643
  result: typeof SyncState.MergeResult.Type,
524
- ): asserts result is typeof SyncState.MergeResultAdvance.Type {
644
+ ) => asserts result is typeof SyncState.MergeResultAdvance.Type = (result) => {
525
645
  expect(result._tag).toBe('advance')
526
646
  }
527
647
 
528
- function expectRebase(
648
+ const expectRebase: (
529
649
  result: typeof SyncState.MergeResult.Type,
530
- ): asserts result is typeof SyncState.MergeResultRebase.Type {
531
- expect(result._tag, `Expected rebase, got ${result}`).toBe('rebase')
650
+ ) => asserts result is typeof SyncState.MergeResultRebase.Type = (result) => {
651
+ expect(result._tag, `Expected rebase, got ${result._tag}`).toBe('rebase')
532
652
  }
533
653
 
534
- function expectReject(
654
+ const expectReject: (
535
655
  result: typeof SyncState.MergeResult.Type,
536
- ): asserts result is typeof SyncState.MergeResultReject.Type {
656
+ ) => asserts result is typeof SyncState.MergeResultReject.Type = (result) => {
537
657
  expect(result._tag).toBe('reject')
538
658
  }