@livestore/common 0.3.0-dev.47 → 0.3.0-dev.49

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 (154) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/adapter-types.d.ts +8 -6
  3. package/dist/adapter-types.d.ts.map +1 -1
  4. package/dist/adapter-types.js +2 -2
  5. package/dist/adapter-types.js.map +1 -1
  6. package/dist/devtools/devtools-messages-client-session.d.ts +25 -25
  7. package/dist/devtools/devtools-messages-client-session.js +3 -3
  8. package/dist/devtools/devtools-messages-client-session.js.map +1 -1
  9. package/dist/devtools/devtools-messages-common.d.ts +6 -6
  10. package/dist/devtools/devtools-messages-leader.d.ts +30 -30
  11. package/dist/devtools/devtools-messages-leader.js +3 -3
  12. package/dist/devtools/devtools-messages-leader.js.map +1 -1
  13. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
  14. package/dist/leader-thread/LeaderSyncProcessor.js +27 -25
  15. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  16. package/dist/leader-thread/eventlog.d.ts +10 -10
  17. package/dist/leader-thread/eventlog.d.ts.map +1 -1
  18. package/dist/leader-thread/eventlog.js +24 -24
  19. package/dist/leader-thread/eventlog.js.map +1 -1
  20. package/dist/leader-thread/leader-worker-devtools.js +1 -1
  21. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  22. package/dist/leader-thread/materialize-event.d.ts +3 -3
  23. package/dist/leader-thread/materialize-event.d.ts.map +1 -1
  24. package/dist/leader-thread/materialize-event.js +19 -15
  25. package/dist/leader-thread/materialize-event.js.map +1 -1
  26. package/dist/leader-thread/types.d.ts +4 -4
  27. package/dist/leader-thread/types.d.ts.map +1 -1
  28. package/dist/make-client-session.d.ts +1 -1
  29. package/dist/make-client-session.d.ts.map +1 -1
  30. package/dist/make-client-session.js +2 -1
  31. package/dist/make-client-session.js.map +1 -1
  32. package/dist/materializer-helper.d.ts.map +1 -1
  33. package/dist/materializer-helper.js +4 -2
  34. package/dist/materializer-helper.js.map +1 -1
  35. package/dist/rematerialize-from-eventlog.js +9 -9
  36. package/dist/rematerialize-from-eventlog.js.map +1 -1
  37. package/dist/schema/EventId.d.ts +28 -28
  38. package/dist/schema/EventId.d.ts.map +1 -1
  39. package/dist/schema/EventId.js +9 -9
  40. package/dist/schema/EventId.js.map +1 -1
  41. package/dist/schema/EventId.test.js +5 -5
  42. package/dist/schema/EventId.test.js.map +1 -1
  43. package/dist/schema/EventNumber.d.ts +57 -0
  44. package/dist/schema/EventNumber.d.ts.map +1 -0
  45. package/dist/schema/EventNumber.js +82 -0
  46. package/dist/schema/EventNumber.js.map +1 -0
  47. package/dist/schema/EventNumber.test.d.ts +2 -0
  48. package/dist/schema/EventNumber.test.d.ts.map +1 -0
  49. package/dist/schema/EventNumber.test.js +11 -0
  50. package/dist/schema/EventNumber.test.js.map +1 -0
  51. package/dist/schema/EventSequenceNumber.d.ts +57 -0
  52. package/dist/schema/EventSequenceNumber.d.ts.map +1 -0
  53. package/dist/schema/EventSequenceNumber.js +82 -0
  54. package/dist/schema/EventSequenceNumber.js.map +1 -0
  55. package/dist/schema/EventSequenceNumber.test.d.ts +2 -0
  56. package/dist/schema/EventSequenceNumber.test.d.ts.map +1 -0
  57. package/dist/schema/EventSequenceNumber.test.js +11 -0
  58. package/dist/schema/EventSequenceNumber.test.js.map +1 -0
  59. package/dist/schema/LiveStoreEvent.d.ts +81 -79
  60. package/dist/schema/LiveStoreEvent.d.ts.map +1 -1
  61. package/dist/schema/LiveStoreEvent.js +31 -32
  62. package/dist/schema/LiveStoreEvent.js.map +1 -1
  63. package/dist/schema/mod.d.ts +1 -1
  64. package/dist/schema/mod.d.ts.map +1 -1
  65. package/dist/schema/mod.js +1 -1
  66. package/dist/schema/mod.js.map +1 -1
  67. package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
  68. package/dist/schema/state/sqlite/query-builder/impl.js +2 -2
  69. package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
  70. package/dist/schema/state/sqlite/query-builder/impl.test.d.ts +3 -3
  71. package/dist/schema/state/sqlite/query-builder/impl.test.js +9 -0
  72. package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
  73. package/dist/schema/state/sqlite/system-tables.d.ts +52 -52
  74. package/dist/schema/state/sqlite/system-tables.d.ts.map +1 -1
  75. package/dist/schema/state/sqlite/system-tables.js +11 -10
  76. package/dist/schema/state/sqlite/system-tables.js.map +1 -1
  77. package/dist/sync/ClientSessionSyncProcessor.js +6 -6
  78. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  79. package/dist/sync/next/compact-events.js +38 -35
  80. package/dist/sync/next/compact-events.js.map +1 -1
  81. package/dist/sync/next/facts.d.ts +4 -4
  82. package/dist/sync/next/facts.d.ts.map +1 -1
  83. package/dist/sync/next/facts.js +8 -8
  84. package/dist/sync/next/facts.js.map +1 -1
  85. package/dist/sync/next/history-dag-common.d.ts +4 -4
  86. package/dist/sync/next/history-dag-common.d.ts.map +1 -1
  87. package/dist/sync/next/history-dag-common.js +7 -4
  88. package/dist/sync/next/history-dag-common.js.map +1 -1
  89. package/dist/sync/next/history-dag.d.ts +0 -2
  90. package/dist/sync/next/history-dag.d.ts.map +1 -1
  91. package/dist/sync/next/history-dag.js +15 -13
  92. package/dist/sync/next/history-dag.js.map +1 -1
  93. package/dist/sync/next/rebase-events.d.ts.map +1 -1
  94. package/dist/sync/next/rebase-events.js +10 -4
  95. package/dist/sync/next/rebase-events.js.map +1 -1
  96. package/dist/sync/next/test/compact-events.calculator.test.js +13 -13
  97. package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
  98. package/dist/sync/next/test/compact-events.test.js +31 -31
  99. package/dist/sync/next/test/compact-events.test.js.map +1 -1
  100. package/dist/sync/next/test/event-fixtures.d.ts +10 -0
  101. package/dist/sync/next/test/event-fixtures.d.ts.map +1 -1
  102. package/dist/sync/next/test/event-fixtures.js +19 -13
  103. package/dist/sync/next/test/event-fixtures.js.map +1 -1
  104. package/dist/sync/sync.d.ts +11 -11
  105. package/dist/sync/sync.d.ts.map +1 -1
  106. package/dist/sync/sync.js +5 -5
  107. package/dist/sync/sync.js.map +1 -1
  108. package/dist/sync/syncstate.d.ts +18 -18
  109. package/dist/sync/syncstate.d.ts.map +1 -1
  110. package/dist/sync/syncstate.js +46 -47
  111. package/dist/sync/syncstate.js.map +1 -1
  112. package/dist/sync/syncstate.test.js +110 -110
  113. package/dist/sync/syncstate.test.js.map +1 -1
  114. package/dist/sync/validate-push-payload.d.ts +2 -2
  115. package/dist/sync/validate-push-payload.d.ts.map +1 -1
  116. package/dist/sync/validate-push-payload.js +4 -4
  117. package/dist/sync/validate-push-payload.js.map +1 -1
  118. package/dist/version.d.ts +2 -2
  119. package/dist/version.js +2 -2
  120. package/package.json +4 -4
  121. package/src/adapter-types.ts +6 -4
  122. package/src/devtools/devtools-messages-client-session.ts +3 -3
  123. package/src/devtools/devtools-messages-leader.ts +3 -3
  124. package/src/leader-thread/LeaderSyncProcessor.ts +36 -29
  125. package/src/leader-thread/eventlog.ts +36 -31
  126. package/src/leader-thread/leader-worker-devtools.ts +1 -1
  127. package/src/leader-thread/materialize-event.ts +21 -17
  128. package/src/leader-thread/types.ts +4 -4
  129. package/src/make-client-session.ts +2 -0
  130. package/src/materializer-helper.ts +5 -2
  131. package/src/rematerialize-from-eventlog.ts +10 -10
  132. package/src/schema/EventSequenceNumber.test.ts +12 -0
  133. package/src/schema/EventSequenceNumber.ts +121 -0
  134. package/src/schema/LiveStoreEvent.ts +66 -65
  135. package/src/schema/mod.ts +1 -1
  136. package/src/schema/state/sqlite/query-builder/impl.test.ts +9 -0
  137. package/src/schema/state/sqlite/query-builder/impl.ts +3 -2
  138. package/src/schema/state/sqlite/system-tables.ts +11 -10
  139. package/src/sync/ClientSessionSyncProcessor.ts +6 -6
  140. package/src/sync/next/compact-events.ts +38 -35
  141. package/src/sync/next/facts.ts +12 -9
  142. package/src/sync/next/history-dag-common.ts +9 -6
  143. package/src/sync/next/history-dag.ts +15 -16
  144. package/src/sync/next/rebase-events.ts +10 -4
  145. package/src/sync/next/test/compact-events.calculator.test.ts +13 -13
  146. package/src/sync/next/test/compact-events.test.ts +31 -31
  147. package/src/sync/next/test/event-fixtures.ts +20 -13
  148. package/src/sync/sync.ts +7 -7
  149. package/src/sync/syncstate.test.ts +112 -112
  150. package/src/sync/syncstate.ts +58 -48
  151. package/src/sync/validate-push-payload.ts +5 -5
  152. package/src/version.ts +2 -2
  153. package/src/schema/EventId.test.ts +0 -12
  154. package/src/schema/EventId.ts +0 -106
