@livestore/common 0.3.0-dev.7 → 0.3.0-dev.9

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 (67) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/devtools/devtools-messages.d.ts +47 -47
  3. package/dist/index.d.ts +0 -4
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/leader-thread/LeaderSyncProcessor.d.ts +37 -0
  6. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -0
  7. package/dist/leader-thread/LeaderSyncProcessor.js +421 -0
  8. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -0
  9. package/dist/leader-thread/apply-mutation.js +1 -1
  10. package/dist/leader-thread/apply-mutation.js.map +1 -1
  11. package/dist/leader-thread/leader-sync-processor.d.ts +2 -2
  12. package/dist/leader-thread/leader-sync-processor.d.ts.map +1 -1
  13. package/dist/leader-thread/leader-sync-processor.js.map +1 -1
  14. package/dist/leader-thread/leader-worker-devtools.d.ts +1 -1
  15. package/dist/leader-thread/make-leader-thread-layer.d.ts +5 -6
  16. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  17. package/dist/leader-thread/make-leader-thread-layer.js +7 -4
  18. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  19. package/dist/leader-thread/pull-queue-set.d.ts.map +1 -1
  20. package/dist/leader-thread/types.d.ts +11 -5
  21. package/dist/leader-thread/types.d.ts.map +1 -1
  22. package/dist/leader-thread/types.js.map +1 -1
  23. package/dist/schema/EventId.test.d.ts +2 -0
  24. package/dist/schema/EventId.test.d.ts.map +1 -0
  25. package/dist/schema/EventId.test.js +11 -0
  26. package/dist/schema/EventId.test.js.map +1 -0
  27. package/dist/schema/MutationEvent.d.ts +6 -5
  28. package/dist/schema/MutationEvent.d.ts.map +1 -1
  29. package/dist/schema/MutationEvent.js +2 -2
  30. package/dist/schema/MutationEvent.js.map +1 -1
  31. package/dist/schema/MutationEvent.test.d.ts +2 -0
  32. package/dist/schema/MutationEvent.test.d.ts.map +1 -0
  33. package/dist/schema/MutationEvent.test.js +2 -0
  34. package/dist/schema/MutationEvent.test.js.map +1 -0
  35. package/dist/sync/ClientSessionSyncProcessor.d.ts +45 -0
  36. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -0
  37. package/dist/sync/ClientSessionSyncProcessor.js +133 -0
  38. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -0
  39. package/dist/sync/index.d.ts +1 -1
  40. package/dist/sync/index.d.ts.map +1 -1
  41. package/dist/sync/index.js +1 -1
  42. package/dist/sync/index.js.map +1 -1
  43. package/dist/sync/sync.d.ts +15 -5
  44. package/dist/sync/sync.d.ts.map +1 -1
  45. package/dist/sync/sync.js.map +1 -1
  46. package/dist/sync/syncstate.d.ts +39 -17
  47. package/dist/sync/syncstate.d.ts.map +1 -1
  48. package/dist/sync/syncstate.js +56 -12
  49. package/dist/sync/syncstate.js.map +1 -1
  50. package/dist/sync/syncstate.test.js +123 -63
  51. package/dist/sync/syncstate.test.js.map +1 -1
  52. package/dist/version.d.ts +1 -1
  53. package/dist/version.js +1 -1
  54. package/package.json +3 -3
  55. package/src/index.ts +0 -6
  56. package/src/leader-thread/{leader-sync-processor.ts → LeaderSyncProcessor.ts} +205 -212
  57. package/src/leader-thread/apply-mutation.ts +1 -1
  58. package/src/leader-thread/make-leader-thread-layer.ts +9 -8
  59. package/src/leader-thread/types.ts +12 -4
  60. package/src/schema/EventId.test.ts +12 -0
  61. package/src/schema/MutationEvent.ts +7 -3
  62. package/src/sync/{client-session-sync-processor.ts → ClientSessionSyncProcessor.ts} +6 -5
  63. package/src/sync/index.ts +1 -1
  64. package/src/sync/sync.ts +15 -4
  65. package/src/sync/syncstate.test.ts +123 -63
  66. package/src/sync/syncstate.ts +21 -19
  67. package/src/version.ts +1 -1
@@ -6,6 +6,7 @@ import type {
6
6
  Option,
7
7
  Queue,
8
8
  Scope,
9
+ Subscribable,
9
10
  SubscriptionRef,
10
11
  WebChannel,
11
12
  } from '@livestore/utils/effect'
@@ -90,7 +91,7 @@ export class LeaderThreadCtx extends Context.Tag('LeaderThreadCtx')<
90
91
  mutationEventSchema: MutationEvent.ForMutationDefRecord<any>
