@livestore/common 0.3.0-dev.47 → 0.3.0-dev.48
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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapter-types.d.ts +8 -6
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js +2 -2
- package/dist/adapter-types.js.map +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +25 -25
- package/dist/devtools/devtools-messages-client-session.js +3 -3
- package/dist/devtools/devtools-messages-client-session.js.map +1 -1
- package/dist/devtools/devtools-messages-common.d.ts +6 -6
- package/dist/devtools/devtools-messages-leader.d.ts +30 -30
- package/dist/devtools/devtools-messages-leader.js +3 -3
- package/dist/devtools/devtools-messages-leader.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +27 -25
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/eventlog.d.ts +10 -10
- package/dist/leader-thread/eventlog.d.ts.map +1 -1
- package/dist/leader-thread/eventlog.js +24 -24
- package/dist/leader-thread/eventlog.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +1 -1
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/materialize-event.d.ts +3 -3
- package/dist/leader-thread/materialize-event.d.ts.map +1 -1
- package/dist/leader-thread/materialize-event.js +19 -15
- package/dist/leader-thread/materialize-event.js.map +1 -1
- package/dist/leader-thread/types.d.ts +4 -4
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/make-client-session.d.ts +1 -1
- package/dist/make-client-session.d.ts.map +1 -1
- package/dist/make-client-session.js +2 -1
- package/dist/make-client-session.js.map +1 -1
- package/dist/materializer-helper.d.ts.map +1 -1
- package/dist/materializer-helper.js +4 -2
- package/dist/materializer-helper.js.map +1 -1
- package/dist/rematerialize-from-eventlog.js +9 -9
- package/dist/rematerialize-from-eventlog.js.map +1 -1
- package/dist/schema/EventId.d.ts +28 -28
- package/dist/schema/EventId.d.ts.map +1 -1
- package/dist/schema/EventId.js +9 -9
- package/dist/schema/EventId.js.map +1 -1
- package/dist/schema/EventId.test.js +5 -5
- package/dist/schema/EventId.test.js.map +1 -1
- package/dist/schema/EventNumber.d.ts +57 -0
- package/dist/schema/EventNumber.d.ts.map +1 -0
- package/dist/schema/EventNumber.js +82 -0
- package/dist/schema/EventNumber.js.map +1 -0
- package/dist/schema/EventNumber.test.d.ts +2 -0
- package/dist/schema/EventNumber.test.d.ts.map +1 -0
- package/dist/schema/EventNumber.test.js +11 -0
- package/dist/schema/EventNumber.test.js.map +1 -0
- package/dist/schema/EventSequenceNumber.d.ts +57 -0
- package/dist/schema/EventSequenceNumber.d.ts.map +1 -0
- package/dist/schema/EventSequenceNumber.js +82 -0
- package/dist/schema/EventSequenceNumber.js.map +1 -0
- package/dist/schema/EventSequenceNumber.test.d.ts +2 -0
- package/dist/schema/EventSequenceNumber.test.d.ts.map +1 -0
- package/dist/schema/EventSequenceNumber.test.js +11 -0
- package/dist/schema/EventSequenceNumber.test.js.map +1 -0
- package/dist/schema/LiveStoreEvent.d.ts +81 -79
- package/dist/schema/LiveStoreEvent.d.ts.map +1 -1
- package/dist/schema/LiveStoreEvent.js +31 -32
- package/dist/schema/LiveStoreEvent.js.map +1 -1
- package/dist/schema/mod.d.ts +1 -1
- package/dist/schema/mod.d.ts.map +1 -1
- package/dist/schema/mod.js +1 -1
- package/dist/schema/mod.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.d.ts.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.js +2 -2
- package/dist/schema/state/sqlite/query-builder/impl.js.map +1 -1
- package/dist/schema/state/sqlite/query-builder/impl.test.d.ts +3 -3
- package/dist/schema/state/sqlite/query-builder/impl.test.js +9 -0
- package/dist/schema/state/sqlite/query-builder/impl.test.js.map +1 -1
- package/dist/schema/state/sqlite/system-tables.d.ts +52 -52
- package/dist/schema/state/sqlite/system-tables.d.ts.map +1 -1
- package/dist/schema/state/sqlite/system-tables.js +11 -10
- package/dist/schema/state/sqlite/system-tables.js.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +6 -6
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/next/compact-events.js +38 -35
- package/dist/sync/next/compact-events.js.map +1 -1
- package/dist/sync/next/facts.d.ts +4 -4
- package/dist/sync/next/facts.d.ts.map +1 -1
- package/dist/sync/next/facts.js +8 -8
- package/dist/sync/next/facts.js.map +1 -1
- package/dist/sync/next/history-dag-common.d.ts +4 -4
- package/dist/sync/next/history-dag-common.d.ts.map +1 -1
- package/dist/sync/next/history-dag-common.js +7 -4
- package/dist/sync/next/history-dag-common.js.map +1 -1
- package/dist/sync/next/history-dag.d.ts +0 -2
- package/dist/sync/next/history-dag.d.ts.map +1 -1
- package/dist/sync/next/history-dag.js +15 -13
- package/dist/sync/next/history-dag.js.map +1 -1
- package/dist/sync/next/rebase-events.d.ts.map +1 -1
- package/dist/sync/next/rebase-events.js +10 -4
- package/dist/sync/next/rebase-events.js.map +1 -1
- package/dist/sync/next/test/compact-events.calculator.test.js +13 -13
- package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
- package/dist/sync/next/test/compact-events.test.js +31 -31
- package/dist/sync/next/test/compact-events.test.js.map +1 -1
- package/dist/sync/next/test/event-fixtures.d.ts +10 -0
- package/dist/sync/next/test/event-fixtures.d.ts.map +1 -1
- package/dist/sync/next/test/event-fixtures.js +19 -13
- package/dist/sync/next/test/event-fixtures.js.map +1 -1
- package/dist/sync/sync.d.ts +11 -11
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/sync.js +5 -5
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/syncstate.d.ts +18 -18
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +46 -47
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +110 -110
- package/dist/sync/syncstate.test.js.map +1 -1
- package/dist/sync/validate-push-payload.d.ts +2 -2
- package/dist/sync/validate-push-payload.d.ts.map +1 -1
- package/dist/sync/validate-push-payload.js +4 -4
- package/dist/sync/validate-push-payload.js.map +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/package.json +4 -4
- package/src/adapter-types.ts +6 -4
- package/src/devtools/devtools-messages-client-session.ts +3 -3
- package/src/devtools/devtools-messages-leader.ts +3 -3
- package/src/leader-thread/LeaderSyncProcessor.ts +36 -29
- package/src/leader-thread/eventlog.ts +36 -31
- package/src/leader-thread/leader-worker-devtools.ts +1 -1
- package/src/leader-thread/materialize-event.ts +21 -17
- package/src/leader-thread/types.ts +4 -4
- package/src/make-client-session.ts +2 -0
- package/src/materializer-helper.ts +5 -2
- package/src/rematerialize-from-eventlog.ts +10 -10
- package/src/schema/EventSequenceNumber.test.ts +12 -0
- package/src/schema/EventSequenceNumber.ts +121 -0
- package/src/schema/LiveStoreEvent.ts +66 -65
- package/src/schema/mod.ts +1 -1
- package/src/schema/state/sqlite/query-builder/impl.test.ts +9 -0
- package/src/schema/state/sqlite/query-builder/impl.ts +3 -2
- package/src/schema/state/sqlite/system-tables.ts +11 -10
- package/src/sync/ClientSessionSyncProcessor.ts +6 -6
- package/src/sync/next/compact-events.ts +38 -35
- package/src/sync/next/facts.ts +12 -9
- package/src/sync/next/history-dag-common.ts +9 -6
- package/src/sync/next/history-dag.ts +15 -16
- package/src/sync/next/rebase-events.ts +10 -4
- package/src/sync/next/test/compact-events.calculator.test.ts +13 -13
- package/src/sync/next/test/compact-events.test.ts +31 -31
- package/src/sync/next/test/event-fixtures.ts +20 -13
- package/src/sync/sync.ts +7 -7
- package/src/sync/syncstate.test.ts +112 -112
- package/src/sync/syncstate.ts +58 -48
- package/src/sync/validate-push-payload.ts +5 -5
- package/src/version.ts +2 -2
- package/src/schema/EventId.test.ts +0 -12
- 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
|
-
|
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 {
|
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
|
-
|
60
|
-
|
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
|
75
|
-
ORDER BY
|
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: _.
|
91
|
-
Option.getOrElse(() =>
|
90
|
+
Option.map((_) => ({ global: _.seqNumGlobal, client: _.seqNumClient })),
|
91
|
+
Option.getOrElse(() => EventSequenceNumber.ROOT),
|
92
92
|
)
|
93
|
-
:
|
93
|
+
: EventSequenceNumber.ROOT
|
94
94
|
const nextItem = Chunk.fromIterable(
|
95
95
|
stmt.select<SystemTables.EventlogMetaRow>({
|
96
|
-
$
|
97
|
-
$
|
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
|
5
|
+
import * as EventSequenceNumber from './EventSequenceNumber.js'
|
6
6
|
import type { LiveStoreSchema } from './schema.js'
|
7
7
|
|
8
|
-
export
|
9
|
-
|
10
|
-
|
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
|
-
|
15
|
-
|
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
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
41
|
-
|
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 =
|
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
|
-
|
51
|
-
|
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
|
-
|
60
|
-
|
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 =
|
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']]:
|
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
|
-
'
|
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
|
-
|
91
|
-
|
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
|
-
|
101
|
-
|
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
|
-
|
132
|
-
|
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
|
-
|
158
|
-
|
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
|
185
|
+
// - More readable way to print the seqNum + parentSeqNum
|
185
186
|
// - not including `meta`, `clientId`, `sessionId`
|
186
187
|
return {
|
187
|
-
|
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
|
196
|
-
* the resulting event
|
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
|
200
|
-
* the resulting event
|
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
|
205
|
-
* | | +--- global parent
|
206
|
-
* | +-- client
|
207
|
-
* +---- global
|
208
|
-
* Client
|
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 = (
|
211
|
+
rebase = (parentSeqNum: EventSequenceNumber.EventSequenceNumber, isClient: boolean) =>
|
211
212
|
new EncodedWithMeta({
|
212
213
|
...this,
|
213
|
-
...
|
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
|
-
|
220
|
-
|
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
|
-
|
227
|
-
|
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.
|
234
|
-
a.
|
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
|
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)
|
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
|
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
|
-
|
48
|
-
|
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: ['
|
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
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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: ['
|
99
|
-
{ columns: ['
|
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
|
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
|
80
|
+
let baseEventSequenceNumber = syncStateRef.current.localHead
|
81
81
|
const encodedEventDefs = batch.map(({ name, args }) => {
|
82
82
|
const eventDef = getEventDef(schema, name)
|
83
|
-
const
|
84
|
-
|
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
|
-
...
|
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(),
|
182
|
+
cursor: { mergeCounter: getMergeCounter(), eventNum: syncStateRef.current.localHead },
|
183
183
|
}),
|
184
184
|
).pipe(
|
185
185
|
Stream.tap(({ payload, mergeCounter: leaderMergeCounter }) =>
|