@livestore/common 0.2.0 → 0.3.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 (244) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/__tests__/fixture.d.ts +163 -1
  3. package/dist/__tests__/fixture.d.ts.map +1 -1
  4. package/dist/__tests__/fixture.js +3 -1
  5. package/dist/__tests__/fixture.js.map +1 -1
  6. package/dist/adapter-types.d.ts +53 -38
  7. package/dist/adapter-types.d.ts.map +1 -1
  8. package/dist/adapter-types.js +5 -7
  9. package/dist/adapter-types.js.map +1 -1
  10. package/dist/bounded-collections.d.ts +2 -2
  11. package/dist/bounded-collections.d.ts.map +1 -1
  12. package/dist/debug-info.d.ts +13 -13
  13. package/dist/derived-mutations.d.ts +1 -1
  14. package/dist/derived-mutations.d.ts.map +1 -1
  15. package/dist/devtools/devtools-bridge.d.ts +2 -2
  16. package/dist/devtools/devtools-bridge.d.ts.map +1 -1
  17. package/dist/devtools/devtools-messages.d.ts +84 -196
  18. package/dist/devtools/devtools-messages.d.ts.map +1 -1
  19. package/dist/devtools/devtools-messages.js +55 -61
  20. package/dist/devtools/devtools-messages.js.map +1 -1
  21. package/dist/devtools/index.d.ts.map +1 -1
  22. package/dist/devtools/index.js +1 -2
  23. package/dist/devtools/index.js.map +1 -1
  24. package/dist/index.d.ts +2 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +2 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/init-singleton-tables.d.ts +1 -1
  29. package/dist/init-singleton-tables.d.ts.map +1 -1
  30. package/dist/leader-thread/apply-mutation.d.ts +8 -0
  31. package/dist/leader-thread/apply-mutation.d.ts.map +1 -0
  32. package/dist/leader-thread/apply-mutation.js +95 -0
  33. package/dist/leader-thread/apply-mutation.js.map +1 -0
  34. package/dist/leader-thread/connection.d.ts +11 -0
  35. package/dist/leader-thread/connection.d.ts.map +1 -0
  36. package/dist/leader-thread/connection.js +44 -0
  37. package/dist/leader-thread/connection.js.map +1 -0
  38. package/dist/leader-thread/leader-sync-processor.d.ts +47 -0
  39. package/dist/leader-thread/leader-sync-processor.d.ts.map +1 -0
  40. package/dist/leader-thread/leader-sync-processor.js +422 -0
  41. package/dist/leader-thread/leader-sync-processor.js.map +1 -0
  42. package/dist/leader-thread/leader-worker-devtools.d.ts +6 -0
  43. package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -0
  44. package/dist/leader-thread/leader-worker-devtools.js +216 -0
  45. package/dist/leader-thread/leader-worker-devtools.js.map +1 -0
  46. package/dist/leader-thread/make-leader-thread-layer.d.ts +20 -0
  47. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -0
  48. package/dist/leader-thread/make-leader-thread-layer.js +106 -0
  49. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -0
  50. package/dist/leader-thread/mod.d.ts +7 -0
  51. package/dist/leader-thread/mod.d.ts.map +1 -0
  52. package/dist/leader-thread/mod.js +7 -0
  53. package/dist/leader-thread/mod.js.map +1 -0
  54. package/dist/leader-thread/mutationlog.d.ts +23 -0
  55. package/dist/leader-thread/mutationlog.d.ts.map +1 -0
  56. package/dist/leader-thread/mutationlog.js +27 -0
  57. package/dist/leader-thread/mutationlog.js.map +1 -0
  58. package/dist/leader-thread/pull-queue-set.d.ts +7 -0
  59. package/dist/leader-thread/pull-queue-set.d.ts.map +1 -0
  60. package/dist/leader-thread/pull-queue-set.js +39 -0
  61. package/dist/leader-thread/pull-queue-set.js.map +1 -0
  62. package/dist/leader-thread/recreate-db.d.ts +7 -0
  63. package/dist/leader-thread/recreate-db.d.ts.map +1 -0
  64. package/dist/leader-thread/recreate-db.js +69 -0
  65. package/dist/leader-thread/recreate-db.js.map +1 -0
  66. package/dist/leader-thread/shutdown-channel.d.ts +15 -0
  67. package/dist/leader-thread/shutdown-channel.d.ts.map +1 -0
  68. package/dist/leader-thread/shutdown-channel.js +7 -0
  69. package/dist/leader-thread/shutdown-channel.js.map +1 -0
  70. package/dist/leader-thread/types.d.ts +87 -0
  71. package/dist/leader-thread/types.d.ts.map +1 -0
  72. package/dist/leader-thread/types.js +11 -0
  73. package/dist/leader-thread/types.js.map +1 -0
  74. package/dist/mutation.d.ts +3 -4
  75. package/dist/mutation.d.ts.map +1 -1
  76. package/dist/mutation.js +0 -14
  77. package/dist/mutation.js.map +1 -1
  78. package/dist/otel.d.ts +7 -0
  79. package/dist/otel.d.ts.map +1 -0
  80. package/dist/otel.js +11 -0
  81. package/dist/otel.js.map +1 -0
  82. package/dist/query-builder/api.d.ts +2 -2
  83. package/dist/query-builder/api.d.ts.map +1 -1
  84. package/dist/query-builder/api.js.map +1 -1
  85. package/dist/query-builder/impl.d.ts +1 -1
  86. package/dist/query-builder/impl.d.ts.map +1 -1
  87. package/dist/query-builder/impl.js +21 -4
  88. package/dist/query-builder/impl.js.map +1 -1
  89. package/dist/query-builder/impl.test.js +24 -1
  90. package/dist/query-builder/impl.test.js.map +1 -1
  91. package/dist/query-info.d.ts +1 -1
  92. package/dist/query-info.d.ts.map +1 -1
  93. package/dist/rehydrate-from-mutationlog.d.ts +1 -1
  94. package/dist/rehydrate-from-mutationlog.d.ts.map +1 -1
  95. package/dist/rehydrate-from-mutationlog.js +6 -6
  96. package/dist/rehydrate-from-mutationlog.js.map +1 -1
  97. package/dist/schema/EventId.d.ts +37 -0
  98. package/dist/schema/EventId.d.ts.map +1 -0
  99. package/dist/schema/EventId.js +30 -0
  100. package/dist/schema/EventId.js.map +1 -0
  101. package/dist/schema/MutationEvent.d.ts +191 -0
  102. package/dist/schema/MutationEvent.d.ts.map +1 -0
  103. package/dist/schema/MutationEvent.js +56 -0
  104. package/dist/schema/MutationEvent.js.map +1 -0
  105. package/dist/schema/mod.d.ts +8 -0
  106. package/dist/schema/mod.d.ts.map +1 -0
  107. package/dist/schema/mod.js +8 -0
  108. package/dist/schema/mod.js.map +1 -0
  109. package/dist/schema/mutations.d.ts +3 -123
  110. package/dist/schema/mutations.d.ts.map +1 -1
  111. package/dist/schema/mutations.js +0 -26
  112. package/dist/schema/mutations.js.map +1 -1
  113. package/dist/schema/{index.d.ts → schema.d.ts} +1 -5
  114. package/dist/schema/schema.d.ts.map +1 -0
  115. package/dist/schema/{index.js → schema.js} +1 -5
  116. package/dist/schema/schema.js.map +1 -0
  117. package/dist/schema/system-tables.d.ts +55 -29
  118. package/dist/schema/system-tables.d.ts.map +1 -1
  119. package/dist/schema/system-tables.js +10 -5
  120. package/dist/schema/system-tables.js.map +1 -1
  121. package/dist/schema-management/migrations.d.ts +1 -1
  122. package/dist/schema-management/migrations.d.ts.map +1 -1
  123. package/dist/schema-management/migrations.js +6 -1
  124. package/dist/schema-management/migrations.js.map +1 -1
  125. package/dist/schema-management/validate-mutation-defs.d.ts +1 -1
  126. package/dist/schema-management/validate-mutation-defs.d.ts.map +1 -1
  127. package/dist/sync/client-session-sync-processor.d.ts +45 -0
  128. package/dist/sync/client-session-sync-processor.d.ts.map +1 -0
  129. package/dist/sync/client-session-sync-processor.js +131 -0
  130. package/dist/sync/client-session-sync-processor.js.map +1 -0
  131. package/dist/sync/index.d.ts +2 -0
  132. package/dist/sync/index.d.ts.map +1 -1
  133. package/dist/sync/index.js +2 -0
  134. package/dist/sync/index.js.map +1 -1
  135. package/dist/sync/next/compact-events.d.ts +1 -1
  136. package/dist/sync/next/compact-events.d.ts.map +1 -1
  137. package/dist/sync/next/compact-events.js +2 -1
  138. package/dist/sync/next/compact-events.js.map +1 -1
  139. package/dist/sync/next/facts.d.ts +5 -5
  140. package/dist/sync/next/facts.d.ts.map +1 -1
  141. package/dist/sync/next/facts.js +1 -1
  142. package/dist/sync/next/facts.js.map +1 -1
  143. package/dist/sync/next/history-dag-common.d.ts +30 -0
  144. package/dist/sync/next/history-dag-common.d.ts.map +1 -0
  145. package/dist/sync/next/history-dag-common.js +20 -0
  146. package/dist/sync/next/history-dag-common.js.map +1 -0
  147. package/dist/sync/next/history-dag.d.ts +4 -27
  148. package/dist/sync/next/history-dag.d.ts.map +1 -1
  149. package/dist/sync/next/history-dag.js +1 -19
  150. package/dist/sync/next/history-dag.js.map +1 -1
  151. package/dist/sync/next/mod.d.ts +1 -0
  152. package/dist/sync/next/mod.d.ts.map +1 -1
  153. package/dist/sync/next/mod.js +1 -0
  154. package/dist/sync/next/mod.js.map +1 -1
  155. package/dist/sync/next/rebase-events.d.ts +3 -2
  156. package/dist/sync/next/rebase-events.d.ts.map +1 -1
  157. package/dist/sync/next/rebase-events.js.map +1 -1
  158. package/dist/sync/next/test/compact-events.test.d.ts.map +1 -1
  159. package/dist/sync/next/test/compact-events.test.js +2 -1
  160. package/dist/sync/next/test/compact-events.test.js.map +1 -1
  161. package/dist/sync/next/test/mutation-fixtures.d.ts +1 -1
  162. package/dist/sync/next/test/mutation-fixtures.d.ts.map +1 -1
  163. package/dist/sync/next/test/mutation-fixtures.js +4 -3
  164. package/dist/sync/next/test/mutation-fixtures.js.map +1 -1
  165. package/dist/sync/sync.d.ts +33 -12
  166. package/dist/sync/sync.d.ts.map +1 -1
  167. package/dist/sync/sync.js +10 -1
  168. package/dist/sync/sync.js.map +1 -1
  169. package/dist/sync/syncstate.d.ts +123 -0
  170. package/dist/sync/syncstate.d.ts.map +1 -0
  171. package/dist/sync/syncstate.js +248 -0
  172. package/dist/sync/syncstate.js.map +1 -0
  173. package/dist/sync/syncstate.test.d.ts +2 -0
  174. package/dist/sync/syncstate.test.d.ts.map +1 -0
  175. package/dist/sync/syncstate.test.js +399 -0
  176. package/dist/sync/syncstate.test.js.map +1 -0
  177. package/dist/sync/validate-push-payload.d.ts +5 -0
  178. package/dist/sync/validate-push-payload.d.ts.map +1 -0
  179. package/dist/sync/validate-push-payload.js +15 -0
  180. package/dist/sync/validate-push-payload.js.map +1 -0
  181. package/dist/util.d.ts +2 -2
  182. package/dist/util.d.ts.map +1 -1
  183. package/dist/version.d.ts +2 -2
  184. package/dist/version.d.ts.map +1 -1
  185. package/dist/version.js +2 -2
  186. package/dist/version.js.map +1 -1
  187. package/package.json +13 -6
  188. package/src/__tests__/fixture.ts +5 -1
  189. package/src/adapter-types.ts +60 -34
  190. package/src/derived-mutations.test.ts +1 -1
  191. package/src/derived-mutations.ts +1 -1
  192. package/src/devtools/devtools-bridge.ts +2 -2
  193. package/src/devtools/devtools-messages.ts +70 -74
  194. package/src/devtools/index.ts +1 -2
  195. package/src/index.ts +2 -1
  196. package/src/init-singleton-tables.ts +1 -1
  197. package/src/leader-thread/apply-mutation.ts +143 -0
  198. package/src/leader-thread/connection.ts +67 -0
  199. package/src/leader-thread/leader-sync-processor.ts +666 -0
  200. package/src/leader-thread/leader-worker-devtools.ts +358 -0
  201. package/src/leader-thread/make-leader-thread-layer.ts +192 -0
  202. package/src/leader-thread/mod.ts +6 -0
  203. package/src/leader-thread/mutationlog.ts +42 -0
  204. package/src/leader-thread/pull-queue-set.ts +58 -0
  205. package/src/leader-thread/recreate-db.ts +109 -0
  206. package/src/leader-thread/shutdown-channel.ts +13 -0
  207. package/src/leader-thread/types.ts +129 -0
  208. package/src/mutation.ts +3 -21
  209. package/src/otel.ts +20 -0
  210. package/src/query-builder/api.ts +3 -2
  211. package/src/query-builder/impl.test.ts +28 -1
  212. package/src/query-builder/impl.ts +21 -5
  213. package/src/query-info.ts +1 -1
  214. package/src/rehydrate-from-mutationlog.ts +7 -11
  215. package/src/schema/EventId.ts +46 -0
  216. package/src/schema/MutationEvent.ts +161 -0
  217. package/src/schema/mod.ts +7 -0
  218. package/src/schema/mutations.ts +5 -126
  219. package/src/schema/{index.ts → schema.ts} +0 -5
  220. package/src/schema/system-tables.ts +18 -5
  221. package/src/schema-management/migrations.ts +9 -2
  222. package/src/schema-management/validate-mutation-defs.ts +1 -1
  223. package/src/sync/client-session-sync-processor.ts +207 -0
  224. package/src/sync/index.ts +2 -0
  225. package/src/sync/next/compact-events.ts +3 -2
  226. package/src/sync/next/facts.ts +11 -5
  227. package/src/sync/next/history-dag-common.ts +44 -0
  228. package/src/sync/next/history-dag.ts +3 -45
  229. package/src/sync/next/mod.ts +1 -0
  230. package/src/sync/next/rebase-events.ts +6 -5
  231. package/src/sync/next/test/compact-events.test.ts +3 -2
  232. package/src/sync/next/test/mutation-fixtures.ts +7 -6
  233. package/src/sync/sync.ts +32 -12
  234. package/src/sync/syncstate.test.ts +464 -0
  235. package/src/sync/syncstate.ts +385 -0
  236. package/src/sync/validate-push-payload.ts +18 -0
  237. package/src/version.ts +2 -2
  238. package/dist/schema/index.d.ts.map +0 -1
  239. package/dist/schema/index.js.map +0 -1
  240. package/dist/sync/next-mutation-event-id-pair.d.ts +0 -14
  241. package/dist/sync/next-mutation-event-id-pair.d.ts.map +0 -1
  242. package/dist/sync/next-mutation-event-id-pair.js +0 -13
  243. package/dist/sync/next-mutation-event-id-pair.js.map +0 -1
  244. package/src/sync/next-mutation-event-id-pair.ts +0 -20