91
92
  // devtools: DevtoolsContext
92
93
  syncBackend: SyncBackend | undefined
93
- syncProcessor: SyncProcessor
94
+ syncProcessor: LeaderSyncProcessor
94
95
  connectedClientSessionPullQueues: PullQueueSet
95
96
  /** e.g. used for `store.__dev` APIs */
96
97
  extraIncomingMessagesQueue: Queue.Queue<Devtools.MessageToAppLeader>
@@ -107,17 +108,24 @@ export type PullQueueItem = {
107
108
  remaining: number
108
109
  }
109
110
 
110
- export interface SyncProcessor {
111
+ export interface LeaderSyncProcessor {
111
112
  push: (
112
113
  /** `batch` needs to follow the same rules as `batch` in `SyncBackend.push` */
113
114
  batch: ReadonlyArray<MutationEvent.EncodedWithMeta>,
114
- ) => Effect.Effect<void, UnexpectedError | InvalidPushError, HttpClient.HttpClient | LeaderThreadCtx>
115
+ options?: {
116
+ /**
117
+ * If true, the effect will only finish when the local push has been processed (i.e. succeeded or was rejected).
118
+ * @default false
119
+ */
120
+ waitForProcessing?: boolean
121
+ },
122
+ ) => Effect.Effect<void, InvalidPushError>
115
123
 
116
124
  pushPartial: (mutationEvent: MutationEvent.PartialAnyEncoded) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx>
117
125
  boot: (args: {
118
126
  dbReady: Deferred.Deferred<void>
119
127
  }) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx | Scope.Scope | HttpClient.HttpClient>
120
- syncState: Effect.Effect<SyncState.SyncState, UnexpectedError>
128
+ syncState: Subscribable.Subscribable<SyncState.SyncState>
121
129
  }
122
130
 
123
131
  export interface PullQueueSet {
@@ -0,0 +1,12 @@
1
+ import { Vitest } from '@livestore/utils/node-vitest'
2
+ import { expect } from 'vitest'
3
+
4
+ import { EventId } from './mod.js'
5
+
6
+ Vitest.describe('EventId', () => {
7
+ Vitest.test('nextPair', () => {
8
+ const e_0_0 = EventId.make({ global: 0, local: 0 })
9
+ expect(EventId.nextPair(e_0_0, false).id).toStrictEqual({ global: 1, local: 0 })
10
+ expect(EventId.nextPair(e_0_0, true).id).toStrictEqual({ global: 0, local: 1 })
11
+ })
12
+ })
@@ -2,6 +2,7 @@ import { memoizeByRef } from '@livestore/utils'
2
2
  import type { Deferred } from '@livestore/utils/effect'
3
3
  import { Schema } from '@livestore/utils/effect'
4
4
 
5
+ import type { InvalidPushError } from '../sync/sync.js'
5
6
  import * as EventId from './EventId.js'
6
7
  import type { MutationDef, MutationDefRecord } from './mutations.js'
7
8
  import type { LiveStoreSchema } from './schema.js'
@@ -127,7 +128,7 @@ export const makeMutationEventPartialSchema = <TSchema extends LiveStoreSchema>(
127
128
  args: def.schema,
128
129
  }),
129
130
  ),
130
- ).annotations({ title: 'MutationEventSchemaPartial' }) as any
131
+ ).annotations({ title: 'MutationEventPartial' }) as any
131
132
 
132
133
  export const makeMutationEventSchemaMemo = memoizeByRef(makeMutationEventSchema)
133
134
 
