@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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/fixture.d.ts +6 -6
- package/dist/adapter-types.d.ts +39 -16
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js +12 -0
- package/dist/adapter-types.js.map +1 -1
- package/dist/derived-mutations.js +3 -3
- package/dist/derived-mutations.js.map +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +23 -23
- package/dist/devtools/devtools-messages-common.d.ts +19 -6
- package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-common.js +14 -0
- package/dist/devtools/devtools-messages-common.js.map +1 -1
- package/dist/devtools/devtools-messages-leader.d.ts +94 -75
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-leader.js +18 -17
- package/dist/devtools/devtools-messages-leader.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +50 -35
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/apply-mutation.d.ts.map +1 -1
- package/dist/leader-thread/apply-mutation.js +8 -6
- package/dist/leader-thread/apply-mutation.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +26 -18
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +23 -6
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/mutationlog.d.ts +1 -1
- package/dist/leader-thread/mutationlog.d.ts.map +1 -1
- package/dist/leader-thread/mutationlog.js +7 -5
- package/dist/leader-thread/mutationlog.js.map +1 -1
- package/dist/leader-thread/pull-queue-set.d.ts.map +1 -1
- package/dist/leader-thread/recreate-db.d.ts +4 -2
- package/dist/leader-thread/recreate-db.d.ts.map +1 -1
- package/dist/leader-thread/recreate-db.js +12 -7
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/leader-thread/types.d.ts +19 -7
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/leader-thread/types.js +1 -1
- package/dist/leader-thread/types.js.map +1 -1
- package/dist/rehydrate-from-mutationlog.d.ts.map +1 -1
- package/dist/rehydrate-from-mutationlog.js +8 -6
- package/dist/rehydrate-from-mutationlog.js.map +1 -1
- package/dist/schema/EventId.d.ts +10 -9
- package/dist/schema/EventId.d.ts.map +1 -1
- package/dist/schema/EventId.js +14 -11
- package/dist/schema/EventId.js.map +1 -1
- package/dist/schema/EventId.test.js +3 -3
- package/dist/schema/EventId.test.js.map +1 -1
- package/dist/schema/MutationEvent.d.ts +37 -12
- package/dist/schema/MutationEvent.d.ts.map +1 -1
- package/dist/schema/MutationEvent.js +20 -4
- package/dist/schema/MutationEvent.js.map +1 -1
- package/dist/schema/mutations.d.ts +4 -8
- package/dist/schema/mutations.d.ts.map +1 -1
- package/dist/schema/mutations.js +2 -2
- package/dist/schema/mutations.js.map +1 -1
- package/dist/schema/system-tables.d.ts +38 -20
- package/dist/schema/system-tables.d.ts.map +1 -1
- package/dist/schema/system-tables.js +9 -6
- package/dist/schema/system-tables.js.map +1 -1
- package/dist/schema/table-def.d.ts +7 -7
- package/dist/schema/table-def.d.ts.map +1 -1
- package/dist/schema/table-def.js +2 -2
- package/dist/schema/table-def.js.map +1 -1
- package/dist/schema-management/migrations.d.ts +2 -2
- package/dist/schema-management/migrations.d.ts.map +1 -1
- package/dist/schema-management/migrations.js +6 -1
- package/dist/schema-management/migrations.js.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +20 -5
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/next/facts.js +1 -1
- package/dist/sync/next/facts.js.map +1 -1
- package/dist/sync/next/history-dag-common.d.ts +2 -0
- package/dist/sync/next/history-dag-common.d.ts.map +1 -1
- package/dist/sync/next/history-dag-common.js +3 -1
- package/dist/sync/next/history-dag-common.js.map +1 -1
- package/dist/sync/next/history-dag.d.ts.map +1 -1
- package/dist/sync/next/history-dag.js +1 -1
- package/dist/sync/next/history-dag.js.map +1 -1
- package/dist/sync/next/rebase-events.d.ts +3 -1
- package/dist/sync/next/rebase-events.d.ts.map +1 -1
- package/dist/sync/next/rebase-events.js +5 -3
- package/dist/sync/next/rebase-events.js.map +1 -1
- package/dist/sync/next/test/compact-events.calculator.test.js +12 -12
- package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
- package/dist/sync/next/test/compact-events.test.js +30 -30
- package/dist/sync/next/test/compact-events.test.js.map +1 -1
- package/dist/sync/next/test/mutation-fixtures.d.ts +1 -1
- package/dist/sync/next/test/mutation-fixtures.d.ts.map +1 -1
- package/dist/sync/next/test/mutation-fixtures.js +9 -7
- package/dist/sync/next/test/mutation-fixtures.js.map +1 -1
- package/dist/sync/sync.d.ts +2 -2
- package/dist/sync/syncstate.d.ts +9 -9
- package/dist/sync/syncstate.js +6 -6
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +18 -16
- package/dist/sync/syncstate.test.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/src/adapter-types.ts +33 -15
- package/src/derived-mutations.ts +3 -3
- package/src/devtools/devtools-messages-common.ts +34 -0
- package/src/devtools/devtools-messages-leader.ts +28 -18
- package/src/index.ts +1 -1
- package/src/leader-thread/LeaderSyncProcessor.ts +59 -38
- package/src/leader-thread/apply-mutation.ts +15 -5
- package/src/leader-thread/leader-worker-devtools.ts +33 -19
- package/src/leader-thread/make-leader-thread-layer.ts +28 -8
- package/src/leader-thread/mutationlog.ts +8 -6
- package/src/leader-thread/recreate-db.ts +18 -9
- package/src/leader-thread/types.ts +20 -5
- package/src/rehydrate-from-mutationlog.ts +8 -6
- package/src/schema/EventId.test.ts +3 -3
- package/src/schema/EventId.ts +20 -16
- package/src/schema/MutationEvent.ts +31 -6
- package/src/schema/mutations.ts +6 -19
- package/src/schema/system-tables.ts +9 -6
- package/src/schema/table-def.ts +8 -8
- package/src/schema-management/migrations.ts +9 -5
- package/src/sync/ClientSessionSyncProcessor.ts +25 -6
- package/src/sync/next/facts.ts +1 -1
- package/src/sync/next/history-dag-common.ts +5 -1
- package/src/sync/next/history-dag.ts +1 -1
- package/src/sync/next/rebase-events.ts +8 -2
- package/src/sync/next/test/compact-events.calculator.test.ts +12 -12
- package/src/sync/next/test/compact-events.test.ts +30 -30
- package/src/sync/next/test/mutation-fixtures.ts +10 -6
- package/src/sync/syncstate.test.ts +19 -17
- package/src/sync/syncstate.ts +6 -6
- 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: (
|
|
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<
|
|
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,
|
|
64
|
-
parentId: { global: row.parentIdGlobal,
|
|
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
|
|
77
|
-
ORDER BY idGlobal 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,
|
|
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
|
-
$
|
|
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,
|
|
9
|
-
expect(EventId.nextPair(e_0_0, false).id).toStrictEqual({ global: 1,
|
|
10
|
-
expect(EventId.nextPair(e_0_0, true).id).toStrictEqual({ global: 0,
|
|
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
|
})
|
package/src/schema/EventId.ts
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import { Brand, Schema } from '@livestore/utils/effect'
|
|
2
2
|
|
|
3
|
-
export type
|
|
4
|
-
export const localEventId = Brand.nominal<
|
|
5
|
-
export const
|
|
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
|
|
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
|
|
15
|
+
* and a client sequence number.
|
|
16
16
|
*
|
|
17
|
-
* The
|
|
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;
|
|
19
|
+
export type EventId = { global: GlobalEventId; client: ClientEventId }
|
|
20
20
|
|
|
21
21
|
export const EventId = Schema.Struct({
|
|
22
22
|
global: GlobalEventId,
|
|
23
|
-
|
|
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.
|
|
33
|
+
return a.client - b.client
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export const isEqual = (a: EventId, b: EventId) => a.global === b.global && a.
|
|
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,
|
|
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.
|
|
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,
|
|
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,
|
|
57
|
-
// NOTE we always point to `
|
|
58
|
-
parentId: { global: id.global,
|
|
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
|
|
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 =
|
|
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.
|
|
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,
|
|
168
|
-
parentId: { global: mutationEvent.parentId,
|
|
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.
|
|
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)
|
package/src/schema/mutations.ts
CHANGED
|
@@ -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
|
|
40
|
+
* When set to true, the mutation won't be synced across clients but
|
|
42
41
|
*/
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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', '
|
|
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', '
|
|
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
|
-
|
|
76
|
+
idClient: SqliteDsl.integer({ primaryKey: true, schema: EventId.ClientEventId }),
|
|
77
77
|
parentIdGlobal: SqliteDsl.integer({ schema: EventId.GlobalEventId }),
|
|
78
|
-
|
|
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', '
|
|
91
|
+
{ columns: ['idGlobal', 'idClient'], name: 'idx_mutationlog_id' },
|
|
89
92
|
],
|
|
90
93
|
},
|
|
91
94
|
)
|
package/src/schema/table-def.ts
CHANGED
|
@@ -61,7 +61,7 @@ export type TableOptionsInput = Partial<{
|
|
|
61
61
|
| boolean
|
|
62
62
|
| {
|
|
63
63
|
enabled: true
|
|
64
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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;
|
|
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;
|
|
271
|
+
? { enabled: true; clientOnly: boolean }
|
|
272
272
|
: TOptionsInput['deriveMutations'] extends false
|
|
273
273
|
? { enabled: false }
|
|
274
|
-
: TOptionsInput['deriveMutations'] extends { enabled: true;
|
|
274
|
+
: TOptionsInput['deriveMutations'] extends { enabled: true; clientOnly?: boolean }
|
|
275
275
|
? {
|
|
276
276
|
enabled: true
|
|
277
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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.
|
|
49
|
-
upstreamHead: clientSession.leaderThread.
|
|
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.
|
|
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.
|
|
68
|
+
const nextIdPair = EventId.nextPair(baseEventId, mutationDef.options.clientOnly)
|
|
70
69
|
baseEventId = nextIdPair.id
|
|
71
70
|
return new MutationEvent.EncodedWithMeta(
|
|
72
|
-
Schema.encodeUnknownSync(mutationEventSchema)({
|
|
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* () {
|
package/src/sync/next/facts.ts
CHANGED
|
@@ -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,
|
|
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.
|
|
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) {
|