@@ -33,6 +33,7 @@ export const makeClientSession = <R>({
33
33
  connectWebmeshNode,
34
34
  webmeshMode,
35
35
  registerBeforeUnload,
36
+ debugInstanceId,
36
37
  }: AdapterArgs & {
37
38
  clientId: string
38
39
  sessionId: string
@@ -130,5 +131,6 @@ export const makeClientSession = <R>({
130
131
  clientId,
131
132
  sessionId,
132
133
  shutdown,
134
+ debugInstanceId,
133
135
  } satisfies ClientSession
134
136
  })
@@ -1,4 +1,4 @@
1
- import { isReadonlyArray } from '@livestore/utils'
1
+ import { isNil, isReadonlyArray } from '@livestore/utils'
2
2
  import { Schema } from '@livestore/utils/effect'
3
3
 
4
4
  import type { SqliteDb } from './adapter-types.js'
@@ -38,7 +38,10 @@ export const getExecArgsFromEvent = ({
38
38
  }> => {
39
39
  const eventArgsDecoded =
40
40
  event.decoded === undefined ? Schema.decodeUnknownSync(eventDef.schema)(event.encoded!.args) : event.decoded.args
41
- const eventArgsEncoded = event.encoded?.args ?? Schema.encodeUnknownSync(eventDef.schema)(event.decoded!.args)
41
+
42
+ const eventArgsEncoded = isNil(event.decoded?.args)
43
+ ? undefined
44
+ : Schema.encodeUnknownSync(eventDef.schema)(event.decoded!.args)
42
45
 
43
46
  const query: MaterializerContextQuery = (
44
47
  rawQueryOrQueryBuilder:
@@ -4,7 +4,7 @@ import { Chunk, Effect, Option, Schema, Stream } from '@livestore/utils/effect'
4
4
  import { type SqliteDb, UnexpectedError } from './adapter-types.js'
5
5
  import type { MaterializeEvent } from './leader-thread/mod.js'
6
6
  import type { EventDef, LiveStoreSchema } from './schema/mod.js'
7
- import { EventId, getEventDef, LiveStoreEvent, SystemTables } from './schema/mod.js'
7
+ import { EventSequenceNumber, getEventDef, LiveStoreEvent, SystemTables } from './schema/mod.js'
8
8
  import type { PreparedBindValues } from './util.js'
9
9
  import { sql } from './util.js'
10
10
 
@@ -56,8 +56,8 @@ This likely means the schema has changed in an incompatible way.
56
56
  )
57
57
 
58
58
  const eventEncoded = LiveStoreEvent.EncodedWithMeta.make({
59
- id: { global: row.idGlobal, client: row.idClient },
60
- parentId: { global: row.parentIdGlobal, client: row.parentIdClient },
59
+ seqNum: { global: row.seqNumGlobal, client: row.seqNumClient },
60
+ parentSeqNum: { global: row.parentSeqNumGlobal, client: row.parentSeqNumClient },
61
61
  name: row.name,
62
62
  args,
63
63
  clientId: row.clientId,
@@ -71,8 +71,8 @@ This likely means the schema has changed in an incompatible way.
71
71
 
72
72
  const stmt = dbEventlog.prepare(sql`\
73
73
  SELECT * FROM ${SystemTables.EVENTLOG_META_TABLE}
74
- WHERE idGlobal > $idGlobal OR (idGlobal = $idGlobal AND idClient > $idClient)
75
- ORDER BY idGlobal ASC, idClient ASC
74
+ WHERE seqNumGlobal > $seqNumGlobal OR (seqNumGlobal = $seqNumGlobal AND seqNumClient > $seqNumClient)
75
+ ORDER BY seqNumGlobal ASC, seqNumClient ASC
76
76
  LIMIT ${CHUNK_SIZE}
77
77
  `)
78
78
 
@@ -87,14 +87,14 @@ LIMIT ${CHUNK_SIZE}
87
87
 
88
88
  const lastId = Chunk.isChunk(item)
89
89
  ? Chunk.last(item).pipe(
90
- Option.map((_) => ({ global: _.idGlobal, client: _.idClient })),
91
- Option.getOrElse(() => EventId.ROOT),
90
+ Option.map((_) => ({ global: _.seqNumGlobal, client: _.seqNumClient })),
91
+ Option.getOrElse(() => EventSequenceNumber.ROOT),
92
92
  )
93
- : EventId.ROOT
93
+ : EventSequenceNumber.ROOT
94
94
  const nextItem = Chunk.fromIterable(
95
95
  stmt.select<SystemTables.EventlogMetaRow>({
96
- $idGlobal: lastId?.global,
97
- $idClient: lastId?.client,
96
+ $seqNumGlobal: lastId?.global,
97
+ $seqNumClient: lastId?.client,
98
98
  } as any as PreparedBindValues),
99
99
  )
100
100
  const prevItem = Chunk.isChunk(item) ? item : Chunk.empty()
@@ -0,0 +1,12 @@
1
+ import { Vitest } from '@livestore/utils-dev/node-vitest'
2
+ import { expect } from 'vitest'
3
+
4
+ import { EventSequenceNumber } from './mod.js'
5
+
6
+ Vitest.describe('EventSequenceNumber', () => {
7
+ Vitest.test('nextPair', () => {
8
+ const e_0_0 = EventSequenceNumber.make({ global: 0, client: 0 })
9
+ expect(EventSequenceNumber.nextPair(e_0_0, false).seqNum).toStrictEqual({ global: 1, client: 0 })
10
+ expect(EventSequenceNumber.nextPair(e_0_0, true).seqNum).toStrictEqual({ global: 0, client: 1 })
11
+ })
12
+ })
@@ -0,0 +1,121 @@
1
+ import { Brand, Schema } from '@livestore/utils/effect'
2
+
3
+ export type ClientEventSequenceNumber = Brand.Branded<number, 'ClientEventSequenceNumber'>
4
+ export const localEventSequenceNumber = Brand.nominal<ClientEventSequenceNumber>()
5
+ export const ClientEventSequenceNumber = Schema.fromBrand(localEventSequenceNumber)(Schema.Int)
6
+
7
+ export type GlobalEventSequenceNumber = Brand.Branded<number, 'GlobalEventSequenceNumber'>
8
+ export const globalEventSequenceNumber = Brand.nominal<GlobalEventSequenceNumber>()
9
+ export const GlobalEventSequenceNumber = Schema.fromBrand(globalEventSequenceNumber)(Schema.Int)
10
+
11
+ export const clientDefault = 0 as any as ClientEventSequenceNumber
12
+
13
+ /**
14
+ * LiveStore event sequence number value consisting of a globally unique event sequence number
15
+ * and a client sequence number.
16
+ *
17
+ * The client sequence number is only used for clientOnly events and starts from 0 for each global sequence number.
18
+ */
19
+ export type EventSequenceNumber = {
20
+ global: GlobalEventSequenceNumber
21
+ client: ClientEventSequenceNumber
22
+ /**
23
+ * TODO add generation number in favour of LEADER_MERGE_COUNTER_TABLE
24
+ */
25
+ // generation: number
26
+ }
27
+
28
+ // export const EventSequenceNumber = Schema.Struct({})
29
+ // export const EventSequenceNumber = Schema.Struct({})
30
+ // export const ClientEventSequenceNumber = Schema.Struct({})
31
+ // export const GlobalEventSequenceNumber = Schema.Struct({})
32
+
33
+ /**
34
+ * NOTE: Client mutation events with a non-0 client id, won't be synced to the sync backend.
35
+ */
36
+ export const EventSequenceNumber = Schema.Struct({
37
+ global: GlobalEventSequenceNumber,
38
+ /** Only increments for clientOnly events */
39
+ client: ClientEventSequenceNumber,
40
+
41
+ // TODO also provide a way to see "confirmation level" of event (e.g. confirmed by leader/sync backend)
42
+
43
+ // TODO: actually add this field
44
+ // Client only
45
+ // generation: Schema.Number.pipe(Schema.optional),
46
+ }).annotations({ title: 'LiveStore.EventSequenceNumber' })
47
+
48
+ /**
49
+ * Compare two event sequence numbers i.e. checks if the first event sequence number is less than the second.
50
+ */
51
+ export const compare = (a: EventSequenceNumber, b: EventSequenceNumber) => {
52
+ if (a.global !== b.global) {
53
+ return a.global - b.global
54
+ }
55
+ return a.client - b.client
56
+ }
57
+
58
+ /**
59
+ * Convert an event sequence number to a string representation.
60
+ */
61
+ export const toString = (seqNum: EventSequenceNumber) =>
62
+ seqNum.client === 0 ? `e${seqNum.global}` : `e${seqNum.global}+${seqNum.client}`
63
+
64
+ /**
65
+ * Convert a string representation of an event sequence number to an event sequence number.
66
+ */
67
+ export const fromString = (str: string): EventSequenceNumber => {
68
+ const [global, client] = str.slice(1, -1).split(',').map(Number)
69
+ if (global === undefined || client === undefined) {
70
+ throw new Error('Invalid event sequence number string')
71
+ }
72
+ return { global, client } as EventSequenceNumber
73
+ }
74
+
75
+ export const isEqual = (a: EventSequenceNumber, b: EventSequenceNumber) =>
76
+ a.global === b.global && a.client === b.client
77
+
78
+ export type EventSequenceNumberPair = { seqNum: EventSequenceNumber; parentSeqNum: EventSequenceNumber }
79
+
80
+ export const ROOT = {
81
+ global: 0 as any as GlobalEventSequenceNumber,
82
+ client: clientDefault,
83
+ } satisfies EventSequenceNumber
84
+
85
+ export const isGreaterThan = (a: EventSequenceNumber, b: EventSequenceNumber) => {
86
+ return a.global > b.global || (a.global === b.global && a.client > b.client)
87
+ }
88
+
89
+ export const isGreaterThanOrEqual = (a: EventSequenceNumber, b: EventSequenceNumber) => {
90
+ return a.global > b.global || (a.global === b.global && a.client >= b.client)
91
+ }
92
+
93
+ export const max = (a: EventSequenceNumber, b: EventSequenceNumber) => {
94
+ return a.global > b.global || (a.global === b.global && a.client > b.client) ? a : b
95
+ }
96
+
97
+ export const diff = (a: EventSequenceNumber, b: EventSequenceNumber) => {
98
+ return {
99
+ global: a.global - b.global,
100
+ client: a.client - b.client,
101
+ }
102
+ }
103
+
104
+ export const make = (seqNum: EventSequenceNumber | typeof EventSequenceNumber.Encoded): EventSequenceNumber => {
105
+ return Schema.is(EventSequenceNumber)(seqNum) ? seqNum : Schema.decodeSync(EventSequenceNumber)(seqNum)
106
+ }
107
+
108
+ export const nextPair = (seqNum: EventSequenceNumber, isLocal: boolean): EventSequenceNumberPair => {
109
+ if (isLocal) {
110
+ return {
111
+ seqNum: { global: seqNum.global, client: (seqNum.client + 1) as any as ClientEventSequenceNumber },
112
+ parentSeqNum: seqNum,
113
+ }
114
+ }
115
+
116
+ return {
117
+ seqNum: { global: (seqNum.global + 1) as any as GlobalEventSequenceNumber, client: clientDefault },
118
+ // NOTE we always point to `client: 0` for non-clientOnly events
119
+ parentSeqNum: { global: seqNum.global, client: clientDefault },
120
+ }
121
+ }
@@ -2,53 +2,55 @@ import { memoizeByRef } from '@livestore/utils'
2
2
  import { Option, Schema } from '@livestore/utils/effect'
3
3
 
4
4
  import type { EventDef, EventDefRecord } from './EventDef.js'
5
- import * as EventId from './EventId.js'
5
+ import * as EventSequenceNumber from './EventSequenceNumber.js'
6
6
  import type { LiveStoreSchema } from './schema.js'
7
7
 
8
- export type EventDefPartial<TEventDef extends EventDef.Any> = {
9
- name: TEventDef['name']
10
- args: Schema.Schema.Type<TEventDef['schema']>
11
- }
8
+ export namespace ForEventDef {
9
+ export type PartialDecoded<TEventDef extends EventDef.Any> = {
10
+ name: TEventDef['name']
11
+ args: Schema.Schema.Type<TEventDef['schema']>
12
+ }
12
13
 
13
- export type PartialEncoded<TEventDef extends EventDef.Any> = {
14
- name: TEventDef['name']
15
- args: Schema.Schema.Encoded<TEventDef['schema']>
16
- }
14
+ export type PartialEncoded<TEventDef extends EventDef.Any> = {
15
+ name: TEventDef['name']
16
+ args: Schema.Schema.Encoded<TEventDef['schema']>
17
+ }
17
18
 
18
- export type ForEventDef<TEventDef extends EventDef.Any> = {
19
- name: TEventDef['name']
20
- args: Schema.Schema.Type<TEventDef['schema']>
21
- id: EventId.EventId
22
- parentId: EventId.EventId
23
- clientId: string
24
- sessionId: string
25
- }
19
+ export type Decoded<TEventDef extends EventDef.Any> = {
20
+ name: TEventDef['name']
21
+ args: Schema.Schema.Type<TEventDef['schema']>
22
+ seqNum: EventSequenceNumber.EventSequenceNumber
23
+ parentSeqNum: EventSequenceNumber.EventSequenceNumber
24
+ clientId: string
25
+ sessionId: string
26
+ }
26
27
 
27
- export type EventDefEncoded<TEventDef extends EventDef.Any> = {
28
- name: TEventDef['name']
29
- args: Schema.Schema.Encoded<TEventDef['schema']>
30
- id: EventId.EventId
31
- parentId: EventId.EventId
32
- clientId: string
33
- sessionId: string
28
+ export type Encoded<TEventDef extends EventDef.Any> = {
29
+ name: TEventDef['name']
30
+ args: Schema.Schema.Encoded<TEventDef['schema']>
31
+ seqNum: EventSequenceNumber.EventSequenceNumber
32
+ parentSeqNum: EventSequenceNumber.EventSequenceNumber
33
+ clientId: string
34
+ sessionId: string
35
+ }
34
36
  }
35
37
 
36
- export type AnyDecoded = ForEventDef<EventDef.Any>
38
+ export type AnyDecoded = ForEventDef.Decoded<EventDef.Any>
37
39
  export const AnyDecoded = Schema.Struct({
38
40
  name: Schema.String,
39
41
  args: Schema.Any,
40
- id: EventId.EventId,
41
- parentId: EventId.EventId,
42
+ seqNum: EventSequenceNumber.EventSequenceNumber,
43
+ parentSeqNum: EventSequenceNumber.EventSequenceNumber,
42
44
  clientId: Schema.String,
43
45
  sessionId: Schema.String,
44
46
  }).annotations({ title: 'LiveStoreEvent.AnyDecoded' })
45
47
 
46
- export type AnyEncoded = EventDefEncoded<EventDef.Any>
48
+ export type AnyEncoded = ForEventDef.Encoded<EventDef.Any>
47
49
  export const AnyEncoded = Schema.Struct({
48
50
  name: Schema.String,
49
51
  args: Schema.Any,
50
- id: EventId.EventId,
51
- parentId: EventId.EventId,
52
+ seqNum: EventSequenceNumber.EventSequenceNumber,
53
+ parentSeqNum: EventSequenceNumber.EventSequenceNumber,
52
54
  clientId: Schema.String,
53
55
  sessionId: Schema.String,
54
56
  }).annotations({ title: 'LiveStoreEvent.AnyEncoded' })
@@ -56,15 +58,15 @@ export const AnyEncoded = Schema.Struct({
56
58
  export const AnyEncodedGlobal = Schema.Struct({
57
59
  name: Schema.String,
58
60
  args: Schema.Any,
59
- id: EventId.GlobalEventId,
60
- parentId: EventId.GlobalEventId,
61
+ seqNum: EventSequenceNumber.GlobalEventSequenceNumber,
62
+ parentSeqNum: EventSequenceNumber.GlobalEventSequenceNumber,
61
63
  clientId: Schema.String,
62
64
  sessionId: Schema.String,
63
65
  }).annotations({ title: 'LiveStoreEvent.AnyEncodedGlobal' })
64
66
  export type AnyEncodedGlobal = typeof AnyEncodedGlobal.Type
65
67
 
66
- export type PartialAnyDecoded = EventDefPartial<EventDef.Any>
67
- export type PartialAnyEncoded = PartialEncoded<EventDef.Any>
68
+ export type PartialAnyDecoded = ForEventDef.PartialDecoded<EventDef.Any>
69
+ export type PartialAnyEncoded = ForEventDef.PartialEncoded<EventDef.Any>
68
70
 
69
71
  export const PartialAnyEncoded = Schema.Struct({
70
72
  name: Schema.String,
@@ -72,23 +74,23 @@ export const PartialAnyEncoded = Schema.Struct({
72
74
  })
73
75
 
74
76
  export type PartialForSchema<TSchema extends LiveStoreSchema> = {
75
- [K in keyof TSchema['_EventDefMapType']]: EventDefPartial<TSchema['_EventDefMapType'][K]>
77
+ [K in keyof TSchema['_EventDefMapType']]: ForEventDef.PartialDecoded<TSchema['_EventDefMapType'][K]>
76
78
  }[keyof TSchema['_EventDefMapType']]
77
79
 
78
80
  export type ForSchema<TSchema extends LiveStoreSchema> = {
79
- [K in keyof TSchema['_EventDefMapType']]: ForEventDef<TSchema['_EventDefMapType'][K]>
81
+ [K in keyof TSchema['_EventDefMapType']]: ForEventDef.Decoded<TSchema['_EventDefMapType'][K]>
80
82
  }[keyof TSchema['_EventDefMapType']]
81
83
 
82
84
  export const isPartialEventDef = (event: AnyDecoded | PartialAnyDecoded): event is PartialAnyDecoded =>
83
- 'id' in event === false && 'parentId' in event === false
85
+ 'num' in event === false && 'parentSeqNum' in event === false
84
86
 
85
87
  export type ForEventDefRecord<TEventDefRecord extends EventDefRecord> = Schema.Schema<
86
88
  {
87
89
  [K in keyof TEventDefRecord]: {
88
90
  name: K
89
91
  args: Schema.Schema.Type<TEventDefRecord[K]['schema']>
90
- id: EventId.EventId
91
- parentId: EventId.EventId
92
+ seqNum: EventSequenceNumber.EventSequenceNumber
93
+ parentSeqNum: EventSequenceNumber.EventSequenceNumber
92
94
  clientId: string
93
95
  sessionId: string
94
96
  }
@@ -97,8 +99,8 @@ export type ForEventDefRecord<TEventDefRecord extends EventDefRecord> = Schema.S
97
99
  [K in keyof TEventDefRecord]: {
98
100
  name: K
99
101
  args: Schema.Schema.Encoded<TEventDefRecord[K]['schema']>
100
- id: EventId.EventId
101
- parentId: EventId.EventId
102
+ seqNum: EventSequenceNumber.EventSequenceNumber
103
+ parentSeqNum: EventSequenceNumber.EventSequenceNumber
102
104
  clientId: string
103
105
  sessionId: string
104
106
  }
@@ -128,8 +130,8 @@ export const makeEventDefSchema = <TSchema extends LiveStoreSchema>(
128
130
  Schema.Struct({
129
131
  name: Schema.Literal(def.name),
130
132
  args: def.schema,
131
- id: EventId.EventId,
132
- parentId: EventId.EventId,
133
+ seqNum: EventSequenceNumber.EventSequenceNumber,
134
+ parentSeqNum: EventSequenceNumber.EventSequenceNumber,
133
135
  clientId: Schema.String,
134
136
  sessionId: Schema.String,
135
137
  }),
@@ -154,9 +156,8 @@ export const makeEventDefSchemaMemo = memoizeByRef(makeEventDefSchema)
154
156
  export class EncodedWithMeta extends Schema.Class<EncodedWithMeta>('LiveStoreEvent.EncodedWithMeta')({
155
157
  name: Schema.String,
156
158
  args: Schema.Any,
157
- // TODO rename to `.num` / `.parentNum`
158
- id: EventId.EventId,
159
- parentId: EventId.EventId,
159
+ seqNum: EventSequenceNumber.EventSequenceNumber,
160
+ parentSeqNum: EventSequenceNumber.EventSequenceNumber,
160
161
  clientId: Schema.String,
161
162
  sessionId: Schema.String,
162
163
  // TODO get rid of `meta` again by cleaning up the usage implementations
@@ -181,10 +182,10 @@ export class EncodedWithMeta extends Schema.Class<EncodedWithMeta>('LiveStoreEve
181
182
  }) {
182
183
  toJSON = (): any => {
183
184
  // Only used for logging/debugging
184
- // - More readable way to print the id + parentId
185
+ // - More readable way to print the seqNum + parentSeqNum
185
186
  // - not including `meta`, `clientId`, `sessionId`
186
187
  return {
187
- id: `${EventId.toString(this.id)} → ${EventId.toString(this.parentId)} (${this.clientId}, ${this.sessionId})`,
188
+ seqNum: `${EventSequenceNumber.toString(this.seqNum)} → ${EventSequenceNumber.toString(this.parentSeqNum)} (${this.clientId}, ${this.sessionId})`,
188
189
  name: this.name,
189
190
  args: this.args,
190
191
  }
@@ -192,46 +193,46 @@ export class EncodedWithMeta extends Schema.Class<EncodedWithMeta>('LiveStoreEve
192
193
 
193
194
  /**
194
195
  * Example: (global event)
195
- * For event id e2 → e1 which should be rebased on event id e3 → e2
196
- * the resulting event id will be e4 → e3
196
+ * For event e2 → e1 which should be rebased on event e3 → e2
197
+ * the resulting event num will be e4 → e3
197
198
  *
198
199
  * Example: (client event)
199
- * For event id e2+1 → e2 which should be rebased on event id e3 → e2
200
- * the resulting event id will be e3+1 → e3
200
+ * For event e2+1 → e2 which should be rebased on event e3 → e2
201
+ * the resulting event num will be e3+1 → e3
201
202
  *
202
203
  * Syntax: e2+2 → e2+1
203
204
  * ^ ^ ^ ^
204
- * | | | +- client parent id
205
- * | | +--- global parent id
206
- * | +-- client id
207
- * +---- global id
208
- * Client id is ommitted for global events
205
+ * | | | +- client parent number
206
+ * | | +--- global parent number
207
+ * | +-- client number
208
+ * +---- global number
209
+ * Client num is ommitted for global events
209
210
  */
210
- rebase = (parentId: EventId.EventId, isClient: boolean) =>
211
+ rebase = (parentSeqNum: EventSequenceNumber.EventSequenceNumber, isClient: boolean) =>
211
212
  new EncodedWithMeta({
212
213
  ...this,
213
- ...EventId.nextPair(parentId, isClient),
214
+ ...EventSequenceNumber.nextPair(parentSeqNum, isClient),
214
215
  })
215
216
 
216
217
  static fromGlobal = (event: AnyEncodedGlobal, syncMetadata: Option.Option<Schema.JsonValue>) =>
217
218
  new EncodedWithMeta({
218
219
  ...event,
219
- id: { global: event.id, client: EventId.clientDefault },
220
- parentId: { global: event.parentId, client: EventId.clientDefault },
220
+ seqNum: { global: event.seqNum, client: EventSequenceNumber.clientDefault },
221
+ parentSeqNum: { global: event.parentSeqNum, client: EventSequenceNumber.clientDefault },
221
222
  meta: { sessionChangeset: { _tag: 'unset' as const }, syncMetadata },
222
223
  })
223
224
 
224
225
  toGlobal = (): AnyEncodedGlobal => ({
225
226
  ...this,
226
- id: this.id.global,
227
- parentId: this.parentId.global,
227
+ seqNum: this.seqNum.global,
228
+ parentSeqNum: this.parentSeqNum.global,
228
229
  })
229
230
  }
230
231
 
231
232
  /** NOTE `meta` is not considered for equality */
232
233
  export const isEqualEncoded = (a: AnyEncoded, b: AnyEncoded) =>
233
- a.id.global === b.id.global &&
234
- a.id.client === b.id.client &&
234
+ a.seqNum.global === b.seqNum.global &&
235
+ a.seqNum.client === b.seqNum.client &&
235
236
  a.name === b.name &&
236
237
  a.clientId === b.clientId &&
237
238
  a.sessionId === b.sessionId &&
package/src/schema/mod.ts CHANGED
@@ -5,5 +5,5 @@ export * from './state/sqlite/schema-helpers.js'
5
5
  export * from './schema.js'
6
6
  export * as State from './state/mod.js'
7
7
  export * as LiveStoreEvent from './LiveStoreEvent.js'
8
- export * as EventId from './EventId.js'
8
+ export * as EventSequenceNumber from './EventSequenceNumber.js'
9
9
  export * as Events from './events.js'
@@ -269,6 +269,15 @@ describe('query builder', () => {
269
269
  "schema": "(ReadonlyArray<({ readonly count: number } <-> number)> <-> number)",
270
270
  }
271
271
  `)
272
+ expect(dump(db.todos.where('completed', true).count())).toMatchInlineSnapshot(`
273
+ {
274
+ "bindValues": [
275
+ 1,
276
+ ],
277
+ "query": "SELECT COUNT(*) as count FROM 'todos' WHERE completed = ?",
278
+ "schema": "(ReadonlyArray<({ readonly count: number } <-> number)> <-> number)",
279
+ }
280
+ `)
272
281
  })
273
282
 
274
283
  it('should handle NULL comparisons', () => {
@@ -122,12 +122,13 @@ export const makeQueryBuilder = <TResult, TTableDef extends TableDefBase>(
122
122
  return makeQueryBuilder(tableDef, { ...ast, offset: Option.some(offset) })
123
123
  },
124
124
  count: () => {
125
- if (isRowQuery(ast)) return invalidQueryBuilder()
125
+ if (isRowQuery(ast) || ast._tag === 'InsertQuery' || ast._tag === 'UpdateQuery' || ast._tag === 'DeleteQuery')
126
+ return invalidQueryBuilder()
126
127
 
127
128
  return makeQueryBuilder(tableDef, {
128
129
  _tag: 'CountQuery',
129
130
  tableDef,
130
- where: [],
131
+ where: ast.where,
131
132
  resultSchema: Schema.Struct({ count: Schema.Number }).pipe(
132
133
  Schema.pluck('count'),
133
134
  Schema.Array,
@@ -1,6 +1,6 @@
1
1
  import { Schema } from '@livestore/utils/effect'
2
2
 
3
- import * as EventId from '../../EventId.js'
3
+ import * as EventSequenceNumber from '../../EventSequenceNumber.js'
4
4
  import { SqliteDsl } from './db-schema/mod.js'
5
5
  import { table } from './table-def.js'
6
6
 
@@ -44,18 +44,19 @@ export const sessionChangesetMetaTable = table({
44
44
  name: SESSION_CHANGESET_META_TABLE,
45
45
  columns: {
46
46
  // TODO bring back primary key
47
- idGlobal: SqliteDsl.integer({ schema: EventId.GlobalEventId }),
48
- idClient: SqliteDsl.integer({ schema: EventId.ClientEventId }),
47
+ seqNumGlobal: SqliteDsl.integer({ schema: EventSequenceNumber.GlobalEventSequenceNumber }),
48
+ seqNumClient: SqliteDsl.integer({ schema: EventSequenceNumber.ClientEventSequenceNumber }),
49
49
  changeset: SqliteDsl.blob({ nullable: true }),
50
50
  debug: SqliteDsl.json({ nullable: true }),
51
51
  },
52
- indexes: [{ columns: ['idGlobal', 'idClient'], name: 'idx_session_changeset_id' }],
52
+ indexes: [{ columns: ['seqNumGlobal', 'seqNumClient'], name: 'idx_session_changeset_id' }],
53
53
  })
54
54
 
55
55
  export type SessionChangesetMetaRow = typeof sessionChangesetMetaTable.Type
56
56
 
57
57
  export const LEADER_MERGE_COUNTER_TABLE = '__livestore_leader_merge_counter'
58
58
 
59
+ // TODO get rid of this table in favour of client-only merge generation
59
60
  export const leaderMergeCounterTable = table({
60
61
  name: LEADER_MERGE_COUNTER_TABLE,
61
62
  columns: {
@@ -83,10 +84,10 @@ export const eventlogMetaTable = table({
83
84
  name: EVENTLOG_META_TABLE,
84
85
  columns: {
85
86
  // TODO Adjust modeling so a global event never needs a client id component
86
- idGlobal: SqliteDsl.integer({ primaryKey: true, schema: EventId.GlobalEventId }),
87
- idClient: SqliteDsl.integer({ primaryKey: true, schema: EventId.ClientEventId }),
88
- parentIdGlobal: SqliteDsl.integer({ schema: EventId.GlobalEventId }),
89
- parentIdClient: SqliteDsl.integer({ schema: EventId.ClientEventId }),
87
+ seqNumGlobal: SqliteDsl.integer({ primaryKey: true, schema: EventSequenceNumber.GlobalEventSequenceNumber }),
88
+ seqNumClient: SqliteDsl.integer({ primaryKey: true, schema: EventSequenceNumber.ClientEventSequenceNumber }),
89
+ parentSeqNumGlobal: SqliteDsl.integer({ schema: EventSequenceNumber.GlobalEventSequenceNumber }),
90
+ parentSeqNumClient: SqliteDsl.integer({ schema: EventSequenceNumber.ClientEventSequenceNumber }),
90
91
  name: SqliteDsl.text({}),
91
92
  argsJson: SqliteDsl.text({ schema: Schema.parseJson(Schema.Any) }),
92
93
  clientId: SqliteDsl.text({}),
@@ -95,8 +96,8 @@ export const eventlogMetaTable = table({
95
96
  syncMetadataJson: SqliteDsl.text({ schema: Schema.parseJson(Schema.Option(Schema.JsonValue)) }),
96
97
  },
97
98
  indexes: [
98
- { columns: ['idGlobal'], name: 'idx_eventlog_idGlobal' },
99
- { columns: ['idGlobal', 'idClient'], name: 'idx_eventlog_id' },
99
+ { columns: ['seqNumGlobal'], name: 'idx_eventlog_seqNumGlobal' },
100
+ { columns: ['seqNumGlobal', 'seqNumClient'], name: 'idx_eventlog_seqNum' },
100
101
  ],
101
102
  })
102
103
 
@@ -5,7 +5,7 @@ import { BucketQueue, Effect, FiberHandle, Queue, Schema, Stream, Subscribable }
5
5
  import * as otel from '@opentelemetry/api'
6
6
 
7
7
  import type { ClientSession, UnexpectedError } from '../adapter-types.js'
8
- import * as EventId from '../schema/EventId.js'
8
+ import * as EventSequenceNumber from '../schema/EventSequenceNumber.js'
9
9
  import * as LiveStoreEvent from '../schema/LiveStoreEvent.js'
10
10
  import { getEventDef, type LiveStoreSchema, SystemTables } from '../schema/mod.js'
11
11
  import { sql } from '../util.js'
@@ -77,16 +77,16 @@ export const makeClientSessionSyncProcessor = ({
77
77
  const push: ClientSessionSyncProcessor['push'] = (batch, { otelContext }) => {
78
78
  // TODO validate batch
79
79
 
80
- let baseEventId = syncStateRef.current.localHead
80
+ let baseEventSequenceNumber = syncStateRef.current.localHead
81
81
  const encodedEventDefs = batch.map(({ name, args }) => {
82
82
  const eventDef = getEventDef(schema, name)
83
- const nextIdPair = EventId.nextPair(baseEventId, eventDef.eventDef.options.clientOnly)
84
- baseEventId = nextIdPair.id
83
+ const nextNumPair = EventSequenceNumber.nextPair(baseEventSequenceNumber, eventDef.eventDef.options.clientOnly)
84
+ baseEventSequenceNumber = nextNumPair.seqNum
85
85
  return new LiveStoreEvent.EncodedWithMeta(
86
86
  Schema.encodeUnknownSync(eventSchema)({
87
87
  name,
88
88
  args,
89
- ...nextIdPair,
89
+ ...nextNumPair,
90
90
  clientId: clientSession.clientId,
91
91
  sessionId: clientSession.sessionId,
92
92
  }),
@@ -179,7 +179,7 @@ export const makeClientSessionSyncProcessor = ({
179
179
  // NOTE We need to lazily call `.pull` as we want the cursor to be updated
180
180
  yield* Stream.suspend(() =>
181
181
  clientSession.leaderThread.events.pull({
182
- cursor: { mergeCounter: getMergeCounter(), eventId: syncStateRef.current.localHead },
182
+ cursor: { mergeCounter: getMergeCounter(), eventNum: syncStateRef.current.localHead },
183
183
  }),
184
184
  ).pipe(
185
185
  Stream.tap(({ payload, mergeCounter: leaderMergeCounter }) =>