@@ -138,7 +139,10 @@ export class EncodedWithMeta extends Schema.Class<EncodedWithMeta>('MutationEven
138
139
  id: EventId.EventId,
139
140
  parentId: EventId.EventId,
140
141
  meta: Schema.optionalWith(
141
- Schema.Any as Schema.Schema<{ deferred?: Deferred.Deferred<void>; sessionChangeset?: Uint8Array }>,
142
+ Schema.Any as Schema.Schema<{
143
+ deferred?: Deferred.Deferred<void, InvalidPushError>
144
+ sessionChangeset?: Uint8Array
145
+ }>,
142
146
  { default: () => ({}) },
143
147
  ),
144
148
  }) {
@@ -156,7 +160,7 @@ export class EncodedWithMeta extends Schema.Class<EncodedWithMeta>('MutationEven
156
160
  rebase = (parentId: EventId.EventId, isLocal: boolean) =>
157
161
  new EncodedWithMeta({
158
162
  ...this,
159
- ...EventId.nextPair(this.id, isLocal),
163
+ ...EventId.nextPair(parentId, isLocal),
160
164
  })
161
165
 
162
166
  static fromGlobal = (mutationEvent: AnyEncodedGlobal) =>
@@ -7,8 +7,7 @@ import type { ClientSessionLeaderThreadProxy, UnexpectedError } from '../adapter
7
7
  import * as EventId from '../schema/EventId.js'
8
8
  import { type LiveStoreSchema } from '../schema/mod.js'
9
9
  import * as MutationEvent from '../schema/MutationEvent.js'
10
- import type { SyncState } from './syncstate.js'
11
- import { updateSyncState } from './syncstate.js'
10
+ import { SyncState, updateSyncState } from './syncstate.js'
12
11
 
13
12
  /**
14
13
  * Rebase behaviour:
@@ -42,19 +41,18 @@ export const makeClientSessionSyncProcessor = ({
42
41
  }
43
42
  rollback: (changeset: Uint8Array) => void
44
43
  refreshTables: (tables: Set<string>) => void
45
- // rebaseBehaviour: 'auto-rebase' | 'manual-rebase'
46
44
  span: otel.Span
47
45
  }): ClientSessionSyncProcessor => {
48
46
  const mutationEventSchema = MutationEvent.makeMutationEventSchemaMemo(schema)
49
47
 
50
48
  const syncStateRef = {
51
- current: {
49
+ current: new SyncState({
52
50
  localHead: initialLeaderHead,
53
51
  upstreamHead: initialLeaderHead,
54
52
  pending: [],
55
53
  // TODO init rollbackTail from leader to be ready for backend rebasing
56
54
  rollbackTail: [],
57
- } as SyncState,
55
+ }),
58
56
  }
59
57
 
60
58
  const isLocalEvent = (mutationEventEncoded: MutationEvent.EncodedWithMeta) => {
@@ -104,6 +102,7 @@ export const makeClientSessionSyncProcessor = ({
104
102
  mutationEvent.meta.sessionChangeset = res.sessionChangeset
105
103
  }
106
104
 
105
+ // console.debug('pushToLeader', encodedMutationEvents.length, ...encodedMutationEvents.map((_) => _.toJSON()))
107
106
  pushToLeader(encodedMutationEvents)
108
107
 
109
108
  return { writeTables }
@@ -155,6 +154,8 @@ export const makeClientSessionSyncProcessor = ({
155
154
  event.meta.sessionChangeset = undefined
156
155
  }
157
156
  }
157
+
158
+ pushToLeader(updateResult.newSyncState.pending)
158
159
  } else {
159
160
  span.addEvent('pull:advance', {
160
161
  payloadTag: payload._tag,
package/src/sync/index.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export * from './sync.js'
2
2
  export * from './validate-push-payload.js'
3
- export * from './client-session-sync-processor.js'
3
+ export * from './ClientSessionSyncProcessor.js'
package/src/sync/sync.ts CHANGED
@@ -1,12 +1,23 @@
1
- import type { Effect, HttpClient, Option, Stream, SubscriptionRef } from '@livestore/utils/effect'
1
+ import type { Effect, HttpClient, Option, Scope, Stream, SubscriptionRef } from '@livestore/utils/effect'
2
2
  import { Schema } from '@livestore/utils/effect'
3
3
 
4
+ import type { UnexpectedError } from '../adapter-types.js'
5
+ import type { InitialSyncOptions } from '../leader-thread/types.js'
4
6
  import * as EventId from '../schema/EventId.js'
5
7
  import type * as MutationEvent from '../schema/MutationEvent.js'
6
8
 
7
- export interface SyncBackendOptionsBase {
8
- type: string
9
- [key: string]: Schema.JsonValue
9
+ /**
10
+ * Those arguments can be used to implement multi-tenancy etc and are passed in from the store.
11
+ */
12
+ export type MakeBackendArgs = {
13
+ storeId: string
14
+ clientId: string
15
+ }
16
+
17
+ export type SyncOptions = {
18
+ makeBackend: (args: MakeBackendArgs) => Effect.Effect<SyncBackend<any>, UnexpectedError, Scope.Scope>
19
+ /** @default { _tag: 'Skip' } */
20
+ initialSyncOptions?: InitialSyncOptions
10
21
  }
11
22
 
12
23
  export type SyncBackend<TSyncMetadata = Schema.JsonValue> = {
@@ -58,12 +58,12 @@ describe('syncstate', () => {
58
58
  'upstream-rebase (trimRollbackUntil: $trimRollbackUntil)',
59
59
  ({ trimRollbackUntil }) => {
60
60
  it('should rollback until start', () => {
61
- const syncState = {
61
+ const syncState = new SyncState.SyncState({
62
62
  pending: [e_1_0],
63
63
  rollbackTail: [e_0_0, e_0_1],
64
64
  upstreamHead: EventId.ROOT,
65
65
  localHead: e_1_0.id,
66
- }
66
+ })
67
67
  const e_0_0_e_1_0 = e_0_0.rebase_(e_1_0.id)
68
68
  const e_0_1_e_1_1 = e_0_1.rebase_(e_0_0_e_1_0.id)
69
69
  const result = run({
@@ -83,19 +83,19 @@ describe('syncstate', () => {
83
83
  } else {
84
84
  expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_0_e_1_0, e_0_1_e_1_1])
85
85
  }
86
- expect(result.newSyncState.upstreamHead).toBe(e_0_1_e_1_1.id)
86
+ expect(result.newSyncState.upstreamHead).toMatchObject(e_0_1_e_1_1.id)
87
87
  expect(result.newSyncState.localHead).toMatchObject(e_1_0_e_2_0.id)
88
88
  expectEventArraysEqual(result.newEvents, [e_0_0_e_1_0, e_0_1_e_1_1])
89
89
  expectEventArraysEqual(result.eventsToRollback, [e_0_0, e_0_1, e_1_0])
90
90
  })
91
91
 
92
92
  it('should rollback only to specified point', () => {
93
- const syncState = {
93
+ const syncState = new SyncState.SyncState({
94
94
  pending: [e_1_0],
95
95
  rollbackTail: [e_0_0, e_0_1],
96
96
  upstreamHead: EventId.ROOT,
97
97
  localHead: e_1_0.id,
98
- }
98
+ })
99
99
  const e_0_1_e_1_0 = e_0_1.rebase_(e_0_0.id)
100
100
  const result = run({
101
101
  syncState,
@@ -114,14 +114,19 @@ describe('syncstate', () => {
114
114
  } else {
115
115
  expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_0, e_0_1_e_1_0])
116
116
  }
117
- expect(result.newSyncState.upstreamHead).toBe(e_0_1_e_1_0.id)
117
+ expect(result.newSyncState.upstreamHead).toMatchObject(e_0_1_e_1_0.id)
118
118
  expect(result.newSyncState.localHead).toMatchObject(e_1_0_e_2_0.id)
119
119
  expectEventArraysEqual(result.newEvents, [e_0_1_e_1_0])
120
120
  expectEventArraysEqual(result.eventsToRollback, [e_0_1, e_1_0])
121
121
  })
122
122
 
123
123
  it('should work for empty pending', () => {
124
- const syncState = { pending: [], rollbackTail: [e_0_0], upstreamHead: EventId.ROOT, localHead: e_0_0.id }
124
+ const syncState = new SyncState.SyncState({
125
+ pending: [],
126
+ rollbackTail: [e_0_0],
127
+ upstreamHead: EventId.ROOT,
128
+ localHead: e_0_0.id,
129
+ })
125
130
  const result = run({
126
131
  syncState,
127
132
  payload: { _tag: 'upstream-rebase', rollbackUntil: e_0_0.id, newEvents: [e_1_0] },
@@ -129,13 +134,18 @@ describe('syncstate', () => {
129
134
  expectRebase(result)
130
135
  expectEventArraysEqual(result.newSyncState.pending, [])
131
136
  expectEventArraysEqual(result.newSyncState.rollbackTail, [e_1_0])
132
- expect(result.newSyncState.upstreamHead).toBe(e_1_0.id)
137
+ expect(result.newSyncState.upstreamHead).toMatchObject(e_1_0.id)
133
138
  expect(result.newSyncState.localHead).toMatchObject(e_1_0.id)
134
- expect(result.newEvents).toEqual([e_1_0])
139
+ expect(result.newEvents).toStrictEqual([e_1_0])
135
140
  })
136
141
 
137
142
  it('should fail for empty rollback tail', () => {
138
- const syncState = { pending: [], rollbackTail: [], upstreamHead: EventId.ROOT, localHead: e_0_0.id }
143
+ const syncState = new SyncState.SyncState({
144
+ pending: [],
145
+ rollbackTail: [],
146
+ upstreamHead: EventId.ROOT,
147
+ localHead: e_0_0.id,
148
+ })
139
149
  expect(() =>
140
150
  run({
141
151
  syncState,
@@ -145,7 +155,12 @@ describe('syncstate', () => {
145
155
  })
146
156
 
147
157
  it('should work for empty incoming', () => {
148
- const syncState = { pending: [], rollbackTail: [e_0_0], upstreamHead: EventId.ROOT, localHead: e_0_0.id }
158
+ const syncState = new SyncState.SyncState({
159
+ pending: [],
160
+ rollbackTail: [e_0_0],
161
+ upstreamHead: EventId.ROOT,
162
+ localHead: e_0_0.id,
163
+ })
149
164
  const result = run({
150
165
  syncState,
151
166
  payload: { _tag: 'upstream-rebase', rollbackUntil: e_0_0.id, newEvents: [] },
@@ -153,67 +168,92 @@ describe('syncstate', () => {
153
168
  expectRebase(result)
154
169
  expectEventArraysEqual(result.newSyncState.pending, [])
155
170
  expectEventArraysEqual(result.newSyncState.rollbackTail, [])
156
- expect(result.newSyncState.upstreamHead).toBe(EventId.ROOT)
171
+ expect(result.newSyncState.upstreamHead).toMatchObject(EventId.ROOT)
157
172
  expect(result.newSyncState.localHead).toMatchObject(EventId.ROOT)
158
- expect(result.newEvents).toEqual([])
173
+ expect(result.newEvents).toStrictEqual([])
159
174
  })
160
175
  },
161
176
  )
162
177
 
163
178
  describe('upstream-advance: advance', () => {
164
179
  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 }
180
+ const syncState = new SyncState.SyncState({
181
+ pending: [e_0_0],
182
+ rollbackTail: [],
183
+ upstreamHead: EventId.ROOT,
184
+ localHead: e_0_0.id,
185
+ })
166
186
  expect(() => run({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e_0_1, e_0_0] } })).toThrow()
167
187
  })
168
188
 
169
189
  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 }
190
+ const syncState = new SyncState.SyncState({
191
+ pending: [e_0_0],
192
+ rollbackTail: [],
193
+ upstreamHead: EventId.ROOT,
194
+ localHead: e_0_0.id,
195
+ })
171
196
  expect(() => run({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e_1_0, e_0_0] } })).toThrow()
172
197
  })
173
198
 
174
199
  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 }
200
+ const syncState = new SyncState.SyncState({
201
+ pending: [e_0_0],
202
+ rollbackTail: [],
203
+ upstreamHead: EventId.ROOT,
204
+ localHead: e_0_0.id,
205
+ })
176
206
  const result = run({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e_0_0] } })
177
207
 
178
208
  expectAdvance(result)
179
209
  expectEventArraysEqual(result.newSyncState.pending, [])
180
210
  expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_0])
181
- expect(result.newSyncState.upstreamHead).toBe(e_0_0.id)
211
+ expect(result.newSyncState.upstreamHead).toMatchObject(e_0_0.id)
182
212
  expect(result.newSyncState.localHead).toMatchObject(e_0_0.id)
183
- expect(result.newEvents).toEqual([])
213
+ expect(result.newEvents).toStrictEqual([])
184
214
  })
185
215
 
186
216
  it('should acknowledge partial pending event when receiving matching event', () => {
187
- const syncState = {
217
+ const syncState = new SyncState.SyncState({
188
218
  pending: [e_0_0, e_1_0],
189
219
  rollbackTail: [],
190
220
  upstreamHead: EventId.ROOT,
191
221
  localHead: e_1_0.id,
192
- }
222
+ })
193
223
  const result = run({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e_0_0] } })
194
224
 
195
225
  expectAdvance(result)
196
226
  expectEventArraysEqual(result.newSyncState.pending, [e_1_0])
197
227
  expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_0])
198
- expect(result.newSyncState.upstreamHead).toBe(e_0_0.id)
228
+ expect(result.newSyncState.upstreamHead).toMatchObject(e_0_0.id)
199
229
  expect(result.newSyncState.localHead).toMatchObject(e_1_0.id)
200
- expect(result.newEvents).toEqual([])
230
+ expect(result.newEvents).toStrictEqual([])
201
231
  })
