@livestore/common 0.3.0-dev.0 → 0.3.0-dev.10
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 +26 -23
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js.map +1 -1
- package/dist/derived-mutations.d.ts +4 -4
- package/dist/derived-mutations.d.ts.map +1 -1
- package/dist/derived-mutations.test.js.map +1 -1
- package/dist/devtools/devtools-bridge.d.ts +2 -1
- package/dist/devtools/devtools-bridge.d.ts.map +1 -1
- package/dist/devtools/devtools-messages.d.ts +98 -110
- package/dist/devtools/devtools-messages.d.ts.map +1 -1
- package/dist/devtools/devtools-messages.js +9 -6
- package/dist/devtools/devtools-messages.js.map +1 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +37 -0
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -0
- package/dist/leader-thread/LeaderSyncProcessor.js +417 -0
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -0
- package/dist/leader-thread/apply-mutation.d.ts +5 -2
- package/dist/leader-thread/apply-mutation.d.ts.map +1 -1
- package/dist/leader-thread/apply-mutation.js +38 -26
- package/dist/leader-thread/apply-mutation.js.map +1 -1
- package/dist/leader-thread/leader-sync-processor.d.ts +2 -2
- package/dist/leader-thread/leader-sync-processor.d.ts.map +1 -1
- package/dist/leader-thread/leader-sync-processor.js +20 -12
- package/dist/leader-thread/leader-sync-processor.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +22 -66
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +8 -7
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +11 -5
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/mutationlog.d.ts +4 -17
- package/dist/leader-thread/mutationlog.d.ts.map +1 -1
- package/dist/leader-thread/mutationlog.js +2 -1
- 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.map +1 -1
- package/dist/leader-thread/recreate-db.js +9 -3
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/leader-thread/types.d.ts +17 -9
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/leader-thread/types.js.map +1 -1
- package/dist/mutation.d.ts +9 -2
- package/dist/mutation.d.ts.map +1 -1
- package/dist/mutation.js +5 -5
- package/dist/mutation.js.map +1 -1
- package/dist/query-builder/impl.d.ts +1 -1
- package/dist/rehydrate-from-mutationlog.d.ts +2 -2
- package/dist/rehydrate-from-mutationlog.d.ts.map +1 -1
- package/dist/rehydrate-from-mutationlog.js +13 -19
- package/dist/rehydrate-from-mutationlog.js.map +1 -1
- package/dist/schema/EventId.d.ts +16 -14
- package/dist/schema/EventId.d.ts.map +1 -1
- package/dist/schema/EventId.js +15 -7
- package/dist/schema/EventId.js.map +1 -1
- package/dist/schema/EventId.test.d.ts +2 -0
- package/dist/schema/EventId.test.d.ts.map +1 -0
- package/dist/schema/EventId.test.js +11 -0
- package/dist/schema/EventId.test.js.map +1 -0
- package/dist/schema/MutationEvent.d.ts +49 -80
- package/dist/schema/MutationEvent.d.ts.map +1 -1
- package/dist/schema/MutationEvent.js +32 -15
- package/dist/schema/MutationEvent.js.map +1 -1
- package/dist/schema/MutationEvent.test.d.ts +2 -0
- package/dist/schema/MutationEvent.test.d.ts.map +1 -0
- package/dist/schema/MutationEvent.test.js +2 -0
- package/dist/schema/MutationEvent.test.js.map +1 -0
- package/dist/schema/system-tables.d.ts +26 -26
- package/dist/schema/system-tables.d.ts.map +1 -1
- package/dist/schema/system-tables.js +19 -11
- package/dist/schema/system-tables.js.map +1 -1
- package/dist/schema-management/migrations.js +6 -6
- package/dist/schema-management/migrations.js.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +45 -0
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -0
- package/dist/sync/ClientSessionSyncProcessor.js +134 -0
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -0
- package/dist/sync/client-session-sync-processor.d.ts +4 -4
- package/dist/sync/client-session-sync-processor.d.ts.map +1 -1
- package/dist/sync/index.d.ts +1 -1
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +1 -1
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/next/history-dag-common.d.ts +1 -4
- package/dist/sync/next/history-dag-common.d.ts.map +1 -1
- package/dist/sync/next/history-dag-common.js +1 -1
- package/dist/sync/next/history-dag-common.js.map +1 -1
- package/dist/sync/next/rebase-events.d.ts +3 -3
- package/dist/sync/next/rebase-events.d.ts.map +1 -1
- package/dist/sync/next/rebase-events.js +3 -2
- package/dist/sync/next/rebase-events.js.map +1 -1
- package/dist/sync/next/test/mutation-fixtures.d.ts.map +1 -1
- package/dist/sync/next/test/mutation-fixtures.js +3 -9
- package/dist/sync/next/test/mutation-fixtures.js.map +1 -1
- package/dist/sync/sync.d.ts +21 -11
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/syncstate.d.ts +45 -23
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +56 -12
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +125 -69
- 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 +2 -2
- package/dist/sync/validate-push-payload.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +6 -5
- package/src/adapter-types.ts +22 -24
- package/src/derived-mutations.test.ts +1 -1
- package/src/derived-mutations.ts +9 -5
- package/src/devtools/devtools-bridge.ts +2 -1
- package/src/devtools/devtools-messages.ts +9 -6
- package/src/index.ts +0 -6
- package/src/leader-thread/{leader-sync-processor.ts → LeaderSyncProcessor.ts} +235 -230
- package/src/leader-thread/apply-mutation.ts +49 -31
- package/src/leader-thread/leader-worker-devtools.ts +30 -109
- package/src/leader-thread/make-leader-thread-layer.ts +24 -13
- package/src/leader-thread/mutationlog.ts +9 -5
- package/src/leader-thread/recreate-db.ts +9 -5
- package/src/leader-thread/types.ts +18 -11
- package/src/mutation.ts +17 -7
- package/src/rehydrate-from-mutationlog.ts +15 -23
- package/src/schema/EventId.test.ts +12 -0
- package/src/schema/EventId.ts +23 -9
- package/src/schema/MutationEvent.ts +46 -24
- package/src/schema/system-tables.ts +19 -11
- package/src/schema-management/migrations.ts +6 -6
- package/src/sync/{client-session-sync-processor.ts → ClientSessionSyncProcessor.ts} +11 -9
- package/src/sync/index.ts +1 -1
- package/src/sync/next/history-dag-common.ts +1 -1
- package/src/sync/next/rebase-events.ts +7 -7
- package/src/sync/next/test/mutation-fixtures.ts +3 -10
- package/src/sync/sync.ts +19 -6
- package/src/sync/syncstate.test.ts +127 -67
- package/src/sync/syncstate.ts +21 -19
- package/src/sync/validate-push-payload.ts +7 -4
- package/src/version.ts +1 -1
@@ -1,8 +1,8 @@
|
|
1
|
-
import {
|
1
|
+
import { memoizeByRef, shouldNeverHappen } from '@livestore/utils'
|
2
2
|
import { Chunk, Effect, Option, Schema, Stream } from '@livestore/utils/effect'
|
3
3
|
|
4
4
|
import { type MigrationOptionsFromMutationLog, type SynchronousDatabase, UnexpectedError } from './adapter-types.js'
|
5
|
-
import {
|
5
|
+
import { makeApplyMutation } from './leader-thread/apply-mutation.js'
|
6
6
|
import type { LiveStoreSchema, MutationDef, MutationEvent, MutationLogMetaRow } from './schema/mod.js'
|
7
7
|
import { EventId, MUTATION_LOG_META_TABLE } from './schema/mod.js'
|
8
8
|
import type { PreparedBindValues } from './util.js'
|
@@ -10,7 +10,8 @@ import { sql } from './util.js'
|
|
10
10
|
|
11
11
|
export const rehydrateFromMutationLog = ({
|
12
12
|
logDb,
|
13
|
-
db
|
13
|
+
// TODO re-use this db when bringing back the boot in-memory db implementation
|
14
|
+
// db,
|
14
15
|
schema,
|
15
16
|
migrationOptions,
|
16
17
|
onProgress,
|
@@ -28,6 +29,8 @@ export const rehydrateFromMutationLog = ({
|
|
28
29
|
|
29
30
|
const hashMutation = memoizeByRef((mutation: MutationDef.Any) => Schema.hash(mutation.schema))
|
30
31
|
|
32
|
+
const applyMutation = yield* makeApplyMutation
|
33
|
+
|
31
34
|
const processMutation = (row: MutationLogMetaRow) =>
|
32
35
|
Effect.gen(function* () {
|
33
36
|
const mutationDef = schema.mutations.get(row.mutation) ?? shouldNeverHappen(`Unknown mutation ${row.mutation}`)
|
@@ -40,7 +43,10 @@ export const rehydrateFromMutationLog = ({
|
|
40
43
|
)
|
41
44
|
}
|
42
45
|
|
43
|
-
const
|
46
|
+
const args = JSON.parse(row.argsJson)
|
47
|
+
|
48
|
+
// Checking whether the schema has changed in an incompatible way
|
49
|
+
yield* Schema.decodeUnknown(mutationDef.schema)(args).pipe(
|
44
50
|
Effect.mapError((cause) =>
|
45
51
|
UnexpectedError.make({
|
46
52
|
cause,
|
@@ -53,28 +59,14 @@ This likely means the schema has changed in an incompatible way.
|
|
53
59
|
),
|
54
60
|
)
|
55
61
|
|
56
|
-
const
|
62
|
+
const mutationEventEncoded = {
|
57
63
|
id: { global: row.idGlobal, local: row.idLocal },
|
58
64
|
parentId: { global: row.parentIdGlobal, local: row.parentIdLocal },
|
59
65
|
mutation: row.mutation,
|
60
|
-
args
|
61
|
-
} satisfies MutationEvent.
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
const makeExecuteOptions = (statementSql: string, bindValues: any) => ({
|
66
|
-
onRowsChanged: (rowsChanged: number) => {
|
67
|
-
if (rowsChanged === 0 && migrationOptions.logging?.excludeAffectedRows?.(statementSql) !== true) {
|
68
|
-
console.warn(`Mutation "${mutationDef.name}" did not affect any rows:`, statementSql, bindValues)
|
69
|
-
}
|
70
|
-
},
|
71
|
-
})
|
72
|
-
|
73
|
-
for (const { statementSql, bindValues } of execArgsArr) {
|
74
|
-
// TODO cache prepared statements for mutations
|
75
|
-
db.execute(statementSql, bindValues, isDevEnv() ? makeExecuteOptions(statementSql, bindValues) : undefined)
|
76
|
-
// console.log(`Re-executed mutation ${mutationSql}`, bindValues)
|
77
|
-
}
|
66
|
+
args,
|
67
|
+
} satisfies MutationEvent.AnyEncoded
|
68
|
+
|
69
|
+
yield* applyMutation(mutationEventEncoded, { skipMutationLog: true })
|
78
70
|
}).pipe(Effect.withSpan(`@livestore/common:rehydrateFromMutationLog:processMutation`))
|
79
71
|
|
80
72
|
const CHUNK_SIZE = 100
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { Vitest } from '@livestore/utils/node-vitest'
|
2
|
+
import { expect } from 'vitest'
|
3
|
+
|
4
|
+
import { EventId } from './mod.js'
|
5
|
+
|
6
|
+
Vitest.describe('EventId', () => {
|
7
|
+
Vitest.test('nextPair', () => {
|
8
|
+
const e_0_0 = EventId.make({ global: 0, local: 0 })
|
9
|
+
expect(EventId.nextPair(e_0_0, false).id).toStrictEqual({ global: 1, local: 0 })
|
10
|
+
expect(EventId.nextPair(e_0_0, true).id).toStrictEqual({ global: 0, local: 1 })
|
11
|
+
})
|
12
|
+
})
|
package/src/schema/EventId.ts
CHANGED
@@ -1,4 +1,14 @@
|
|
1
|
-
import { Schema } from '@livestore/utils/effect'
|
1
|
+
import { Brand, Schema } from '@livestore/utils/effect'
|
2
|
+
|
3
|
+
export type LocalEventId = Brand.Branded<number, 'LocalEventId'>
|
4
|
+
export const localEventId = Brand.nominal<LocalEventId>()
|
5
|
+
export const LocalEventId = Schema.fromBrand(localEventId)(Schema.Int)
|
6
|
+
|
7
|
+
export type GlobalEventId = Brand.Branded<number, 'GlobalEventId'>
|
8
|
+
export const globalEventId = Brand.nominal<GlobalEventId>()
|
9
|
+
export const GlobalEventId = Schema.fromBrand(globalEventId)(Schema.Int)
|
10
|
+
|
11
|
+
export const localDefault = 0 as any as LocalEventId
|
2
12
|
|
3
13
|
/**
|
4
14
|
* LiveStore event id value consisting of a globally unique event sequence number
|
@@ -6,11 +16,11 @@ import { Schema } from '@livestore/utils/effect'
|
|
6
16
|
*
|
7
17
|
* The local sequence number is only used for localOnly mutations and starts from 0 for each global sequence number.
|
8
18
|
*/
|
9
|
-
export type EventId = { global:
|
19
|
+
export type EventId = { global: GlobalEventId; local: LocalEventId }
|
10
20
|
|
11
21
|
export const EventId = Schema.Struct({
|
12
|
-
global:
|
13
|
-
local:
|
22
|
+
global: GlobalEventId,
|
23
|
+
local: LocalEventId,
|
14
24
|
}).annotations({ title: 'LiveStore.EventId' })
|
15
25
|
|
16
26
|
/**
|
@@ -27,20 +37,24 @@ export const isEqual = (a: EventId, b: EventId) => a.global === b.global && a.lo
|
|
27
37
|
|
28
38
|
export type EventIdPair = { id: EventId; parentId: EventId }
|
29
39
|
|
30
|
-
export const ROOT = { global: -1, local:
|
40
|
+
export const ROOT = { global: -1 as any as GlobalEventId, local: localDefault } satisfies EventId
|
31
41
|
|
32
42
|
export const isGreaterThan = (a: EventId, b: EventId) => {
|
33
43
|
return a.global > b.global || (a.global === b.global && a.local > b.local)
|
34
44
|
}
|
35
45
|
|
36
|
-
export const
|
46
|
+
export const make = (id: EventId | typeof EventId.Encoded): EventId => {
|
47
|
+
return Schema.is(EventId)(id) ? id : Schema.decodeSync(EventId)(id)
|
48
|
+
}
|
49
|
+
|
50
|
+
export const nextPair = (id: EventId, isLocal: boolean): EventIdPair => {
|
37
51
|
if (isLocal) {
|
38
|
-
return { id: { global: id.global, local: id.local + 1 }, parentId: id }
|
52
|
+
return { id: { global: id.global, local: (id.local + 1) as any as LocalEventId }, parentId: id }
|
39
53
|
}
|
40
54
|
|
41
55
|
return {
|
42
|
-
id: { global: id.global + 1, local:
|
56
|
+
id: { global: (id.global + 1) as any as GlobalEventId, local: localDefault },
|
43
57
|
// NOTE we always point to `local: 0` for non-localOnly mutations
|
44
|
-
parentId: { global: id.global, local:
|
58
|
+
parentId: { global: id.global, local: localDefault },
|
45
59
|
}
|
46
60
|
}
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import { memoizeByRef } from '@livestore/utils'
|
2
|
-
import type { Deferred } from '@livestore/utils/effect'
|
3
2
|
import { Schema } from '@livestore/utils/effect'
|
4
3
|
|
5
4
|
import * as EventId from './EventId.js'
|
@@ -30,10 +29,31 @@ export type MutationEventEncoded<TMutationsDef extends MutationDef.Any> = {
|
|
30
29
|
parentId: EventId.EventId
|
31
30
|
}
|
32
31
|
|
33
|
-
export type
|
32
|
+
export type AnyDecoded = MutationEvent<MutationDef.Any>
|
33
|
+
export const AnyDecoded = Schema.Struct({
|
34
|
+
mutation: Schema.String,
|
35
|
+
args: Schema.Any,
|
36
|
+
id: EventId.EventId,
|
37
|
+
parentId: EventId.EventId,
|
38
|
+
}).annotations({ title: 'MutationEvent.AnyDecoded' })
|
39
|
+
|
34
40
|
export type AnyEncoded = MutationEventEncoded<MutationDef.Any>
|
41
|
+
export const AnyEncoded = Schema.Struct({
|
42
|
+
mutation: Schema.String,
|
43
|
+
args: Schema.Any,
|
44
|
+
id: EventId.EventId,
|
45
|
+
parentId: EventId.EventId,
|
46
|
+
}).annotations({ title: 'MutationEvent.AnyEncoded' })
|
47
|
+
|
48
|
+
export const AnyEncodedGlobal = Schema.Struct({
|
49
|
+
mutation: Schema.String,
|
50
|
+
args: Schema.Any,
|
51
|
+
id: EventId.GlobalEventId,
|
52
|
+
parentId: EventId.GlobalEventId,
|
53
|
+
}).annotations({ title: 'MutationEvent.AnyEncodedGlobal' })
|
54
|
+
export type AnyEncodedGlobal = typeof AnyEncodedGlobal.Type
|
35
55
|
|
36
|
-
export type
|
56
|
+
export type PartialAnyDecoded = MutationEventPartial<MutationDef.Any>
|
37
57
|
export type PartialAnyEncoded = MutationEventPartialEncoded<MutationDef.Any>
|
38
58
|
|
39
59
|
export type PartialForSchema<TSchema extends LiveStoreSchema> = {
|
@@ -44,8 +64,9 @@ export type ForSchema<TSchema extends LiveStoreSchema> = {
|
|
44
64
|
[K in keyof TSchema['_MutationDefMapType']]: MutationEvent<TSchema['_MutationDefMapType'][K]>
|
45
65
|
}[keyof TSchema['_MutationDefMapType']]
|
46
66
|
|
47
|
-
export const isPartialMutationEvent = (
|
48
|
-
|
67
|
+
export const isPartialMutationEvent = (
|
68
|
+
mutationEvent: AnyDecoded | PartialAnyDecoded,
|
69
|
+
): mutationEvent is PartialAnyDecoded => 'id' in mutationEvent === false && 'parentId' in mutationEvent === false
|
49
70
|
|
50
71
|
export type ForMutationDefRecord<TMutationsDefRecord extends MutationDefRecord> = Schema.Schema<
|
51
72
|
{
|
@@ -105,33 +126,21 @@ export const makeMutationEventPartialSchema = <TSchema extends LiveStoreSchema>(
|
|
105
126
|
args: def.schema,
|
106
127
|
}),
|
107
128
|
),
|
108
|
-
).annotations({ title: '
|
129
|
+
).annotations({ title: 'MutationEventPartial' }) as any
|
109
130
|
|
110
131
|
export const makeMutationEventSchemaMemo = memoizeByRef(makeMutationEventSchema)
|
111
132
|
|
112
|
-
|
113
|
-
mutation: Schema.String,
|
114
|
-
args: Schema.Any,
|
115
|
-
id: EventId.EventId,
|
116
|
-
parentId: EventId.EventId,
|
117
|
-
}).annotations({ title: 'MutationEvent.Any' })
|
118
|
-
|
119
|
-
export const DecodedAny = Schema.typeSchema(Any).annotations({
|
120
|
-
title: 'MutationEvent.DecodedAny',
|
121
|
-
})
|
122
|
-
|
123
|
-
export const EncodedAny = Schema.encodedSchema(Any).annotations({
|
124
|
-
title: 'MutationEvent.EncodedAny',
|
125
|
-
})
|
126
|
-
|
127
|
-
/** Equivalent to EncodedAny but with a meta field and some convenience methods */
|
133
|
+
/** Equivalent to AnyEncoded but with a meta field and some convenience methods */
|
128
134
|
export class EncodedWithMeta extends Schema.Class<EncodedWithMeta>('MutationEvent.EncodedWithMeta')({
|
129
135
|
mutation: Schema.String,
|
130
136
|
args: Schema.Any,
|
131
137
|
id: EventId.EventId,
|
132
138
|
parentId: EventId.EventId,
|
139
|
+
// TODO get rid of `meta` again by cleaning up the usage implementations
|
133
140
|
meta: Schema.optionalWith(
|
134
|
-
Schema.Any as Schema.Schema<{
|
141
|
+
Schema.Any as Schema.Schema<{
|
142
|
+
sessionChangeset?: Uint8Array
|
143
|
+
}>,
|
135
144
|
{ default: () => ({}) },
|
136
145
|
),
|
137
146
|
}) {
|
@@ -149,8 +158,21 @@ export class EncodedWithMeta extends Schema.Class<EncodedWithMeta>('MutationEven
|
|
149
158
|
rebase = (parentId: EventId.EventId, isLocal: boolean) =>
|
150
159
|
new EncodedWithMeta({
|
151
160
|
...this,
|
152
|
-
...EventId.nextPair(
|
161
|
+
...EventId.nextPair(parentId, isLocal),
|
153
162
|
})
|
163
|
+
|
164
|
+
static fromGlobal = (mutationEvent: AnyEncodedGlobal) =>
|
165
|
+
new EncodedWithMeta({
|
166
|
+
...mutationEvent,
|
167
|
+
id: { global: mutationEvent.id, local: EventId.localDefault },
|
168
|
+
parentId: { global: mutationEvent.parentId, local: EventId.localDefault },
|
169
|
+
})
|
170
|
+
|
171
|
+
toGlobal = (): AnyEncodedGlobal => ({
|
172
|
+
...this,
|
173
|
+
id: this.id.global,
|
174
|
+
parentId: this.parentId.global,
|
175
|
+
})
|
154
176
|
}
|
155
177
|
|
156
178
|
export const isEqualEncoded = (a: AnyEncoded, b: AnyEncoded) =>
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { type SqliteAst as __SqliteAst, SqliteDsl } from '@livestore/db-schema'
|
2
2
|
import { Schema } from '@livestore/utils/effect'
|
3
3
|
|
4
|
+
import * as EventId from './EventId.js'
|
4
5
|
import type { FromTable } from './table-def.js'
|
5
6
|
import { table } from './table-def.js'
|
6
7
|
|
@@ -46,14 +47,15 @@ export const sessionChangesetMetaTable = table(
|
|
46
47
|
SESSION_CHANGESET_META_TABLE,
|
47
48
|
{
|
48
49
|
// TODO bring back primary key
|
49
|
-
idGlobal: SqliteDsl.integer({}),
|
50
|
-
idLocal: SqliteDsl.integer({}),
|
51
|
-
|
52
|
-
// idLocal: SqliteDsl.integer({ primaryKey: true }),
|
53
|
-
changeset: SqliteDsl.blob({}),
|
50
|
+
idGlobal: SqliteDsl.integer({ schema: EventId.GlobalEventId }),
|
51
|
+
idLocal: SqliteDsl.integer({ schema: EventId.LocalEventId }),
|
52
|
+
changeset: SqliteDsl.blob({ nullable: true }),
|
54
53
|
debug: SqliteDsl.json({ nullable: true }),
|
55
54
|
},
|
56
|
-
{
|
55
|
+
{
|
56
|
+
disableAutomaticIdColumn: true,
|
57
|
+
indexes: [{ columns: ['idGlobal', 'idLocal'], name: 'idx_session_changeset_id' }],
|
58
|
+
},
|
57
59
|
)
|
58
60
|
|
59
61
|
export type SessionChangesetMetaRow = FromTable.RowDecoded<typeof sessionChangesetMetaTable>
|
@@ -70,16 +72,22 @@ export const MUTATION_LOG_META_TABLE = 'mutation_log'
|
|
70
72
|
export const mutationLogMetaTable = table(
|
71
73
|
MUTATION_LOG_META_TABLE,
|
72
74
|
{
|
73
|
-
idGlobal: SqliteDsl.integer({ primaryKey: true }),
|
74
|
-
idLocal: SqliteDsl.integer({ primaryKey: true }),
|
75
|
-
parentIdGlobal: SqliteDsl.integer({}),
|
76
|
-
parentIdLocal: SqliteDsl.integer({}),
|
75
|
+
idGlobal: SqliteDsl.integer({ primaryKey: true, schema: EventId.GlobalEventId }),
|
76
|
+
idLocal: SqliteDsl.integer({ primaryKey: true, schema: EventId.LocalEventId }),
|
77
|
+
parentIdGlobal: SqliteDsl.integer({ schema: EventId.GlobalEventId }),
|
78
|
+
parentIdLocal: SqliteDsl.integer({ schema: EventId.LocalEventId }),
|
77
79
|
mutation: SqliteDsl.text({}),
|
78
80
|
argsJson: SqliteDsl.text({ schema: Schema.parseJson(Schema.Any) }),
|
79
81
|
schemaHash: SqliteDsl.integer({}),
|
80
82
|
syncMetadataJson: SqliteDsl.text({ schema: Schema.parseJson(Schema.Option(Schema.JsonValue)) }),
|
81
83
|
},
|
82
|
-
{
|
84
|
+
{
|
85
|
+
disableAutomaticIdColumn: true,
|
86
|
+
indexes: [
|
87
|
+
{ columns: ['idGlobal'], name: 'idx_idGlobal' },
|
88
|
+
{ columns: ['idGlobal', 'idLocal'], name: 'idx_mutationlog_id' },
|
89
|
+
],
|
90
|
+
},
|
83
91
|
)
|
84
92
|
|
85
93
|
export type MutationLogMetaRow = FromTable.RowDecoded<typeof mutationLogMetaTable>
|
@@ -129,10 +129,10 @@ export const migrateTable = ({
|
|
129
129
|
|
130
130
|
if (behaviour === 'drop-and-recreate') {
|
131
131
|
// TODO need to possibly handle cascading deletes due to foreign keys
|
132
|
-
dbExecute(db, sql`drop table if exists ${tableName}`)
|
133
|
-
dbExecute(db, sql`create table if not exists ${tableName} (${columnSpec}) strict`)
|
132
|
+
dbExecute(db, sql`drop table if exists '${tableName}'`)
|
133
|
+
dbExecute(db, sql`create table if not exists '${tableName}' (${columnSpec}) strict`)
|
134
134
|
} else if (behaviour === 'create-if-not-exists') {
|
135
|
-
dbExecute(db, sql`create table if not exists ${tableName} (${columnSpec}) strict`)
|
135
|
+
dbExecute(db, sql`create table if not exists '${tableName}' (${columnSpec}) strict`)
|
136
136
|
}
|
137
137
|
|
138
138
|
for (const index of tableAst.indexes) {
|
@@ -162,11 +162,11 @@ export const migrateTable = ({
|
|
162
162
|
|
163
163
|
const createIndexFromDefinition = (tableName: string, index: SqliteAst.Index) => {
|
164
164
|
const uniqueStr = index.unique ? 'UNIQUE' : ''
|
165
|
-
return sql`create ${uniqueStr} index if not exists ${index.name} on ${tableName} (${index.columns.join(', ')})`
|
165
|
+
return sql`create ${uniqueStr} index if not exists '${index.name}' on '${tableName}' (${index.columns.join(', ')})`
|
166
166
|
}
|
167
167
|
|
168
168
|
export const makeColumnSpec = (tableAst: SqliteAst.Table) => {
|
169
|
-
const primaryKeys = tableAst.columns.filter((_) => _.primaryKey).map((_) => _.name)
|
169
|
+
const primaryKeys = tableAst.columns.filter((_) => _.primaryKey).map((_) => `'${_.name}'`)
|
170
170
|
const columnDefStrs = tableAst.columns.map(toSqliteColumnSpec)
|
171
171
|
if (primaryKeys.length > 0) {
|
172
172
|
columnDefStrs.push(`PRIMARY KEY (${primaryKeys.join(', ')})`)
|
@@ -191,5 +191,5 @@ const toSqliteColumnSpec = (column: SqliteAst.Column) => {
|
|
191
191
|
return `default ${encodedDefaultValue}`
|
192
192
|
})()
|
193
193
|
|
194
|
-
return
|
194
|
+
return `'${column.name}' ${columnTypeStr} ${nullableStr} ${defaultValueStr}`
|
195
195
|
}
|
@@ -3,12 +3,11 @@ import type { Scope } from '@livestore/utils/effect'
|
|
3
3
|
import { Effect, Schema, Stream } from '@livestore/utils/effect'
|
4
4
|
import * as otel from '@opentelemetry/api'
|
5
5
|
|
6
|
-
import type {
|
6
|
+
import type { ClientSessionLeaderThreadProxy, UnexpectedError } from '../adapter-types.js'
|
7
7
|
import * as EventId from '../schema/EventId.js'
|
8
8
|
import { type LiveStoreSchema } from '../schema/mod.js'
|
9
9
|
import * as MutationEvent from '../schema/MutationEvent.js'
|
10
|
-
import
|
11
|
-
import { updateSyncState } from './syncstate.js'
|
10
|
+
import { SyncState, updateSyncState } from './syncstate.js'
|
12
11
|
|
13
12
|
/**
|
14
13
|
* Rebase behaviour:
|
@@ -32,9 +31,9 @@ export const makeClientSessionSyncProcessor = ({
|
|
32
31
|
schema: LiveStoreSchema
|
33
32
|
initialLeaderHead: EventId.EventId
|
34
33
|
pushToLeader: (batch: ReadonlyArray<MutationEvent.AnyEncoded>) => void
|
35
|
-
pullFromLeader:
|
34
|
+
pullFromLeader: ClientSessionLeaderThreadProxy['mutations']['pull']
|
36
35
|
applyMutation: (
|
37
|
-
mutationEventDecoded: MutationEvent.
|
36
|
+
mutationEventDecoded: MutationEvent.PartialAnyDecoded,
|
38
37
|
options: { otelContext: otel.Context; withChangeset: boolean },
|
39
38
|
) => {
|
40
39
|
writeTables: Set<string>
|
@@ -42,19 +41,18 @@ export const makeClientSessionSyncProcessor = ({
|
|
42
41
|
}
|
43
42
|
rollback: (changeset: Uint8Array) => void
|
44
43
|
refreshTables: (tables: Set<string>) => void
|
45
|
-
// rebaseBehaviour: 'auto-rebase' | 'manual-rebase'
|
46
44
|
span: otel.Span
|
47
45
|
}): ClientSessionSyncProcessor => {
|
48
46
|
const mutationEventSchema = MutationEvent.makeMutationEventSchemaMemo(schema)
|
49
47
|
|
50
48
|
const syncStateRef = {
|
51
|
-
current: {
|
49
|
+
current: new SyncState({
|
52
50
|
localHead: initialLeaderHead,
|
53
51
|
upstreamHead: initialLeaderHead,
|
54
52
|
pending: [],
|
55
53
|
// TODO init rollbackTail from leader to be ready for backend rebasing
|
56
54
|
rollbackTail: [],
|
57
|
-
}
|
55
|
+
}),
|
58
56
|
}
|
59
57
|
|
60
58
|
const isLocalEvent = (mutationEventEncoded: MutationEvent.EncodedWithMeta) => {
|
@@ -104,6 +102,7 @@ export const makeClientSessionSyncProcessor = ({
|
|
104
102
|
mutationEvent.meta.sessionChangeset = res.sessionChangeset
|
105
103
|
}
|
106
104
|
|
105
|
+
// console.debug('pushToLeader', encodedMutationEvents.length, ...encodedMutationEvents.map((_) => _.toJSON()))
|
107
106
|
pushToLeader(encodedMutationEvents)
|
108
107
|
|
109
108
|
return { writeTables }
|
@@ -155,6 +154,8 @@ export const makeClientSessionSyncProcessor = ({
|
|
155
154
|
event.meta.sessionChangeset = undefined
|
156
155
|
}
|
157
156
|
}
|
157
|
+
|
158
|
+
pushToLeader(updateResult.newSyncState.pending)
|
158
159
|
} else {
|
159
160
|
span.addEvent('pull:advance', {
|
160
161
|
payloadTag: payload._tag,
|
@@ -182,6 +183,7 @@ export const makeClientSessionSyncProcessor = ({
|
|
182
183
|
}),
|
183
184
|
),
|
184
185
|
Stream.runDrain,
|
186
|
+
Effect.forever, // NOTE Whenever the leader changes, we need to re-start the stream
|
185
187
|
Effect.tapCauseLogPretty,
|
186
188
|
Effect.forkScoped,
|
187
189
|
)
|
@@ -196,7 +198,7 @@ export const makeClientSessionSyncProcessor = ({
|
|
196
198
|
|
197
199
|
export interface ClientSessionSyncProcessor {
|
198
200
|
push: (
|
199
|
-
batch: ReadonlyArray<MutationEvent.
|
201
|
+
batch: ReadonlyArray<MutationEvent.PartialAnyDecoded>,
|
200
202
|
options: { otelContext: otel.Context },
|
201
203
|
) => {
|
202
204
|
writeTables: Set<string>
|
package/src/sync/index.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 = { global: EventId.ROOT.global - 1, local:
|
23
|
+
export const rootParentId = EventId.make({ global: EventId.ROOT.global - 1, local: EventId.localDefault })
|
24
24
|
|
25
25
|
export type HistoryDagNode = {
|
26
26
|
id: EventId.EventId
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import
|
1
|
+
import * as EventId from '../../schema/EventId.js'
|
2
2
|
import type * as MutationEvent from '../../schema/MutationEvent.js'
|
3
3
|
import type { MutationDef, MutationEventFactsSnapshot } from '../../schema/mutations.js'
|
4
4
|
import {
|
@@ -19,13 +19,13 @@ export type RebaseInput = {
|
|
19
19
|
newRemoteEvents: RebaseEventWithConflict[]
|
20
20
|
pendingLocalEvents: RebaseEventWithConflict[]
|
21
21
|
validate: (args: {
|
22
|
-
rebasedLocalEvents: MutationEvent.
|
22
|
+
rebasedLocalEvents: MutationEvent.PartialAnyDecoded[]
|
23
23
|
mutationDefs: Record<string, MutationDef.Any>
|
24
24
|
}) => FactValidationResult
|
25
25
|
}
|
26
26
|
|
27
27
|
export type RebaseOutput = {
|
28
|
-
rebasedLocalEvents: MutationEvent.
|
28
|
+
rebasedLocalEvents: MutationEvent.PartialAnyDecoded[]
|
29
29
|
}
|
30
30
|
|
31
31
|
export type RebaseFn = (input: RebaseInput) => RebaseOutput
|
@@ -48,7 +48,7 @@ export const rebaseEvents = ({
|
|
48
48
|
newRemoteEvents: HistoryDagNode[]
|
49
49
|
rebaseFn: RebaseFn
|
50
50
|
currentFactsSnapshot: MutationEventFactsSnapshot
|
51
|
-
}): MutationEvent.
|
51
|
+
}): ReadonlyArray<MutationEvent.AnyDecoded> => {
|
52
52
|
const initialSnapshot = new Map(currentFactsSnapshot)
|
53
53
|
applyFactGroups(
|
54
54
|
newRemoteEvents.map((event) => event.factsGroup),
|
@@ -89,10 +89,10 @@ export const rebaseEvents = ({
|
|
89
89
|
return rebasedLocalEvents.map(
|
90
90
|
(event, index) =>
|
91
91
|
({
|
92
|
-
id: { global: headGlobalId + index + 1, local:
|
93
|
-
parentId: { global: headGlobalId + index, local:
|
92
|
+
id: EventId.make({ global: headGlobalId + index + 1, local: EventId.localDefault }),
|
93
|
+
parentId: EventId.make({ global: headGlobalId + index, local: EventId.localDefault }),
|
94
94
|
mutation: event.mutation,
|
95
95
|
args: event.args,
|
96
|
-
}) satisfies MutationEvent.
|
96
|
+
}) satisfies MutationEvent.AnyDecoded,
|
97
97
|
)
|
98
98
|
}
|
@@ -140,16 +140,9 @@ export const toEventNodes = (
|
|
140
140
|
|
141
141
|
let currentEventId: EventId.EventId = EventId.ROOT
|
142
142
|
|
143
|
-
const getNextEventId = (mutationDef: MutationDef.Any): EventId.EventId => {
|
144
|
-
if (mutationDef.options.localOnly) {
|
145
|
-
return { global: currentEventId.global, local: currentEventId.local + 1 }
|
146
|
-
}
|
147
|
-
return { global: currentEventId.global + 1, local: 0 }
|
148
|
-
}
|
149
|
-
|
150
143
|
const eventNodes = partialEvents.map((partialEvent) => {
|
151
144
|
const mutationDef = mutationDefs[partialEvent.mutation]!
|
152
|
-
const eventId =
|
145
|
+
const eventId = EventId.nextPair(currentEventId, mutationDef.options.localOnly).id
|
153
146
|
currentEventId = eventId
|
154
147
|
|
155
148
|
const factsSnapshot = factsSnapshotForDag(historyDagFromNodes(nodesAcc, { skipFactsCheck: true }), undefined)
|
@@ -224,8 +217,8 @@ const getParentId = (eventId: EventId.EventId): EventId.EventId => {
|
|
224
217
|
const localParentId = eventId.local - 1
|
225
218
|
|
226
219
|
if (localParentId < 0) {
|
227
|
-
return { global: globalParentId - 1, local:
|
220
|
+
return EventId.make({ global: globalParentId - 1, local: EventId.localDefault })
|
228
221
|
}
|
229
222
|
|
230
|
-
return { global: globalParentId, local: localParentId }
|
223
|
+
return EventId.make({ global: globalParentId, local: localParentId })
|
231
224
|
}
|
package/src/sync/sync.ts
CHANGED
@@ -1,12 +1,23 @@
|
|
1
|
-
import type { Effect, HttpClient, Option, Stream, SubscriptionRef } from '@livestore/utils/effect'
|
1
|
+
import type { Effect, HttpClient, Option, Scope, Stream, SubscriptionRef } from '@livestore/utils/effect'
|
2
2
|
import { Schema } from '@livestore/utils/effect'
|
3
3
|
|
4
|
+
import type { UnexpectedError } from '../adapter-types.js'
|
5
|
+
import type { InitialSyncOptions } from '../leader-thread/types.js'
|
4
6
|
import * as EventId from '../schema/EventId.js'
|
5
7
|
import type * as MutationEvent from '../schema/MutationEvent.js'
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
9
|
+
/**
|
10
|
+
* Those arguments can be used to implement multi-tenancy etc and are passed in from the store.
|
11
|
+
*/
|
12
|
+
export type MakeBackendArgs = {
|
13
|
+
storeId: string
|
14
|
+
clientId: string
|
15
|
+
}
|
16
|
+
|
17
|
+
export type SyncOptions = {
|
18
|
+
makeBackend: (args: MakeBackendArgs) => Effect.Effect<SyncBackend<any>, UnexpectedError, Scope.Scope>
|
19
|
+
/** @default { _tag: 'Skip' } */
|
20
|
+
initialSyncOptions?: InitialSyncOptions
|
10
21
|
}
|
11
22
|
|
12
23
|
export type SyncBackend<TSyncMetadata = Schema.JsonValue> = {
|
@@ -18,7 +29,7 @@ export type SyncBackend<TSyncMetadata = Schema.JsonValue> = {
|
|
18
29
|
) => Stream.Stream<
|
19
30
|
{
|
20
31
|
batch: ReadonlyArray<{
|
21
|
-
mutationEventEncoded: MutationEvent.
|
32
|
+
mutationEventEncoded: MutationEvent.AnyEncodedGlobal
|
22
33
|
metadata: Option.Option<TSyncMetadata>
|
23
34
|
}>
|
24
35
|
remaining: number
|
@@ -33,7 +44,7 @@ export type SyncBackend<TSyncMetadata = Schema.JsonValue> = {
|
|
33
44
|
* - Number of events: 1-100
|
34
45
|
* - event ids must be in ascending order
|
35
46
|
* */
|
36
|
-
batch: ReadonlyArray<MutationEvent.
|
47
|
+
batch: ReadonlyArray<MutationEvent.AnyEncodedGlobal>,
|
37
48
|
) => Effect.Effect<
|
38
49
|
{
|
39
50
|
/** Indexes are relative to `batch` */
|
@@ -46,6 +57,7 @@ export type SyncBackend<TSyncMetadata = Schema.JsonValue> = {
|
|
46
57
|
}
|
47
58
|
|
48
59
|
export class IsOfflineError extends Schema.TaggedError<IsOfflineError>()('IsOfflineError', {}) {}
|
60
|
+
|
49
61
|
export class InvalidPushError extends Schema.TaggedError<InvalidPushError>()('InvalidPushError', {
|
50
62
|
reason: Schema.Union(
|
51
63
|
Schema.TaggedStruct('Unexpected', {
|
@@ -61,6 +73,7 @@ export class InvalidPushError extends Schema.TaggedError<InvalidPushError>()('In
|
|
61
73
|
}),
|
62
74
|
),
|
63
75
|
}) {}
|
76
|
+
|
64
77
|
export class InvalidPullError extends Schema.TaggedError<InvalidPullError>()('InvalidPullError', {
|
65
78
|
message: Schema.String,
|
66
79
|
}) {}
|