@@ -0,0 +1,464 @@
1
+ /* eslint-disable prefer-arrow/prefer-arrow-functions */
2
+ import { describe, expect, it } from 'vitest'
3
+
4
+ import * as EventId from '../schema/EventId.js'
5
+ import * as MutationEvent from '../schema/MutationEvent.js'
6
+ import * as SyncState from './syncstate.js'
7
+
8
+ class TestEvent extends MutationEvent.EncodedWithMeta {
9
+ constructor(
10
+ public readonly id: EventId.EventId,
11
+ public readonly parentId: EventId.EventId,
12
+ public readonly payload: string,
13
+ public readonly isLocal: boolean,
14
+ ) {
15
+ super({
16
+ id,
17
+ parentId,
18
+ mutation: 'a',
19
+ args: payload,
20
+ meta: {},
21
+ })
22
+ }
23
+
24
+ rebase_ = (parentId: EventId.EventId) => {
25
+ return this.rebase(parentId, this.isLocal)
26
+ }
27
+
28
+ // Only used for Vitest printing
29
+ // toJSON = () => `(${this.id.global},${this.id.local},${this.payload})`
30
+ // toString = () => this.toJSON()
31
+ }
32
+
33
+ const e_r_1 = new TestEvent({ global: -1, local: 1 }, EventId.ROOT, 'a', true)
34
+ const e_0_0 = new TestEvent({ global: 0, local: 0 }, EventId.ROOT, 'a', false)
35
+ const e_0_1 = new TestEvent({ global: 0, local: 1 }, e_0_0.id, 'a', true)
36
+ const e_0_2 = new TestEvent({ global: 0, local: 2 }, e_0_1.id, 'a', true)
37
+ const e_0_3 = new TestEvent({ global: 0, local: 3 }, e_0_2.id, 'a', true)
38
+ const e_1_0 = new TestEvent({ global: 1, local: 0 }, e_0_0.id, 'a', false)
39
+ const e_1_1 = new TestEvent({ global: 1, local: 1 }, e_1_0.id, 'a', true)
40
+
41
+ const isEqualEvent = MutationEvent.isEqualEncoded
42
+
43
+ const isLocalEvent = (event: MutationEvent.EncodedWithMeta) => (event as TestEvent).isLocal
44
+
45
+ describe('syncstate', () => {
46
+ describe('updateSyncState', () => {
47
+ const run = ({
48
+ syncState,
49
+ payload,
50
+ ignoreLocalEvents = false,
51
+ }: {
52
+ syncState: SyncState.SyncState
53
+ payload: typeof SyncState.Payload.Type
54
+ ignoreLocalEvents?: boolean
55
+ }) => SyncState.updateSyncState({ syncState, payload, isLocalEvent, isEqualEvent, ignoreLocalEvents })
56
+
57
+ describe.each([{ trimRollbackUntil: false }, { trimRollbackUntil: true }])(
58
+ 'upstream-rebase (trimRollbackUntil: $trimRollbackUntil)',
59
+ ({ trimRollbackUntil }) => {
60
+ it('should rollback until start', () => {
61
+ const syncState = {
62
+ pending: [e_1_0],
63
+ rollbackTail: [e_0_0, e_0_1],
64
+ upstreamHead: EventId.ROOT,
65
+ localHead: e_1_0.id,
66
+ }
67
+ const e_0_0_e_1_0 = e_0_0.rebase_(e_1_0.id)
68
+ const e_0_1_e_1_1 = e_0_1.rebase_(e_0_0_e_1_0.id)
69
+ const result = run({
70
+ syncState,
71
+ payload: {
72
+ _tag: 'upstream-rebase',
73
+ rollbackUntil: e_0_0.id,
74
+ newEvents: [e_0_0_e_1_0, e_0_1_e_1_1],
75
+ trimRollbackUntil: trimRollbackUntil ? e_0_1_e_1_1.id : undefined,
76
+ },
77
+ })
78
+ const e_1_0_e_2_0 = e_1_0.rebase_(e_0_0_e_1_0.id)
79
+ expectRebase(result)
80
+ expectEventArraysEqual(result.newSyncState.pending, [e_1_0_e_2_0])
81
+ if (trimRollbackUntil) {
82
+ expectEventArraysEqual(result.newSyncState.rollbackTail, [])
83
+ } else {
84
+ expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_0_e_1_0, e_0_1_e_1_1])
85
+ }
86
+ expect(result.newSyncState.upstreamHead).toBe(e_0_1_e_1_1.id)
87
+ expect(result.newSyncState.localHead).toMatchObject(e_1_0_e_2_0.id)
88
+ expectEventArraysEqual(result.newEvents, [e_0_0_e_1_0, e_0_1_e_1_1])
89
+ expectEventArraysEqual(result.eventsToRollback, [e_0_0, e_0_1, e_1_0])
90
+ })
91
+
92
+ it('should rollback only to specified point', () => {
93
+ const syncState = {
94
+ pending: [e_1_0],
95
+ rollbackTail: [e_0_0, e_0_1],
96
+ upstreamHead: EventId.ROOT,
97
+ localHead: e_1_0.id,
98
+ }
99
+ const e_0_1_e_1_0 = e_0_1.rebase_(e_0_0.id)
100
+ const result = run({
101
+ syncState,
102
+ payload: {
103
+ _tag: 'upstream-rebase',
104
+ rollbackUntil: e_0_1.id,
105
+ newEvents: [e_0_1_e_1_0],
106
+ trimRollbackUntil: trimRollbackUntil ? e_0_0.id : undefined,
107
+ },
108
+ })
109
+ const e_1_0_e_2_0 = e_1_0.rebase_(e_0_1_e_1_0.id)
110
+ expectRebase(result)
111
+ expectEventArraysEqual(result.newSyncState.pending, [e_1_0_e_2_0])
112
+ if (trimRollbackUntil) {
113
+ expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_1_e_1_0])
114
+ } else {
115
+ expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_0, e_0_1_e_1_0])
116
+ }
117
+ expect(result.newSyncState.upstreamHead).toBe(e_0_1_e_1_0.id)
118
+ expect(result.newSyncState.localHead).toMatchObject(e_1_0_e_2_0.id)
119
+ expectEventArraysEqual(result.newEvents, [e_0_1_e_1_0])
120
+ expectEventArraysEqual(result.eventsToRollback, [e_0_1, e_1_0])
121
+ })
122
+
123
+ it('should work for empty pending', () => {
124
+ const syncState = { pending: [], rollbackTail: [e_0_0], upstreamHead: EventId.ROOT, localHead: e_0_0.id }
125
+ const result = run({
126
+ syncState,
127
+ payload: { _tag: 'upstream-rebase', rollbackUntil: e_0_0.id, newEvents: [e_1_0] },
128
+ })
129
+ expectRebase(result)
130
+ expectEventArraysEqual(result.newSyncState.pending, [])
131
+ expectEventArraysEqual(result.newSyncState.rollbackTail, [e_1_0])
132
+ expect(result.newSyncState.upstreamHead).toBe(e_1_0.id)
133
+ expect(result.newSyncState.localHead).toMatchObject(e_1_0.id)
134
+ expect(result.newEvents).toEqual([e_1_0])
135
+ })
136
+
137
+ it('should fail for empty rollback tail', () => {
138
+ const syncState = { pending: [], rollbackTail: [], upstreamHead: EventId.ROOT, localHead: e_0_0.id }
139
+ expect(() =>
140
+ run({
141
+ syncState,
142
+ payload: { _tag: 'upstream-rebase', rollbackUntil: e_0_0.id, newEvents: [e_1_0] },
143
+ }),
144
+ ).toThrow()
145
+ })
146
+
147
+ it('should work for empty incoming', () => {
148
+ const syncState = { pending: [], rollbackTail: [e_0_0], upstreamHead: EventId.ROOT, localHead: e_0_0.id }
149
+ const result = run({
150
+ syncState,
151
+ payload: { _tag: 'upstream-rebase', rollbackUntil: e_0_0.id, newEvents: [] },
152
+ })
153
+ expectRebase(result)
154
+ expectEventArraysEqual(result.newSyncState.pending, [])
155
+ expectEventArraysEqual(result.newSyncState.rollbackTail, [])
156
+ expect(result.newSyncState.upstreamHead).toBe(EventId.ROOT)
157
+ expect(result.newSyncState.localHead).toMatchObject(EventId.ROOT)
158
+ expect(result.newEvents).toEqual([])
159
+ })
160
+ },
161
+ )
162
+
163
+ describe('upstream-advance: advance', () => {
164
+ it('should throw error if newEvents are not sorted in ascending order by eventId (local)', () => {
165
+ const syncState = { pending: [e_0_0], rollbackTail: [], upstreamHead: EventId.ROOT, localHead: e_0_0.id }
166
+ expect(() => run({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e_0_1, e_0_0] } })).toThrow()
167
+ })
168
+
169
+ it('should throw error if newEvents are not sorted in ascending order by eventId (global)', () => {
170
+ const syncState = { pending: [e_0_0], rollbackTail: [], upstreamHead: EventId.ROOT, localHead: e_0_0.id }
171
+ expect(() => run({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e_1_0, e_0_0] } })).toThrow()
172
+ })
173
+
174
+ it('should acknowledge pending event when receiving matching event', () => {
175
+ const syncState = { pending: [e_0_0], rollbackTail: [], upstreamHead: EventId.ROOT, localHead: e_0_0.id }
176
+ const result = run({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e_0_0] } })
177
+
178
+ expectAdvance(result)
179
+ expectEventArraysEqual(result.newSyncState.pending, [])
180
+ expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_0])
181
+ expect(result.newSyncState.upstreamHead).toBe(e_0_0.id)
182
+ expect(result.newSyncState.localHead).toMatchObject(e_0_0.id)
183
+ expect(result.newEvents).toEqual([])
184
+ })
185
+
186
+ it('should acknowledge partial pending event when receiving matching event', () => {
187
+ const syncState = {
188
+ pending: [e_0_0, e_1_0],
189
+ rollbackTail: [],
190
+ upstreamHead: EventId.ROOT,
191
+ localHead: e_1_0.id,
192
+ }
193
+ const result = run({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e_0_0] } })
194
+
195
+ expectAdvance(result)
196
+ expectEventArraysEqual(result.newSyncState.pending, [e_1_0])
197
+ expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_0])
198
+ expect(result.newSyncState.upstreamHead).toBe(e_0_0.id)
199
+ expect(result.newSyncState.localHead).toMatchObject(e_1_0.id)
200
+ expect(result.newEvents).toEqual([])
201
+ })
202
+
203
+ it('should acknowledge pending event and add new event', () => {
204
+ const syncState = { pending: [e_0_0], rollbackTail: [], upstreamHead: EventId.ROOT, localHead: e_0_0.id }
205
+ const result = run({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e_0_0, e_0_1] } })
206
+
207
+ expectAdvance(result)
208
+ expectEventArraysEqual(result.newSyncState.pending, [])
209
+ expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_0, e_0_1])
210
+ expect(result.newSyncState.upstreamHead).toBe(e_0_1.id)
211
+ expect(result.newSyncState.localHead).toMatchObject(e_0_1.id)
212
+ expect(result.newEvents).toEqual([e_0_1])
213
+ })
214
+
215
+ it('should acknowledge pending event and add multiple new events', () => {
216
+ const syncState = { pending: [e_0_1], rollbackTail: [], upstreamHead: e_0_0.id, localHead: e_0_1.id }
217
+ const result = run({
218
+ syncState,
219
+ payload: { _tag: 'upstream-advance', newEvents: [e_0_1, e_0_2, e_0_3, e_1_0, e_1_1] },
220
+ })
221
+
222
+ expectAdvance(result)
223
+ expectEventArraysEqual(result.newSyncState.pending, [])
224
+ expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_1, e_0_2, e_0_3, e_1_0, e_1_1])
225
+ expect(result.newSyncState.upstreamHead).toBe(e_1_1.id)
226
+ expect(result.newSyncState.localHead).toMatchObject(e_1_1.id)
227
+ expect(result.newEvents).toEqual([e_0_2, e_0_3, e_1_0, e_1_1])
228
+ })
229
+
230
+ it('should ignore local events (incoming is subset of pending)', () => {
231
+ const syncState = {
232
+ pending: [e_r_1, e_0_0],
233
+ rollbackTail: [],
234
+ upstreamHead: EventId.ROOT,
235
+ localHead: e_0_0.id,
236
+ }
237
+ const result = run({
238
+ syncState,
239
+ payload: { _tag: 'upstream-advance', newEvents: [e_0_0] },
240
+ ignoreLocalEvents: true,
241
+ })
242
+ expectAdvance(result)
243
+ expectEventArraysEqual(result.newSyncState.pending, [])
244
+ expectEventArraysEqual(result.newSyncState.rollbackTail, [e_r_1, e_0_0])
245
+ expect(result.newSyncState.upstreamHead).toBe(e_0_0.id)
246
+ expect(result.newSyncState.localHead).toMatchObject(e_0_0.id)
247
+ expect(result.newEvents).toEqual([])
248
+ })
249
+
250
+ it('should ignore local events (incoming is subset of pending case 2)', () => {
251
+ const syncState = {
252
+ pending: [e_r_1, e_0_0, e_1_0],
253
+ rollbackTail: [],
254
+ upstreamHead: EventId.ROOT,
255
+ localHead: e_0_0.id,
256
+ }
257
+ const result = run({
258
+ syncState,
259
+ payload: { _tag: 'upstream-advance', newEvents: [e_0_0] },
260
+ ignoreLocalEvents: true,
261
+ })
262
+ expectAdvance(result)
263
+ expectEventArraysEqual(result.newSyncState.pending, [e_1_0])
264
+ expectEventArraysEqual(result.newSyncState.rollbackTail, [e_r_1, e_0_0])
265
+ expect(result.newSyncState.upstreamHead).toBe(e_0_0.id)
266
+ expect(result.newSyncState.localHead).toMatchObject(e_1_0.id)
267
+ expect(result.newEvents).toEqual([])
268
+ })
269
+
270
+ it('should ignore local events (incoming goes beyond pending)', () => {
271
+ const syncState = {
272
+ pending: [e_r_1, e_0_0, e_0_1],
273
+ rollbackTail: [],
274
+ upstreamHead: EventId.ROOT,
275
+ localHead: e_0_1.id,
276
+ }
277
+ const result = run({
278
+ syncState,
279
+ payload: { _tag: 'upstream-advance', newEvents: [e_0_0, e_1_0] },
280
+ ignoreLocalEvents: true,
281
+ })
282
+
283
+ expectAdvance(result)
284
+ expectEventArraysEqual(result.newSyncState.pending, [])
285
+ expectEventArraysEqual(result.newSyncState.rollbackTail, [e_r_1, e_0_0, e_0_1, e_1_0])
286
+ expect(result.newSyncState.upstreamHead).toBe(e_1_0.id)
287
+ expect(result.newSyncState.localHead).toMatchObject(e_1_0.id)
288
+ expect(result.newEvents).toEqual([e_1_0])
289
+ })
290
+ })
291
+
292
+ describe('upstream-advance: rebase', () => {
293
+ it('should rebase single local event to end', () => {
294
+ const syncState = { pending: [e_0_0], rollbackTail: [], upstreamHead: EventId.ROOT, localHead: e_0_0.id }
295
+ const result = run({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e_0_1] } })
296
+
297
+ const e_0_0_e_0_2 = e_0_0.rebase_(e_0_1.id)
298
+
299
+ expectRebase(result)
300
+ expectEventArraysEqual(result.newSyncState.pending, [e_0_0_e_0_2])
301
+ expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_1])
302
+ expect(result.newSyncState.upstreamHead).toBe(e_0_1.id)
303
+ expect(result.newSyncState.localHead).toMatchObject(e_0_0_e_0_2.id)
304
+ expectEventArraysEqual(result.eventsToRollback, [e_0_0])
305
+ expectEventArraysEqual(result.newEvents, [e_0_1, e_0_0_e_0_2])
306
+ })
307
+
308
+ it('should rebase different event with same id (no rollback tail)', () => {
309
+ const e_0_0_b = new TestEvent({ global: 0, local: 0 }, EventId.ROOT, '0_0_b', true)
310
+ const syncState = { pending: [e_0_0_b], rollbackTail: [], upstreamHead: EventId.ROOT, localHead: e_0_0_b.id }
311
+ const result = run({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e_0_0] } })
312
+
313
+ const e_0_0_e_1_0 = e_0_0_b.rebase_(e_0_0.id)
314
+
315
+ expectRebase(result)
316
+ expectEventArraysEqual(result.newSyncState.pending, [e_0_0_e_1_0])
317
+ expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_0])
318
+ expectEventArraysEqual(result.newEvents, [e_0_0, e_0_0_e_1_0])
319
+ expectEventArraysEqual(result.eventsToRollback, [e_0_0_b])
320
+ expect(result.newSyncState.upstreamHead).toBe(e_0_0.id)
321
+ expect(result.newSyncState.localHead).toMatchObject(e_0_0_e_1_0.id)
322
+ })
323
+
324
+ it('should rebase different event with same id', () => {
325
+ const e_1_0_b = new TestEvent({ global: 1, local: 0 }, e_0_0.id, '1_0_b', false)
326
+ const syncState = {
327
+ pending: [e_1_0_b],
328
+ rollbackTail: [e_0_0, e_0_1],
329
+ upstreamHead: EventId.ROOT,
330
+ localHead: e_1_0_b.id,
331
+ }
332
+ const result = run({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e_1_0] } })
333
+ const e_1_0_e_2_0 = e_1_0_b.rebase_(e_1_0.id)
334
+
335
+ expectRebase(result)
336
+ expectEventArraysEqual(result.newSyncState.pending, [e_1_0_e_2_0])
337
+ expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_0, e_0_1, e_1_0])
338
+ expectEventArraysEqual(result.newEvents, [e_1_0, e_1_0_e_2_0])
339
+ expectEventArraysEqual(result.eventsToRollback, [e_0_0, e_0_1, e_1_0_b])
340
+ expect(result.newSyncState.upstreamHead).toBe(e_1_0.id)
341
+ expect(result.newSyncState.localHead).toMatchObject(e_1_0_e_2_0.id)
342
+ })
343
+
344
+ it('should rebase single local event to end (more incoming events)', () => {
345
+ const syncState = { pending: [e_0_0], rollbackTail: [], upstreamHead: EventId.ROOT, localHead: e_0_0.id }
346
+ const result = run({
347
+ syncState,
348
+ payload: { _tag: 'upstream-advance', newEvents: [e_0_1, e_0_2, e_0_3, e_1_0] },
349
+ })
350
+
351
+ const e_0_0_e_2_0 = e_0_0.rebase_(e_1_0.id)
352
+
353
+ expectRebase(result)
354
+ expectEventArraysEqual(result.newSyncState.pending, [e_0_0_e_2_0])
355
+ expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_1, e_0_2, e_0_3, e_1_0])
356
+ expect(result.newSyncState.upstreamHead).toBe(e_1_0.id)
357
+ expect(result.newSyncState.localHead).toMatchObject(e_0_0_e_2_0.id)
358
+ })
359
+
360
+ it('should only rebase divergent events when first event matches', () => {
361
+ const syncState = {
362
+ pending: [e_0_0, e_0_1],
363
+ rollbackTail: [],
364
+ upstreamHead: EventId.ROOT,
365
+ localHead: e_0_0.id,
366
+ }
367
+ const result = run({
368
+ syncState,
369
+ payload: { _tag: 'upstream-advance', newEvents: [e_0_0, e_0_2, e_0_3, e_1_0] },
370
+ })
371
+
372
+ const e_0_1_e_1_1 = e_0_1.rebase_(e_1_0.id)
373
+
374
+ expectRebase(result)
375
+ expectEventArraysEqual(result.newSyncState.pending, [e_0_1_e_1_1])
376
+ expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_0, e_0_2, e_0_3, e_1_0])
377
+ expectEventArraysEqual(result.eventsToRollback, [e_0_1])
378
+ expectEventArraysEqual(result.newEvents, [e_0_2, e_0_3, e_1_0, e_0_1_e_1_1])
379
+ expect(result.newSyncState.upstreamHead).toBe(e_1_0.id)
380
+ expect(result.newSyncState.localHead).toMatchObject(e_0_1_e_1_1.id)
381
+ })
382
+
383
+ it('should rebase all local events when incoming chain starts differently', () => {
384
+ const syncState = {
385
+ pending: [e_0_0, e_0_1],
386
+ rollbackTail: [],
387
+ upstreamHead: EventId.ROOT,
388
+ localHead: e_0_1.id,
389
+ }
390
+ const result = run({
391
+ syncState,
392
+ payload: { _tag: 'upstream-advance', newEvents: [e_0_1, e_0_2, e_0_3, e_1_0] },
393
+ })
394
+
395
+ const e_0_0_e_1_1 = e_0_0.rebase_(e_1_0.id)
396
+ const e_0_1_e_1_2 = e_0_1.rebase_(e_0_0_e_1_1.id)
397
+
398
+ expectRebase(result)
399
+ expectEventArraysEqual(result.newSyncState.pending, [e_0_0_e_1_1, e_0_1_e_1_2])
400
+ expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_1, e_0_2, e_0_3, e_1_0])
401
+ expectEventArraysEqual(result.newEvents, [e_0_1, e_0_2, e_0_3, e_1_0, e_0_0_e_1_1, e_0_1_e_1_2])
402
+ expectEventArraysEqual(result.eventsToRollback, [e_0_0, e_0_1])
403
+ expect(result.newSyncState.upstreamHead).toBe(e_1_0.id)
404
+ expect(result.newSyncState.localHead).toMatchObject(e_0_1_e_1_2.id)
405
+ })
406
+
407
+ describe('local-push', () => {
408
+ describe('advance', () => {
409
+ it('should advance with new events', () => {
410
+ const syncState = { pending: [e_0_0], rollbackTail: [], upstreamHead: EventId.ROOT, localHead: e_0_0.id }
411
+ const result = run({ syncState, payload: { _tag: 'local-push', newEvents: [e_0_1, e_0_2, e_0_3] } })
412
+
413
+ expectAdvance(result)
414
+ expectEventArraysEqual(result.newSyncState.pending, [e_0_0, e_0_1, e_0_2, e_0_3])
415
+ expectEventArraysEqual(result.newSyncState.rollbackTail, [])
416
+ expect(result.newSyncState.upstreamHead).toBe(EventId.ROOT)
417
+ expect(result.newSyncState.localHead).toMatchObject(e_0_3.id)
418
+ expectEventArraysEqual(result.newEvents, [e_0_1, e_0_2, e_0_3])
419
+ })
420
+ })
421
+
422
+ describe('reject', () => {
423
+ it('should reject when new events are greater than pending events', () => {
424
+ const syncState = {
425
+ pending: [e_0_0, e_0_1],
426
+ rollbackTail: [],
427
+ upstreamHead: EventId.ROOT,
428
+ localHead: e_0_1.id,
429
+ }
430
+ const result = run({ syncState, payload: { _tag: 'local-push', newEvents: [e_0_1, e_0_2] } })
431
+
432
+ expectReject(result)
433
+ expect(result.expectedMinimumId).toMatchObject(e_0_2.id)
434
+ })
435
+ })
436
+ })
437
+ })
438
+ })
439
+ })
440
+
441
+ const expectEventArraysEqual = (
442
+ actual: ReadonlyArray<MutationEvent.EncodedWithMeta>,
443
+ expected: ReadonlyArray<MutationEvent.EncodedWithMeta>,
444
+ ) => {
445
+ expect(actual.length).toBe(expected.length)
446
+ actual.forEach((event, i) => {
447
+ expect(event.id).toEqual(expected[i]!.id)
448
+ expect(event.parentId).toEqual(expected[i]!.parentId)
449
+ expect(event.mutation).toEqual(expected[i]!.mutation)
450
+ expect(event.args).toEqual(expected[i]!.args)
451
+ })
452
+ }
453
+
454
+ function expectAdvance(result: SyncState.UpdateResult): asserts result is SyncState.UpdateResultAdvance {
455
+ expect(result._tag).toBe('advance')
456
+ }
457
+
458
+ function expectRebase(result: SyncState.UpdateResult): asserts result is SyncState.UpdateResultRebase {
459
+ expect(result._tag).toBe('rebase')
460
+ }
461
+
462
+ function expectReject(result: SyncState.UpdateResult): asserts result is SyncState.UpdateResultReject {
463
+ expect(result._tag).toBe('reject')
464
+ }