202
232
 
203
233
  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 }
234
+ const syncState = new SyncState.SyncState({
235
+ pending: [e_0_0],
236
+ rollbackTail: [],
237
+ upstreamHead: EventId.ROOT,
238
+ localHead: e_0_0.id,
239
+ })
205
240
  const result = run({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e_0_0, e_0_1] } })
206
241
 
207
242
  expectAdvance(result)
208
243
  expectEventArraysEqual(result.newSyncState.pending, [])
209
244
  expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_0, e_0_1])
210
- expect(result.newSyncState.upstreamHead).toBe(e_0_1.id)
245
+ expect(result.newSyncState.upstreamHead).toMatchObject(e_0_1.id)
211
246
  expect(result.newSyncState.localHead).toMatchObject(e_0_1.id)
212
- expect(result.newEvents).toEqual([e_0_1])
247
+ expect(result.newEvents).toStrictEqual([e_0_1])
213
248
  })
214
249
 
215
250
  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 }
251
+ const syncState = new SyncState.SyncState({
252
+ pending: [e_0_1],
253
+ rollbackTail: [],
254
+ upstreamHead: e_0_0.id,
255
+ localHead: e_0_1.id,
256
+ })
217
257
  const result = run({
218
258
  syncState,
219
259
  payload: { _tag: 'upstream-advance', newEvents: [e_0_1, e_0_2, e_0_3, e_1_0, e_1_1] },
@@ -222,18 +262,18 @@ describe('syncstate', () => {
222
262
  expectAdvance(result)
223
263
  expectEventArraysEqual(result.newSyncState.pending, [])
224
264
  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)
265
+ expect(result.newSyncState.upstreamHead).toMatchObject(e_1_1.id)
226
266
  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])
267
+ expect(result.newEvents).toStrictEqual([e_0_2, e_0_3, e_1_0, e_1_1])
228
268
  })
