@livestore/common 0.0.0-snapshot-aed277ba0960f72b8d464508961ab4aec1881230 → 0.0.0-snapshot-7bcbc24bb8873481e482d982636957f0c1f791f6

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 (139) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/__tests__/fixture.d.ts +6 -6
  3. package/dist/adapter-types.d.ts +39 -16
  4. package/dist/adapter-types.d.ts.map +1 -1
  5. package/dist/adapter-types.js +12 -0
  6. package/dist/adapter-types.js.map +1 -1
  7. package/dist/derived-mutations.js +3 -3
  8. package/dist/derived-mutations.js.map +1 -1
  9. package/dist/devtools/devtools-messages-client-session.d.ts +23 -23
  10. package/dist/devtools/devtools-messages-common.d.ts +19 -6
  11. package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
  12. package/dist/devtools/devtools-messages-common.js +14 -0
  13. package/dist/devtools/devtools-messages-common.js.map +1 -1
  14. package/dist/devtools/devtools-messages-leader.d.ts +94 -75
  15. package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
  16. package/dist/devtools/devtools-messages-leader.js +18 -17
  17. package/dist/devtools/devtools-messages-leader.js.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
  23. package/dist/leader-thread/LeaderSyncProcessor.js +50 -35
  24. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  25. package/dist/leader-thread/apply-mutation.d.ts.map +1 -1
  26. package/dist/leader-thread/apply-mutation.js +8 -6
  27. package/dist/leader-thread/apply-mutation.js.map +1 -1
  28. package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
  29. package/dist/leader-thread/leader-worker-devtools.js +26 -18
  30. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  31. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  32. package/dist/leader-thread/make-leader-thread-layer.js +23 -6
  33. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  34. package/dist/leader-thread/mutationlog.d.ts +1 -1
  35. package/dist/leader-thread/mutationlog.d.ts.map +1 -1
  36. package/dist/leader-thread/mutationlog.js +7 -5
  37. package/dist/leader-thread/mutationlog.js.map +1 -1
  38. package/dist/leader-thread/pull-queue-set.d.ts.map +1 -1
  39. package/dist/leader-thread/recreate-db.d.ts +4 -2
  40. package/dist/leader-thread/recreate-db.d.ts.map +1 -1
  41. package/dist/leader-thread/recreate-db.js +12 -7
  42. package/dist/leader-thread/recreate-db.js.map +1 -1
  43. package/dist/leader-thread/types.d.ts +19 -7
  44. package/dist/leader-thread/types.d.ts.map +1 -1
  45. package/dist/leader-thread/types.js +1 -1
  46. package/dist/leader-thread/types.js.map +1 -1
  47. package/dist/rehydrate-from-mutationlog.d.ts.map +1 -1
  48. package/dist/rehydrate-from-mutationlog.js +8 -6
  49. package/dist/rehydrate-from-mutationlog.js.map +1 -1
  50. package/dist/schema/EventId.d.ts +10 -9
  51. package/dist/schema/EventId.d.ts.map +1 -1
  52. package/dist/schema/EventId.js +14 -11
  53. package/dist/schema/EventId.js.map +1 -1
  54. package/dist/schema/EventId.test.js +3 -3
  55. package/dist/schema/EventId.test.js.map +1 -1
  56. package/dist/schema/MutationEvent.d.ts +37 -12
  57. package/dist/schema/MutationEvent.d.ts.map +1 -1
  58. package/dist/schema/MutationEvent.js +20 -4
  59. package/dist/schema/MutationEvent.js.map +1 -1
  60. package/dist/schema/mutations.d.ts +4 -8
  61. package/dist/schema/mutations.d.ts.map +1 -1
  62. package/dist/schema/mutations.js +2 -2
  63. package/dist/schema/mutations.js.map +1 -1
  64. package/dist/schema/system-tables.d.ts +38 -20
  65. package/dist/schema/system-tables.d.ts.map +1 -1
  66. package/dist/schema/system-tables.js +9 -6
  67. package/dist/schema/system-tables.js.map +1 -1
  68. package/dist/schema/table-def.d.ts +7 -7
  69. package/dist/schema/table-def.d.ts.map +1 -1
  70. package/dist/schema/table-def.js +2 -2
  71. package/dist/schema/table-def.js.map +1 -1
  72. package/dist/schema-management/migrations.d.ts +2 -2
  73. package/dist/schema-management/migrations.d.ts.map +1 -1
  74. package/dist/schema-management/migrations.js +6 -1
  75. package/dist/schema-management/migrations.js.map +1 -1
  76. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  77. package/dist/sync/ClientSessionSyncProcessor.js +20 -5
  78. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  79. package/dist/sync/next/facts.js +1 -1
  80. package/dist/sync/next/facts.js.map +1 -1
  81. package/dist/sync/next/history-dag-common.d.ts +2 -0
  82. package/dist/sync/next/history-dag-common.d.ts.map +1 -1
  83. package/dist/sync/next/history-dag-common.js +3 -1
  84. package/dist/sync/next/history-dag-common.js.map +1 -1
  85. package/dist/sync/next/history-dag.d.ts.map +1 -1
  86. package/dist/sync/next/history-dag.js +1 -1
  87. package/dist/sync/next/history-dag.js.map +1 -1
  88. package/dist/sync/next/rebase-events.d.ts +3 -1
  89. package/dist/sync/next/rebase-events.d.ts.map +1 -1
  90. package/dist/sync/next/rebase-events.js +5 -3
  91. package/dist/sync/next/rebase-events.js.map +1 -1
  92. package/dist/sync/next/test/compact-events.calculator.test.js +12 -12
  93. package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
  94. package/dist/sync/next/test/compact-events.test.js +30 -30
  95. package/dist/sync/next/test/compact-events.test.js.map +1 -1
  96. package/dist/sync/next/test/mutation-fixtures.d.ts +1 -1
  97. package/dist/sync/next/test/mutation-fixtures.d.ts.map +1 -1
  98. package/dist/sync/next/test/mutation-fixtures.js +9 -7
  99. package/dist/sync/next/test/mutation-fixtures.js.map +1 -1
  100. package/dist/sync/sync.d.ts +2 -2
  101. package/dist/sync/syncstate.d.ts +9 -9
  102. package/dist/sync/syncstate.js +6 -6
  103. package/dist/sync/syncstate.js.map +1 -1
  104. package/dist/sync/syncstate.test.js +18 -16
  105. package/dist/sync/syncstate.test.js.map +1 -1
  106. package/dist/version.d.ts +1 -1
  107. package/dist/version.js +1 -1
  108. package/package.json +3 -3
  109. package/src/adapter-types.ts +33 -15
  110. package/src/derived-mutations.ts +3 -3
  111. package/src/devtools/devtools-messages-common.ts +34 -0
  112. package/src/devtools/devtools-messages-leader.ts +28 -18
  113. package/src/index.ts +1 -1
  114. package/src/leader-thread/LeaderSyncProcessor.ts +59 -38
  115. package/src/leader-thread/apply-mutation.ts +15 -5
  116. package/src/leader-thread/leader-worker-devtools.ts +33 -19
  117. package/src/leader-thread/make-leader-thread-layer.ts +28 -8
  118. package/src/leader-thread/mutationlog.ts +8 -6
  119. package/src/leader-thread/recreate-db.ts +18 -9
  120. package/src/leader-thread/types.ts +20 -5
  121. package/src/rehydrate-from-mutationlog.ts +8 -6
  122. package/src/schema/EventId.test.ts +3 -3
  123. package/src/schema/EventId.ts +20 -16
  124. package/src/schema/MutationEvent.ts +31 -6
  125. package/src/schema/mutations.ts +6 -19
  126. package/src/schema/system-tables.ts +9 -6
  127. package/src/schema/table-def.ts +8 -8
  128. package/src/schema-management/migrations.ts +9 -5
  129. package/src/sync/ClientSessionSyncProcessor.ts +25 -6
  130. package/src/sync/next/facts.ts +1 -1
  131. package/src/sync/next/history-dag-common.ts +5 -1
  132. package/src/sync/next/history-dag.ts +1 -1
  133. package/src/sync/next/rebase-events.ts +8 -2
  134. package/src/sync/next/test/compact-events.calculator.test.ts +12 -12
  135. package/src/sync/next/test/compact-events.test.ts +30 -30
  136. package/src/sync/next/test/mutation-fixtures.ts +10 -6
  137. package/src/sync/syncstate.test.ts +19 -17
  138. package/src/sync/syncstate.ts +6 -6
  139. package/src/version.ts +1 -1