229
269
 
230
270
  it('should ignore local events (incoming is subset of pending)', () => {
231
- const syncState = {
271
+ const syncState = new SyncState.SyncState({
232
272
  pending: [e_r_1, e_0_0],
233
273
  rollbackTail: [],
234
274
  upstreamHead: EventId.ROOT,
235
275
  localHead: e_0_0.id,
236
- }
276
+ })
237
277
  const result = run({
238
278
  syncState,
239
279
  payload: { _tag: 'upstream-advance', newEvents: [e_0_0] },
@@ -242,18 +282,18 @@ describe('syncstate', () => {
242
282
  expectAdvance(result)
243
283
  expectEventArraysEqual(result.newSyncState.pending, [])
244
284
  expectEventArraysEqual(result.newSyncState.rollbackTail, [e_r_1, e_0_0])
245
- expect(result.newSyncState.upstreamHead).toBe(e_0_0.id)
285
+ expect(result.newSyncState.upstreamHead).toMatchObject(e_0_0.id)
246
286
  expect(result.newSyncState.localHead).toMatchObject(e_0_0.id)
247
- expect(result.newEvents).toEqual([])
287
+ expect(result.newEvents).toStrictEqual([])
248
288
  })
249
289
 
250
290
  it('should ignore local events (incoming is subset of pending case 2)', () => {
251
- const syncState = {
291
+ const syncState = new SyncState.SyncState({
252
292
  pending: [e_r_1, e_0_0, e_1_0],
253
293
  rollbackTail: [],
254
294
  upstreamHead: EventId.ROOT,
255
295
  localHead: e_0_0.id,
256
- }
296
+ })
257
297
  const result = run({
258
298
  syncState,
259
299
  payload: { _tag: 'upstream-advance', newEvents: [e_0_0] },
@@ -262,18 +302,18 @@ describe('syncstate', () => {
262
302
  expectAdvance(result)
263
303
  expectEventArraysEqual(result.newSyncState.pending, [e_1_0])
264
304
  expectEventArraysEqual(result.newSyncState.rollbackTail, [e_r_1, e_0_0])
265
- expect(result.newSyncState.upstreamHead).toBe(e_0_0.id)
305
+ expect(result.newSyncState.upstreamHead).toMatchObject(e_0_0.id)
266
306
  expect(result.newSyncState.localHead).toMatchObject(e_1_0.id)
267
- expect(result.newEvents).toEqual([])
307
+ expect(result.newEvents).toStrictEqual([])
268
308
  })
269
309
 
270
310
  it('should ignore local events (incoming goes beyond pending)', () => {
271
- const syncState = {
311
+ const syncState = new SyncState.SyncState({
272
312
  pending: [e_r_1, e_0_0, e_0_1],
273
313
  rollbackTail: [],
274
314
  upstreamHead: EventId.ROOT,
275
315
  localHead: e_0_1.id,
276
- }
316
+ })
277
317
  const result = run({
278
318
  syncState,
279
319
  payload: { _tag: 'upstream-advance', newEvents: [e_0_0, e_1_0] },
@@ -283,15 +323,20 @@ describe('syncstate', () => {
283
323
  expectAdvance(result)
284
324
  expectEventArraysEqual(result.newSyncState.pending, [])
285
325
  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)
326
+ expect(result.newSyncState.upstreamHead).toMatchObject(e_1_0.id)
287
327
  expect(result.newSyncState.localHead).toMatchObject(e_1_0.id)
288
- expect(result.newEvents).toEqual([e_1_0])
328
+ expect(result.newEvents).toStrictEqual([e_1_0])
289
329
  })
290
330
  })
291
331
 
292
332
  describe('upstream-advance: rebase', () => {
293
333
  it('should rebase single local event to end', () => {
294
- const syncState = { pending: [e_0_0], rollbackTail: [], upstreamHead: EventId.ROOT, localHead: e_0_0.id }
334
+ const syncState = new SyncState.SyncState({
335
+ pending: [e_0_0],
336
+ rollbackTail: [],
337
+ upstreamHead: EventId.ROOT,
338
+ localHead: e_0_0.id,
339
+ })
295
340
  const result = run({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e_0_1] } })
296
341
 
297
342
  const e_0_0_e_0_2 = e_0_0.rebase_(e_0_1.id)
@@ -299,7 +344,7 @@ describe('syncstate', () => {
299
344
  expectRebase(result)
300
345
  expectEventArraysEqual(result.newSyncState.pending, [e_0_0_e_0_2])
301
346
  expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_1])
302
- expect(result.newSyncState.upstreamHead).toBe(e_0_1.id)
347
+ expect(result.newSyncState.upstreamHead).toMatchObject(e_0_1.id)
303
348
  expect(result.newSyncState.localHead).toMatchObject(e_0_0_e_0_2.id)
304
349
  expectEventArraysEqual(result.eventsToRollback, [e_0_0])
305
350
  expectEventArraysEqual(result.newEvents, [e_0_1, e_0_0_e_0_2])
@@ -307,7 +352,12 @@ describe('syncstate', () => {
307
352
 
308
353
  it('should rebase different event with same id (no rollback tail)', () => {
309
354
  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 }
355
+ const syncState = new SyncState.SyncState({
356
+ pending: [e_0_0_b],
357
+ rollbackTail: [],
358
+ upstreamHead: EventId.ROOT,
359
+ localHead: e_0_0_b.id,
360
+ })
311
361
  const result = run({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e_0_0] } })
312
362
 
313
363
  const e_0_0_e_1_0 = e_0_0_b.rebase_(e_0_0.id)
@@ -317,18 +367,18 @@ describe('syncstate', () => {
317
367
  expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_0])
318
368
  expectEventArraysEqual(result.newEvents, [e_0_0, e_0_0_e_1_0])
319
369
  expectEventArraysEqual(result.eventsToRollback, [e_0_0_b])
320
- expect(result.newSyncState.upstreamHead).toBe(e_0_0.id)
370
+ expect(result.newSyncState.upstreamHead).toMatchObject(e_0_0.id)
321
371
  expect(result.newSyncState.localHead).toMatchObject(e_0_0_e_1_0.id)
322
372
  })
323
373
 
324
374
  it('should rebase different event with same id', () => {
325
375
  const e_1_0_b = new TestEvent({ global: 1, local: 0 }, e_0_0.id, '1_0_b', false)
326
- const syncState = {
376
+ const syncState = new SyncState.SyncState({
327
377
  pending: [e_1_0_b],
328
378
  rollbackTail: [e_0_0, e_0_1],
329
379
  upstreamHead: EventId.ROOT,
330
380
  localHead: e_1_0_b.id,
331
- }
381
+ })
332
382
  const result = run({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e_1_0] } })
333
383
  const e_1_0_e_2_0 = e_1_0_b.rebase_(e_1_0.id)
334
384
 
@@ -337,12 +387,17 @@ describe('syncstate', () => {
337
387
  expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_0, e_0_1, e_1_0])
338
388
  expectEventArraysEqual(result.newEvents, [e_1_0, e_1_0_e_2_0])
339
389
  expectEventArraysEqual(result.eventsToRollback, [e_0_0, e_0_1, e_1_0_b])
340
- expect(result.newSyncState.upstreamHead).toBe(e_1_0.id)
390
+ expect(result.newSyncState.upstreamHead).toMatchObject(e_1_0.id)
341
391
  expect(result.newSyncState.localHead).toMatchObject(e_1_0_e_2_0.id)
342
392
  })
343
393
 
344
394
  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 }
395
+ const syncState = new SyncState.SyncState({
396
+ pending: [e_0_0],
397
+ rollbackTail: [],
398
+ upstreamHead: EventId.ROOT,
399
+ localHead: e_0_0.id,
400
+ })
346
401
  const result = run({
347
402
  syncState,
348
403
  payload: { _tag: 'upstream-advance', newEvents: [e_0_1, e_0_2, e_0_3, e_1_0] },
@@ -353,17 +408,17 @@ describe('syncstate', () => {
353
408
  expectRebase(result)
354
409
  expectEventArraysEqual(result.newSyncState.pending, [e_0_0_e_2_0])
355
410
  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)
411
+ expect(result.newSyncState.upstreamHead).toMatchObject(e_1_0.id)
357
412
  expect(result.newSyncState.localHead).toMatchObject(e_0_0_e_2_0.id)
358
413
  })
359
414
 
360
415
  it('should only rebase divergent events when first event matches', () => {
361
- const syncState = {
416
+ const syncState = new SyncState.SyncState({
362
417
  pending: [e_0_0, e_0_1],
363
418
  rollbackTail: [],
364
419
  upstreamHead: EventId.ROOT,
365
420
  localHead: e_0_0.id,
366
- }
421
+ })
367
422
  const result = run({
368
423
  syncState,
369
424
  payload: { _tag: 'upstream-advance', newEvents: [e_0_0, e_0_2, e_0_3, e_1_0] },
@@ -376,17 +431,17 @@ describe('syncstate', () => {
376
431
  expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_0, e_0_2, e_0_3, e_1_0])
377
432
  expectEventArraysEqual(result.eventsToRollback, [e_0_1])
378
433
  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)
434
+ expect(result.newSyncState.upstreamHead).toMatchObject(e_1_0.id)
380
435
  expect(result.newSyncState.localHead).toMatchObject(e_0_1_e_1_1.id)
381
436
  })
382
437
 
383
438
  it('should rebase all local events when incoming chain starts differently', () => {
384
- const syncState = {
439
+ const syncState = new SyncState.SyncState({
385
440
  pending: [e_0_0, e_0_1],
386
441
  rollbackTail: [],
387
442
  upstreamHead: EventId.ROOT,
388
443
  localHead: e_0_1.id,
389
- }
444
+ })
390
445
  const result = run({
391
446
  syncState,
392
447
  payload: { _tag: 'upstream-advance', newEvents: [e_0_1, e_0_2, e_0_3, e_1_0] },
@@ -400,20 +455,25 @@ describe('syncstate', () => {
400
455
  expectEventArraysEqual(result.newSyncState.rollbackTail, [e_0_1, e_0_2, e_0_3, e_1_0])
401
456
  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
457
  expectEventArraysEqual(result.eventsToRollback, [e_0_0, e_0_1])
403
- expect(result.newSyncState.upstreamHead).toBe(e_1_0.id)
458
+ expect(result.newSyncState.upstreamHead).toMatchObject(e_1_0.id)
404
459
  expect(result.newSyncState.localHead).toMatchObject(e_0_1_e_1_2.id)
405
460
  })
406
461
 
407
462
  describe('local-push', () => {
408
463
  describe('advance', () => {
409
464
  it('should advance with new events', () => {
410
- const syncState = { pending: [e_0_0], rollbackTail: [], upstreamHead: EventId.ROOT, localHead: e_0_0.id }
465
+ const syncState = new SyncState.SyncState({
466
+ pending: [e_0_0],
467
+ rollbackTail: [],
468
+ upstreamHead: EventId.ROOT,
469
+ localHead: e_0_0.id,
470
+ })
411
471
  const result = run({ syncState, payload: { _tag: 'local-push', newEvents: [e_0_1, e_0_2, e_0_3] } })
412
472
 
413
473
  expectAdvance(result)
414
474
  expectEventArraysEqual(result.newSyncState.pending, [e_0_0, e_0_1, e_0_2, e_0_3])
415
475
  expectEventArraysEqual(result.newSyncState.rollbackTail, [])
416
- expect(result.newSyncState.upstreamHead).toBe(EventId.ROOT)
476
+ expect(result.newSyncState.upstreamHead).toMatchObject(EventId.ROOT)
417
477
  expect(result.newSyncState.localHead).toMatchObject(e_0_3.id)
418
478
  expectEventArraysEqual(result.newEvents, [e_0_1, e_0_2, e_0_3])
419
479
  })
@@ -421,12 +481,12 @@ describe('syncstate', () => {
421
481
 
422
482
  describe('reject', () => {
423
483
  it('should reject when new events are greater than pending events', () => {
424
- const syncState = {
484
+ const syncState = new SyncState.SyncState({
425
485
  pending: [e_0_0, e_0_1],
426
486
  rollbackTail: [],
427
487
  upstreamHead: EventId.ROOT,
428
488
  localHead: e_0_1.id,
429
- }
489
+ })
430
490
  const result = run({ syncState, payload: { _tag: 'local-push', newEvents: [e_0_1, e_0_2] } })
431
491
 
432
492
  expectReject(result)
@@ -444,10 +504,10 @@ const expectEventArraysEqual = (
444
504
  ) => {
445
505
  expect(actual.length).toBe(expected.length)
446
506
  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)
507
+ expect(event.id).toStrictEqual(expected[i]!.id)
508
+ expect(event.parentId).toStrictEqual(expected[i]!.parentId)
509
+ expect(event.mutation).toStrictEqual(expected[i]!.mutation)
510
+ expect(event.args).toStrictEqual(expected[i]!.args)
451
511
  })
452
512
  }
453
513