@@ -16,6 +16,7 @@ import type {
16
16
  Devtools,
17
17
  InvalidPushError,
18
18
  MakeSqliteDb,
19
+ MigrationsReport,
19
20
  PersistenceInfo,
20
21
  SqliteDb,
21
22
  SyncBackend,
@@ -31,7 +32,7 @@ export const InitialSyncOptionsSkip = Schema.TaggedStruct('Skip', {})
31
32
  export type InitialSyncOptionsSkip = typeof InitialSyncOptionsSkip.Type
32
33
 
33
34
  export const InitialSyncOptionsBlocking = Schema.TaggedStruct('Blocking', {
34
- timeout: Schema.DurationFromMillis,
35
+ timeout: Schema.Union(Schema.DurationFromMillis, Schema.Number),
35
36
  })
36
37
 
37
38
  export type InitialSyncOptionsBlocking = typeof InitialSyncOptionsBlocking.Type
@@ -70,8 +71,10 @@ export type DevtoolsOptions =
70
71
  export type DevtoolsContext =
71
72
  | {
72
73
  enabled: true
73
- syncBackendPullLatch: Effect.Latch
74
- syncBackendPushLatch: Effect.Latch
74
+ // syncBackendPullLatch: Effect.Latch
75
+ // syncBackendPushLatch: Effect.Latch
76
+ syncBackendLatch: Effect.Latch
77
+ syncBackendLatchState: SubscriptionRef.SubscriptionRef<{ latchClosed: boolean }>
75
78
  }
76
79
  | {
77
80
  enabled: false
@@ -95,6 +98,10 @@ export class LeaderThreadCtx extends Context.Tag('LeaderThreadCtx')<
95
98
  syncBackend: SyncBackend | undefined
96
99
  syncProcessor: LeaderSyncProcessor
97
100
  connectedClientSessionPullQueues: PullQueueSet
101
+ initialState: {
102
+ leaderHead: EventId.EventId
103
+ migrationsReport: MigrationsReport
104
+ }
98
105
  /**
99
106
  * e.g. used for `store._dev` APIs
100
107
  *
@@ -127,10 +134,18 @@ export interface LeaderSyncProcessor {
127
134
  },
128
135
  ) => Effect.Effect<void, InvalidPushError>
129
136
 
130
- pushPartial: (mutationEvent: MutationEvent.PartialAnyEncoded) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx>
137
+ pushPartial: (args: {
138
+ mutationEvent: MutationEvent.PartialAnyEncoded
139
+ clientId: string
140
+ sessionId: string | undefined
141
+ }) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx>
131
142
  boot: (args: {
132
143
  dbReady: Deferred.Deferred<void>
133
- }) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx | Scope.Scope | HttpClient.HttpClient>
144
+ }) => Effect.Effect<
145
+ { initialLeaderHead: EventId.EventId },
146
+ UnexpectedError,
147
+ LeaderThreadCtx | Scope.Scope | HttpClient.HttpClient
148
+ >
134
149
  syncState: Subscribable.Subscribable<SyncState.SyncState>
135
150
  }
136
151
 
@@ -60,10 +60,12 @@ This likely means the schema has changed in an incompatible way.
60
60
  )
61
61
 
62
62
  const mutationEventEncoded = {
63
- id: { global: row.idGlobal, local: row.idLocal },
64
- parentId: { global: row.parentIdGlobal, local: row.parentIdLocal },
63
+ id: { global: row.idGlobal, client: row.idClient },
64
+ parentId: { global: row.parentIdGlobal, client: row.parentIdClient },
65
65
  mutation: row.mutation,
66
66
  args,
67
+ clientId: row.clientId,
68
+ sessionId: row.sessionId ?? undefined,
67
69
  } satisfies MutationEvent.AnyEncoded
68
70
 
69
71
  yield* applyMutation(mutationEventEncoded, { skipMutationLog: true })
@@ -73,8 +75,8 @@ This likely means the schema has changed in an incompatible way.
73
75
 
74
76
  const stmt = logDb.prepare(sql`\
75
77
  SELECT * FROM ${MUTATION_LOG_META_TABLE}
76
- WHERE idGlobal > $idGlobal OR (idGlobal = $idGlobal AND idLocal > $idLocal)
77
- ORDER BY idGlobal ASC, idLocal ASC
78
+ WHERE idGlobal > $idGlobal OR (idGlobal = $idGlobal AND idClient > $idClient)
79
+ ORDER BY idGlobal ASC, idClient ASC
78
80
  LIMIT ${CHUNK_SIZE}
79
81
  `)
80
82
 
@@ -88,14 +90,14 @@ LIMIT ${CHUNK_SIZE}
88
90
 
89
91
  const lastId = Chunk.isChunk(item)
90
92
  ? Chunk.last(item).pipe(
91
- Option.map((_) => ({ global: _.idGlobal, local: _.idLocal })),
93
+ Option.map((_) => ({ global: _.idGlobal, client: _.idClient })),
92
94
  Option.getOrElse(() => EventId.ROOT),
93
95
  )
94
96
  : EventId.ROOT
95
97
  const nextItem = Chunk.fromIterable(
96
98
  stmt.select<MutationLogMetaRow>({
97
99
  $idGlobal: lastId?.global,
98
- $idLocal: lastId?.local,
100
+ $idClient: lastId?.client,
99
101
  } as any as PreparedBindValues),
100
102
  )
101
103
  const prevItem = Chunk.isChunk(item) ? item : Chunk.empty()
@@ -5,8 +5,8 @@ import { EventId } from './mod.js'
5
5
 
6
6
  Vitest.describe('EventId', () => {
7
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 })
8
+ const e_0_0 = EventId.make({ global: 0, client: 0 })
9
+ expect(EventId.nextPair(e_0_0, false).id).toStrictEqual({ global: 1, client: 0 })
10
+ expect(EventId.nextPair(e_0_0, true).id).toStrictEqual({ global: 0, client: 1 })
11
11
  })
12
12
  })
@@ -1,26 +1,26 @@
1
1
  import { Brand, Schema } from '@livestore/utils/effect'
2
2
 
3
- export type LocalEventId = Brand.Branded<number, 'LocalEventId'>
4
- export const localEventId = Brand.nominal<LocalEventId>()
5
- export const LocalEventId = Schema.fromBrand(localEventId)(Schema.Int)
3
+ export type ClientEventId = Brand.Branded<number, 'ClientEventId'>
4
+ export const localEventId = Brand.nominal<ClientEventId>()
5
+ export const ClientEventId = Schema.fromBrand(localEventId)(Schema.Int)
6
6
 
7
7
  export type GlobalEventId = Brand.Branded<number, 'GlobalEventId'>
8
8
  export const globalEventId = Brand.nominal<GlobalEventId>()
9
9
  export const GlobalEventId = Schema.fromBrand(globalEventId)(Schema.Int)
10
10
 
11
- export const localDefault = 0 as any as LocalEventId
11
+ export const clientDefault = 0 as any as ClientEventId
12
12
 
13
13
  /**
14
14
  * LiveStore event id value consisting of a globally unique event sequence number
15
- * and a local sequence number.
15
+ * and a client sequence number.
16
16
  *
17
- * The local sequence number is only used for localOnly mutations and starts from 0 for each global sequence number.
17
+ * The client sequence number is only used for clientOnly mutations and starts from 0 for each global sequence number.
18
18
  */
19
- export type EventId = { global: GlobalEventId; local: LocalEventId }
19
+ export type EventId = { global: GlobalEventId; client: ClientEventId }
20
20
 
21
21
  export const EventId = Schema.Struct({
22
22
  global: GlobalEventId,
23
- local: LocalEventId,
23
+ client: ClientEventId,
24
24
  }).annotations({ title: 'LiveStore.EventId' })
25
25
 
26
26
  /**
@@ -30,17 +30,21 @@ export const compare = (a: EventId, b: EventId) => {
30
30
  if (a.global !== b.global) {
31
31
  return a.global - b.global
32
32
  }
33
- return a.local - b.local
33
+ return a.client - b.client
34
34
  }
35
35
 
36
- export const isEqual = (a: EventId, b: EventId) => a.global === b.global && a.local === b.local
36
+ export const isEqual = (a: EventId, b: EventId) => a.global === b.global && a.client === b.client
37
37
 
38
38
  export type EventIdPair = { id: EventId; parentId: EventId }
39
39
 
40
- export const ROOT = { global: -1 as any as GlobalEventId, local: localDefault } satisfies EventId
40
+ export const ROOT = { global: -1 as any as GlobalEventId, client: clientDefault } satisfies EventId
41
41
 
42
42
  export const isGreaterThan = (a: EventId, b: EventId) => {
43
- return a.global > b.global || (a.global === b.global && a.local > b.local)
43
+ return a.global > b.global || (a.global === b.global && a.client > b.client)
44
+ }
45
+
46
+ export const isGreaterThanOrEqual = (a: EventId, b: EventId) => {
47
+ return a.global > b.global || (a.global === b.global && a.client >= b.client)
44
48
  }
45
49
 
46
50
  export const make = (id: EventId | typeof EventId.Encoded): EventId => {
@@ -49,12 +53,12 @@ export const make = (id: EventId | typeof EventId.Encoded): EventId => {
49
53
 
50
54
  export const nextPair = (id: EventId, isLocal: boolean): EventIdPair => {
51
55
  if (isLocal) {
52
- return { id: { global: id.global, local: (id.local + 1) as any as LocalEventId }, parentId: id }
56
+ return { id: { global: id.global, client: (id.client + 1) as any as ClientEventId }, parentId: id }
53
57
  }
54
58
 
55
59
  return {
56
- id: { global: (id.global + 1) as any as GlobalEventId, local: localDefault },
57
- // NOTE we always point to `local: 0` for non-localOnly mutations
58
- parentId: { global: id.global, local: localDefault },
60
+ id: { global: (id.global + 1) as any as GlobalEventId, client: clientDefault },
61
+ // NOTE we always point to `client: 0` for non-clientOnly mutations
62
+ parentId: { global: id.global, client: clientDefault },
59
63
  }
60
64
  }
@@ -10,7 +10,7 @@ export type MutationEventPartial<TMutationsDef extends MutationDef.Any> = {
10
10
  args: Schema.Schema.Type<TMutationsDef['schema']>
11
11
  }
12
12
 
13
- export type MutationEventPartialEncoded<TMutationsDef extends MutationDef.Any> = {
13
+ export type PartialEncoded<TMutationsDef extends MutationDef.Any> = {
14
14
  mutation: TMutationsDef['name']
15
15
  args: Schema.Schema.Encoded<TMutationsDef['schema']>
16
16
  }
@@ -20,6 +20,8 @@ export type MutationEvent<TMutationsDef extends MutationDef.Any> = {
20
20
  args: Schema.Schema.Type<TMutationsDef['schema']>
21
21
  id: EventId.EventId
22
22
  parentId: EventId.EventId
23
+ clientId: string
24
+ sessionId: string | undefined
23
25
  }
24
26
 
25
27
  export type MutationEventEncoded<TMutationsDef extends MutationDef.Any> = {
@@ -27,6 +29,8 @@ export type MutationEventEncoded<TMutationsDef extends MutationDef.Any> = {
27
29
  args: Schema.Schema.Encoded<TMutationsDef['schema']>
28
30
  id: EventId.EventId
29
31
  parentId: EventId.EventId
32
+ clientId: string
33
+ sessionId: string | undefined
30
34
  }
31
35
 
32
36
  export type AnyDecoded = MutationEvent<MutationDef.Any>
@@ -35,6 +39,8 @@ export const AnyDecoded = Schema.Struct({
35
39
  args: Schema.Any,
36
40
  id: EventId.EventId,
37
41
  parentId: EventId.EventId,
42
+ clientId: Schema.String,
43
+ sessionId: Schema.UndefinedOr(Schema.String),
38
44
  }).annotations({ title: 'MutationEvent.AnyDecoded' })
39
45
 
40
46
  export type AnyEncoded = MutationEventEncoded<MutationDef.Any>
@@ -43,6 +49,8 @@ export const AnyEncoded = Schema.Struct({
43
49
  args: Schema.Any,
44
50
  id: EventId.EventId,
45
51
  parentId: EventId.EventId,
52
+ clientId: Schema.String,
53
+ sessionId: Schema.UndefinedOr(Schema.String),
46
54
  }).annotations({ title: 'MutationEvent.AnyEncoded' })
47
55
 
48
56
  export const AnyEncodedGlobal = Schema.Struct({
@@ -50,11 +58,17 @@ export const AnyEncodedGlobal = Schema.Struct({
50
58
  args: Schema.Any,
51
59
  id: EventId.GlobalEventId,
52
60
  parentId: EventId.GlobalEventId,
61
+ clientId: Schema.String,
53
62
  }).annotations({ title: 'MutationEvent.AnyEncodedGlobal' })
54
63
  export type AnyEncodedGlobal = typeof AnyEncodedGlobal.Type
55
64
 
56
65
  export type PartialAnyDecoded = MutationEventPartial<MutationDef.Any>
57
- export type PartialAnyEncoded = MutationEventPartialEncoded<MutationDef.Any>
66
+ export type PartialAnyEncoded = PartialEncoded<MutationDef.Any>
67
+
68
+ export const PartialAnyEncoded = Schema.Struct({
69
+ mutation: Schema.String,
70
+ args: Schema.Any,
71
+ })
58
72
 
59
73
  export type PartialForSchema<TSchema extends LiveStoreSchema> = {
60
74
  [K in keyof TSchema['_MutationDefMapType']]: MutationEventPartial<TSchema['_MutationDefMapType'][K]>
@@ -75,6 +89,8 @@ export type ForMutationDefRecord<TMutationsDefRecord extends MutationDefRecord>
75
89
  args: Schema.Schema.Type<TMutationsDefRecord[K]['schema']>
76
90
  id: EventId.EventId
77
91
  parentId: EventId.EventId
92
+ clientId: string
93
+ sessionId: string | undefined
78
94
  }
79
95
  }[keyof TMutationsDefRecord],
80
96
  {
@@ -83,6 +99,8 @@ export type ForMutationDefRecord<TMutationsDefRecord extends MutationDefRecord>
83
99
  args: Schema.Schema.Encoded<TMutationsDefRecord[K]['schema']>
84
100
  id: EventId.EventId
85
101
  parentId: EventId.EventId
102
+ clientId: string
103
+ sessionId: string | undefined
86
104
  }
87
105
  }[keyof TMutationsDefRecord]
88
106
  >
@@ -112,6 +130,8 @@ export const makeMutationEventSchema = <TSchema extends LiveStoreSchema>(
112
130
  args: def.schema,
113
131
  id: EventId.EventId,
114
132
  parentId: EventId.EventId,
133
+ clientId: Schema.String,
134
+ sessionId: Schema.UndefinedOr(Schema.String),
115
135
  }),
116
136
  ),
117
137
  ).annotations({ title: 'MutationEvent' }) as any
@@ -136,6 +156,8 @@ export class EncodedWithMeta extends Schema.Class<EncodedWithMeta>('MutationEven
136
156
  args: Schema.Any,
137
157
  id: EventId.EventId,
138
158
  parentId: EventId.EventId,
159
+ clientId: Schema.String,
160
+ sessionId: Schema.UndefinedOr(Schema.String),
139
161
  // TODO get rid of `meta` again by cleaning up the usage implementations
140
162
  meta: Schema.optionalWith(
141
163
  Schema.Any as Schema.Schema<{
@@ -149,7 +171,7 @@ export class EncodedWithMeta extends Schema.Class<EncodedWithMeta>('MutationEven
149
171
  // - More readable way to print the id + parentId
150
172
  // - not including `meta`
151
173
  return {
152
- id: `(${this.id.global},${this.id.local}) → (${this.parentId.global},${this.parentId.local})`,
174
+ id: `(${this.id.global},${this.id.client}) → (${this.parentId.global},${this.parentId.client})`,
153
175
  mutation: this.mutation,
154
176
  args: this.args,
155
177
  }
@@ -164,8 +186,9 @@ export class EncodedWithMeta extends Schema.Class<EncodedWithMeta>('MutationEven
164
186
  static fromGlobal = (mutationEvent: AnyEncodedGlobal) =>
165
187
  new EncodedWithMeta({
166
188
  ...mutationEvent,
167
- id: { global: mutationEvent.id, local: EventId.localDefault },
168
- parentId: { global: mutationEvent.parentId, local: EventId.localDefault },
189
+ id: { global: mutationEvent.id, client: EventId.clientDefault },
190
+ parentId: { global: mutationEvent.parentId, client: EventId.clientDefault },
191
+ sessionId: undefined,
169
192
  })
170
193
 
171
194
  toGlobal = (): AnyEncodedGlobal => ({
@@ -177,7 +200,9 @@ export class EncodedWithMeta extends Schema.Class<EncodedWithMeta>('MutationEven
177
200
 
178
201
  export const isEqualEncoded = (a: AnyEncoded, b: AnyEncoded) =>
179
202
  a.id.global === b.id.global &&
180
- a.id.local === b.id.local &&
203
+ a.id.client === b.id.client &&
181
204
  a.mutation === b.mutation &&
205
+ a.clientId === b.clientId &&
206
+ // a.sessionId === b.sessionId &&
182
207
  // TODO use schema equality here
183
208
  JSON.stringify(a.args) === JSON.stringify(b.args)
@@ -1,7 +1,6 @@
1
1
  import { Schema } from '@livestore/utils/effect'
2
2
 
3
3
  import type { BindValues } from '../sql-queries/sql-queries.js'
4
- import type * as EventId from './EventId.js'
5
4
 
6
5
  export type MutationDefMap = Map<string | 'livestore.RawSql', MutationDef.Any>
7
6
  export type MutationDefRecord = {
@@ -38,24 +37,17 @@ export type MutationDef<TName extends string, TFrom, TTo> = {
38
37
  /** Warning: This feature is not fully implemented yet */
39
38
  historyId: string
40
39
  /**
41
- * When set to true, the mutation won't be synced over the network
40
+ * When set to true, the mutation won't be synced across clients but
42
41
  */
43
- localOnly: boolean
42
+ clientOnly: boolean
44
43
  /** Warning: This feature is not fully implemented yet */
45
44
  facts: FactsCallback<TTo> | undefined
46
45
  }
47
46
 
48
47
  /** Helper function to construct a partial mutation event */
49
- (
50
- args: TTo,
51
- options?: {
52
- id?: number
53
- },
54
- ): {
48
+ (args: TTo): {
55
49
  mutation: TName
56
50
  args: TTo
57
- // TODO remove/clean up after sync-next is fully implemented
58
- id?: EventId.EventId
59
51
  }
60
52
  }
61
53
 
@@ -120,7 +112,7 @@ export type DefineMutationOptions<TTo> = {
120
112
  /**
121
113
  * When set to true, the mutation won't be synced over the network
122
114
  */
123
- localOnly?: boolean
115
+ clientOnly?: boolean
124
116
  }
125
117
 
126
118
  // TODO possibly also allow for mutation event subsumption behaviour
@@ -130,12 +122,7 @@ export const defineMutation = <TName extends string, TFrom, TTo>(
130
122
  sql: MutationDefSqlResult<NoInfer<TTo>>,
131
123
  options?: DefineMutationOptions<TTo>,
132
124
  ): MutationDef<TName, TFrom, TTo> => {
133
- const makePartialEvent = (
134
- args: TTo,
135
- options?: {
136
- id?: EventId.EventId
137
- },
138
- ) => ({ mutation: name, args, ...options })
125
+ const makePartialEvent = (args: TTo) => ({ mutation: name, args })
139
126
 
140
127
  Object.defineProperty(makePartialEvent, 'name', { value: name })
141
128
  Object.defineProperty(makePartialEvent, 'schema', { value: schema })
@@ -143,7 +130,7 @@ export const defineMutation = <TName extends string, TFrom, TTo>(
143
130
  Object.defineProperty(makePartialEvent, 'options', {
144
131
  value: {
145
132
  historyId: options?.historyId ?? 'main',
146
- localOnly: options?.localOnly ?? false,
133
+ clientOnly: options?.clientOnly ?? false,
147
134
  facts: options?.facts
148
135
  ? (args, currentFacts) => {
149
136
  const res = options.facts!(args, currentFacts)
@@ -48,13 +48,13 @@ export const sessionChangesetMetaTable = table(
48
48
  {
49
49
  // TODO bring back primary key
50
50
  idGlobal: SqliteDsl.integer({ schema: EventId.GlobalEventId }),
51
- idLocal: SqliteDsl.integer({ schema: EventId.LocalEventId }),
51
+ idClient: SqliteDsl.integer({ schema: EventId.ClientEventId }),
52
52
  changeset: SqliteDsl.blob({ nullable: true }),
53
53
  debug: SqliteDsl.json({ nullable: true }),
54
54
  },
55
55
  {
56
56
  disableAutomaticIdColumn: true,
57
- indexes: [{ columns: ['idGlobal', 'idLocal'], name: 'idx_session_changeset_id' }],
57
+ indexes: [{ columns: ['idGlobal', 'idClient'], name: 'idx_session_changeset_id' }],
58
58
  },
59
59
  )
60
60
 
@@ -64,7 +64,7 @@ export const systemTables = [schemaMetaTable, schemaMutationsMetaTable, sessionC
64
64
 
65
65
  /// Mutation log DB
66
66
 
67
- export const SyncStatus = Schema.Literal('synced', 'pending', 'error', 'localOnly')
67
+ export const SyncStatus = Schema.Literal('synced', 'pending', 'error', 'clientOnly')
68
68
  export type SyncStatus = typeof SyncStatus.Type
69
69
 
70
70
  export const MUTATION_LOG_META_TABLE = 'mutation_log'
@@ -73,11 +73,14 @@ export const mutationLogMetaTable = table(
73
73
  MUTATION_LOG_META_TABLE,
74
74
  {
75
75
  idGlobal: SqliteDsl.integer({ primaryKey: true, schema: EventId.GlobalEventId }),
76
- idLocal: SqliteDsl.integer({ primaryKey: true, schema: EventId.LocalEventId }),
76
+ idClient: SqliteDsl.integer({ primaryKey: true, schema: EventId.ClientEventId }),
77
77
  parentIdGlobal: SqliteDsl.integer({ schema: EventId.GlobalEventId }),
78
- parentIdLocal: SqliteDsl.integer({ schema: EventId.LocalEventId }),
78
+ parentIdClient: SqliteDsl.integer({ schema: EventId.ClientEventId }),
79
79
  mutation: SqliteDsl.text({}),
80
80
  argsJson: SqliteDsl.text({ schema: Schema.parseJson(Schema.Any) }),
81
+ clientId: SqliteDsl.text({}),
82
+ /** Only available for mutations which were executed in this client */
83
+ sessionId: SqliteDsl.text({ nullable: true }),
81
84
  schemaHash: SqliteDsl.integer({}),
82
85
  syncMetadataJson: SqliteDsl.text({ schema: Schema.parseJson(Schema.Option(Schema.JsonValue)) }),
83
86
  },
@@ -85,7 +88,7 @@ export const mutationLogMetaTable = table(
85
88
  disableAutomaticIdColumn: true,
86
89
  indexes: [
87
90
  { columns: ['idGlobal'], name: 'idx_idGlobal' },
88
- { columns: ['idGlobal', 'idLocal'], name: 'idx_mutationlog_id' },
91
+ { columns: ['idGlobal', 'idClient'], name: 'idx_mutationlog_id' },
89
92
  ],
90
93
  },
91
94
  )
@@ -61,7 +61,7 @@ export type TableOptionsInput = Partial<{
61
61
  | boolean
62
62
  | {
63
63
  enabled: true
64
- localOnly?: boolean
64
+ clientOnly?: boolean
65
65
  }
66
66
  }>
67
67
 
@@ -115,7 +115,7 @@ export type TableOptions = {
115
115
  /**
116
116
  * When set to true, the mutations won't be synced over the network
117
117
  */
118
- localOnly: boolean
118
+ clientOnly: boolean
119
119
  }
120
120
 
121
121
  /** Derived based on whether the table definition has one or more columns (besides the `id` column) */
@@ -153,12 +153,12 @@ export const table = <
153
153
  disableAutomaticIdColumn: options?.disableAutomaticIdColumn ?? false,
154
154
  deriveMutations:
155
155
  options?.deriveMutations === true
156
- ? { enabled: true as const, localOnly: false }
156
+ ? { enabled: true as const, clientOnly: false }
157
157
  : options?.deriveMutations === false
158
158
  ? { enabled: false as const }
159
159
  : options?.deriveMutations === undefined
160
160
  ? { enabled: false as const }
161
- : { enabled: true as const, localOnly: options.deriveMutations.localOnly ?? false },
161
+ : { enabled: true as const, clientOnly: options.deriveMutations.clientOnly ?? false },
162
162
  isSingleColumn: SqliteDsl.isColumnDefinition(columnOrColumns) === true,
163
163
  requiredInsertColumnNames: 'type-level-only',
164
164
  }
@@ -234,7 +234,7 @@ export const table = <
234
234
  export const tableHasDerivedMutations = <TTableDef extends TableDefBase>(
235
235
  tableDef: TTableDef,
236
236
  ): tableDef is TTableDef & {
237
- options: { deriveMutations: { enabled: true; localOnly: boolean } }
237
+ options: { deriveMutations: { enabled: true; clientOnly: boolean } }
238
238
  } & DerivedMutationHelperFns<TTableDef['sqliteDef']['columns'], TTableDef['options']> =>
239
239
  tableDef.options.deriveMutations.enabled === true
240
240
 
@@ -268,13 +268,13 @@ type WithDefaults<
268
268
  isSingleton: TOptionsInput['isSingleton'] extends true ? true : false
269
269
  disableAutomaticIdColumn: TOptionsInput['disableAutomaticIdColumn'] extends true ? true : false
270
270
  deriveMutations: TOptionsInput['deriveMutations'] extends true
271
- ? { enabled: true; localOnly: boolean }
271
+ ? { enabled: true; clientOnly: boolean }
272
272
  : TOptionsInput['deriveMutations'] extends false
273
273
  ? { enabled: false }
274
- : TOptionsInput['deriveMutations'] extends { enabled: true; localOnly?: boolean }
274
+ : TOptionsInput['deriveMutations'] extends { enabled: true; clientOnly?: boolean }
275
275
  ? {
276
276
  enabled: true
277
- localOnly: TOptionsInput['deriveMutations']['localOnly'] extends true ? true : false
277
+ clientOnly: TOptionsInput['deriveMutations']['clientOnly'] extends true ? true : false
278
278
  }
279
279
  : never
280
280
  isSingleColumn: SqliteDsl.IsSingleColumn<TColumns>
@@ -2,7 +2,7 @@ import { SqliteAst, SqliteDsl } from '@livestore/db-schema'
2
2
  import { memoizeByStringifyArgs } from '@livestore/utils'
3
3
  import { Effect, Schema as EffectSchema } from '@livestore/utils/effect'
4
4
 
5
- import type { SqliteDb } from '../adapter-types.js'
5
+ import type { MigrationsReport, MigrationsReportEntry, SqliteDb, UnexpectedError } from '../adapter-types.js'
6
6
  import type { LiveStoreSchema } from '../schema/mod.js'
7
7
  import type { SchemaMetaRow, SchemaMutationsMetaRow } from '../schema/system-tables.js'
8
8
  import {
@@ -54,7 +54,7 @@ export const migrateDb = ({
54
54
  db: SqliteDb
55
55
  schema: LiveStoreSchema
56
56
  onProgress?: (opts: { done: number; total: number }) => Effect.Effect<void>
57
- }) =>
57
+ }): Effect.Effect<MigrationsReport, UnexpectedError> =>
58
58
  Effect.gen(function* () {
59
59
  yield* migrateTable({
60
60
  db,
@@ -81,6 +81,7 @@ export const migrateDb = ({
81
81
 
82
82
  const tablesToMigrate = new Set<{ tableAst: SqliteAst.Table; schemaHash: number }>()
83
83
 
84
+ const migrationsReportEntries: MigrationsReportEntry[] = []
84
85
  for (const tableDef of tableDefs) {
85
86
  const tableAst = tableDef.sqliteDef.ast
86
87
  const tableName = tableAst.name
@@ -90,9 +91,10 @@ export const migrateDb = ({
90
91
  if (schemaHash !== dbSchemaHash) {
91
92
  tablesToMigrate.add({ tableAst, schemaHash })
92
93
 
93
- console.log(
94
- `Schema hash mismatch for table '${tableName}' (DB: ${dbSchemaHash}, expected: ${schemaHash}), migrating table...`,
95
- )
94
+ migrationsReportEntries.push({
95
+ tableName,
96
+ hashes: { expected: schemaHash, actual: dbSchemaHash },
97
+ })
96
98
  }
97
99
  }
98
100
 
@@ -107,6 +109,8 @@ export const migrateDb = ({
107
109
  yield* onProgress({ done: processedTables, total: tablesCount })
108
110
  }
109
111
  }
112
+
113
+ return { migrations: migrationsReportEntries }
110
114
  })
111
115
 
112
116
  export const migrateTable = ({
@@ -45,8 +45,8 @@ export const makeClientSessionSyncProcessor = ({
45
45
 
46
46
  const syncStateRef = {
47
47
  current: new SyncState.SyncState({
48
- localHead: clientSession.leaderThread.mutations.initialMutationEventId,
49
- upstreamHead: clientSession.leaderThread.mutations.initialMutationEventId,
48
+ localHead: clientSession.leaderThread.initialState.leaderHead,
49
+ upstreamHead: clientSession.leaderThread.initialState.leaderHead,
50
50
  pending: [],
51
51
  // TODO init rollbackTail from leader to be ready for backend rebasing
52
52
  rollbackTail: [],
@@ -54,10 +54,9 @@ export const makeClientSessionSyncProcessor = ({
54
54
  }
55
55
 
56
56
  const syncStateUpdateQueue = Queue.unbounded<SyncState.SyncState>().pipe(Effect.runSync)
57
-
58
57
  const isLocalEvent = (mutationEventEncoded: MutationEvent.EncodedWithMeta) => {
59
58
  const mutationDef = schema.mutations.get(mutationEventEncoded.mutation)!
60
- return mutationDef.options.localOnly
59
+ return mutationDef.options.clientOnly
61
60
  }
62
61
 
63
62
  const push: ClientSessionSyncProcessor['push'] = (batch, { otelContext }) => {
@@ -66,10 +65,15 @@ export const makeClientSessionSyncProcessor = ({
66
65
  let baseEventId = syncStateRef.current.localHead
67
66
  const encodedMutationEvents = batch.map((mutationEvent) => {
68
67
  const mutationDef = schema.mutations.get(mutationEvent.mutation)!
69
- const nextIdPair = EventId.nextPair(baseEventId, mutationDef.options.localOnly)
68
+ const nextIdPair = EventId.nextPair(baseEventId, mutationDef.options.clientOnly)
70
69
  baseEventId = nextIdPair.id
71
70
  return new MutationEvent.EncodedWithMeta(
72
- Schema.encodeUnknownSync(mutationEventSchema)({ ...mutationEvent, ...nextIdPair }),
71
+ Schema.encodeUnknownSync(mutationEventSchema)({
72
+ ...mutationEvent,
73
+ ...nextIdPair,
74
+ clientId: clientSession.clientId,
75
+ sessionId: clientSession.sessionId,
76
+ }),
73
77
  )
74
78
  })
75
79
 
@@ -114,6 +118,21 @@ export const makeClientSessionSyncProcessor = ({
114
118
  const otelContext = otel.trace.setSpan(otel.context.active(), span)
115
119
 
116
120
  const boot: ClientSessionSyncProcessor['boot'] = Effect.gen(function* () {
121
+ // eslint-disable-next-line unicorn/prefer-global-this
122
+ if (typeof window !== 'undefined') {
123
+ const onBeforeUnload = (event: BeforeUnloadEvent) => {
124
+ if (syncStateRef.current.pending.length > 0) {
125
+ // Trigger the default browser dialog
126
+ event.preventDefault()
127
+ }
128
+ }
129
+
130
+ yield* Effect.acquireRelease(
131
+ Effect.sync(() => window.addEventListener('beforeunload', onBeforeUnload)),
132
+ () => Effect.sync(() => window.removeEventListener('beforeunload', onBeforeUnload)),
133
+ )
134
+ }
135
+
117
136
  yield* clientSession.leaderThread.mutations.pull.pipe(
118
137
  Stream.tap(({ payload, remaining }) =>
119
138
  Effect.gen(function* () {
@@ -231,5 +231,5 @@ export const compareEventIds = (a: EventId.EventId, b: EventId.EventId) => {
231
231
  if (a.global !== b.global) {
232
232
  return a.global - b.global
233
233
  }
234
- return a.local - b.local
234
+ return a.client - b.client
235
235
  }
@@ -20,7 +20,7 @@ export const emptyHistoryDag = (): HistoryDag =>
20
20
  })
21
21
 
22
22
  // TODO consider making `ROOT_ID` parent to itself
23
- export const rootParentId = EventId.make({ global: EventId.ROOT.global - 1, local: EventId.localDefault })
23
+ export const rootParentId = EventId.make({ global: EventId.ROOT.global - 1, client: EventId.clientDefault })
24
24
 
25
25
  export type HistoryDagNode = {
26
26
  id: EventId.EventId
@@ -30,6 +30,8 @@ export type HistoryDagNode = {
30
30
  /** Facts are being used for conflict detection and history compaction */
31
31
  factsGroup: MutationEventFactsGroup
32
32
  meta?: any
33
+ clientId: string
34
+ sessionId: string | undefined
33
35
  }
34
36
 
35
37
  export const rootEventNode: HistoryDagNode = {
@@ -39,6 +41,8 @@ export const rootEventNode: HistoryDagNode = {
39
41
  mutation: '__Root__',
40
42
  args: {},
41
43
  factsGroup: { modifySet: new Map(), modifyUnset: new Map(), depRequire: new Map(), depRead: new Map() },
44
+ clientId: 'root',
45
+ sessionId: undefined,
42
46
  }
43
47
 
44
48
  export const EMPTY_FACT_VALUE = Symbol('EMPTY_FACT_VALUE')
@@ -3,7 +3,7 @@ import { factsToString, validateFacts } from './facts.js'
3
3
  import { emptyHistoryDag, type HistoryDagNode, rootParentId } from './history-dag-common.js'
4
4
 
5
5
  export const eventIdToString = (eventId: EventId.EventId) =>
6
- eventId.local === 0 ? eventId.global.toString() : `${eventId.global}.${eventId.local}`
6
+ eventId.client === 0 ? eventId.global.toString() : `${eventId.global}.${eventId.client}`
7
7
 
8
8
  export const historyDagFromNodes = (dagNodes: HistoryDagNode[], options?: { skipFactsCheck: boolean }) => {
9
9
  if (options?.skipFactsCheck !== true) {