@livestore/common 0.3.0-dev.16 → 0.3.0-dev.18
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 +12 -4
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js +4 -0
- package/dist/adapter-types.js.map +1 -1
- package/dist/bounded-collections.d.ts +1 -1
- package/dist/bounded-collections.d.ts.map +1 -1
- package/dist/debug-info.d.ts.map +1 -1
- package/dist/derived-mutations.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +21 -21
- package/dist/devtools/devtools-messages-common.d.ts +6 -6
- package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-leader.d.ts +28 -28
- package/dist/devtools/index.d.ts.map +1 -1
- package/dist/init-singleton-tables.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +3 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +130 -50
- 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 +11 -5
- package/dist/leader-thread/apply-mutation.js.map +1 -1
- package/dist/leader-thread/connection.d.ts.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts.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 +1 -0
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/mutationlog.d.ts.map +1 -1
- package/dist/leader-thread/pull-queue-set.d.ts +3 -3
- package/dist/leader-thread/pull-queue-set.d.ts.map +1 -1
- package/dist/leader-thread/pull-queue-set.js +9 -0
- package/dist/leader-thread/pull-queue-set.js.map +1 -1
- package/dist/leader-thread/shutdown-channel.d.ts +2 -5
- package/dist/leader-thread/shutdown-channel.d.ts.map +1 -1
- package/dist/leader-thread/shutdown-channel.js +2 -4
- package/dist/leader-thread/shutdown-channel.js.map +1 -1
- package/dist/leader-thread/types.d.ts +7 -2
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/mutation.d.ts.map +1 -1
- package/dist/otel.d.ts.map +1 -1
- package/dist/query-builder/api.d.ts.map +1 -1
- package/dist/query-builder/impl.d.ts.map +1 -1
- package/dist/rehydrate-from-mutationlog.d.ts.map +1 -1
- package/dist/rehydrate-from-mutationlog.js +3 -3
- package/dist/rehydrate-from-mutationlog.js.map +1 -1
- package/dist/schema/EventId.d.ts +8 -0
- package/dist/schema/EventId.d.ts.map +1 -1
- package/dist/schema/EventId.js +14 -0
- package/dist/schema/EventId.js.map +1 -1
- package/dist/schema/MutationEvent.d.ts.map +1 -1
- package/dist/schema/MutationEvent.js +3 -3
- package/dist/schema/MutationEvent.js.map +1 -1
- package/dist/schema/db-schema/ast/sqlite.d.ts.map +1 -1
- package/dist/schema/db-schema/ast/validate.d.ts.map +1 -1
- package/dist/schema/db-schema/dsl/field-defs.d.ts.map +1 -1
- package/dist/schema/db-schema/dsl/field-defs.js.map +1 -1
- package/dist/schema/db-schema/dsl/mod.d.ts.map +1 -1
- package/dist/schema/db-schema/dsl/mod.js.map +1 -1
- package/dist/schema/db-schema/hash.d.ts.map +1 -1
- package/dist/schema/mutations.d.ts +5 -2
- package/dist/schema/mutations.d.ts.map +1 -1
- package/dist/schema/mutations.js.map +1 -1
- package/dist/schema/schema-helpers.d.ts.map +1 -1
- package/dist/schema/schema.d.ts +4 -1
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js +19 -8
- package/dist/schema/schema.js.map +1 -1
- package/dist/schema/table-def.d.ts +1 -8
- package/dist/schema/table-def.d.ts.map +1 -1
- package/dist/schema-management/common.d.ts.map +1 -1
- package/dist/schema-management/migrations.d.ts.map +1 -1
- package/dist/schema-management/validate-mutation-defs.d.ts.map +1 -1
- package/dist/schema-management/validate-mutation-defs.js +2 -2
- package/dist/schema-management/validate-mutation-defs.js.map +1 -1
- package/dist/sql-queries/misc.d.ts.map +1 -1
- package/dist/sql-queries/sql-queries.d.ts.map +1 -1
- package/dist/sql-queries/sql-query-builder.d.ts.map +1 -1
- package/dist/sql-queries/types.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +11 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +51 -19
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/next/compact-events.d.ts.map +1 -1
- package/dist/sync/next/facts.d.ts.map +1 -1
- package/dist/sync/next/history-dag.d.ts.map +1 -1
- package/dist/sync/next/rebase-events.d.ts.map +1 -1
- package/dist/sync/next/test/mutation-fixtures.d.ts +7 -7
- package/dist/sync/next/test/mutation-fixtures.d.ts.map +1 -1
- package/dist/sync/sync.d.ts +14 -9
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/sync.js +7 -3
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/syncstate.d.ts +132 -21
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +129 -41
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +19 -7
- package/dist/sync/syncstate.test.js.map +1 -1
- package/dist/sync/validate-push-payload.d.ts.map +1 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/src/adapter-types.ts +9 -4
- package/src/leader-thread/LeaderSyncProcessor.ts +169 -61
- package/src/leader-thread/apply-mutation.ts +21 -5
- package/src/leader-thread/make-leader-thread-layer.ts +1 -0
- package/src/leader-thread/pull-queue-set.ts +10 -1
- package/src/leader-thread/shutdown-channel.ts +2 -4
- package/src/leader-thread/types.ts +8 -2
- package/src/rehydrate-from-mutationlog.ts +2 -2
- package/src/schema/EventId.ts +16 -0
- package/src/schema/MutationEvent.ts +3 -3
- package/src/schema/db-schema/dsl/field-defs.ts +1 -2
- package/src/schema/db-schema/dsl/mod.ts +1 -1
- package/src/schema/mutations.ts +4 -1
- package/src/schema/schema.ts +20 -8
- package/src/schema-management/validate-mutation-defs.ts +2 -2
- package/src/sync/ClientSessionSyncProcessor.ts +82 -19
- package/src/sync/sync.ts +7 -4
- package/src/sync/syncstate.test.ts +32 -14
- package/src/sync/syncstate.ts +145 -60
- package/src/version.ts +1 -1
- package/tmp/pack.tgz +0 -0
@@ -124,7 +124,7 @@ export const makeMutationEventSchema = <TSchema extends LiveStoreSchema>(
|
|
124
124
|
schema: TSchema,
|
125
125
|
): ForMutationDefRecord<TSchema['_MutationDefMapType']> =>
|
126
126
|
Schema.Union(
|
127
|
-
...[...schema.mutations.values()].map((def) =>
|
127
|
+
...[...schema.mutations.map.values()].map((def) =>
|
128
128
|
Schema.Struct({
|
129
129
|
mutation: Schema.Literal(def.name),
|
130
130
|
args: def.schema,
|
@@ -140,7 +140,7 @@ export const makeMutationEventPartialSchema = <TSchema extends LiveStoreSchema>(
|
|
140
140
|
schema: TSchema,
|
141
141
|
): MutationEventPartialSchema<TSchema['_MutationDefMapType']> =>
|
142
142
|
Schema.Union(
|
143
|
-
...[...schema.mutations.values()].map((def) =>
|
143
|
+
...[...schema.mutations.map.values()].map((def) =>
|
144
144
|
Schema.Struct({
|
145
145
|
mutation: Schema.Literal(def.name),
|
146
146
|
args: def.schema,
|
@@ -169,7 +169,7 @@ export class EncodedWithMeta extends Schema.Class<EncodedWithMeta>('MutationEven
|
|
169
169
|
toJSON = (): any => {
|
170
170
|
// Only used for logging/debugging
|
171
171
|
// - More readable way to print the id + parentId
|
172
|
-
// - not including `meta`
|
172
|
+
// - not including `meta`, `clientId`, `sessionId`
|
173
173
|
return {
|
174
174
|
id: `(${this.id.global},${this.id.client}) → (${this.parentId.global},${this.parentId.client})`,
|
175
175
|
mutation: this.mutation,
|
@@ -53,8 +53,7 @@ export type ColDefFn<TColumnType extends FieldColumnType> = {
|
|
53
53
|
const TNullable extends boolean = false,
|
54
54
|
const TDefault extends TDecoded | SqlDefaultValue | NoDefault | (TNullable extends true ? null : never) = NoDefault,
|
55
55
|
const TPrimaryKey extends boolean = false,
|
56
|
-
>(args: {
|
57
|
-
schema?: Schema.Schema<TDecoded, TEncoded>
|
56
|
+
>(args: { schema?: Schema.Schema<TDecoded, TEncoded>
|
58
57
|
default?: TDefault
|
59
58
|
nullable?: TNullable
|
60
59
|
primaryKey?: TPrimaryKey
|
@@ -30,7 +30,7 @@ export type DbSchemaFromInputSchema<TSchemaInput extends DbSchemaInput> =
|
|
30
30
|
export const makeDbSchema = <TDbSchemaInput extends DbSchemaInput>(
|
31
31
|
schema: TDbSchemaInput,
|
32
32
|
): DbSchemaFromInputSchema<TDbSchemaInput> => {
|
33
|
-
return Array.isArray(schema) ? Object.fromEntries(schema.map((_) => [_.name, _])) : schema
|
33
|
+
return Array.isArray(schema) ? Object.fromEntries(schema.map((_) => [_.name, _])) : schema as any
|
34
34
|
}
|
35
35
|
|
36
36
|
export const table = <TTableName extends string, TColumns extends Columns, TIndexes extends Index[]>(
|
package/src/schema/mutations.ts
CHANGED
@@ -2,7 +2,10 @@ import { Schema } from '@livestore/utils/effect'
|
|
2
2
|
|
3
3
|
import type { BindValues } from '../sql-queries/sql-queries.js'
|
4
4
|
|
5
|
-
export type MutationDefMap =
|
5
|
+
export type MutationDefMap = {
|
6
|
+
map: Map<string | 'livestore.RawSql', MutationDef.Any>
|
7
|
+
wasProvided: boolean
|
8
|
+
}
|
6
9
|
export type MutationDefRecord = {
|
7
10
|
'livestore.RawSql': RawSqlMutation
|
8
11
|
[name: string]: MutationDef.Any
|
package/src/schema/schema.ts
CHANGED
@@ -72,29 +72,32 @@ export const makeSchema = <TInputSchema extends InputSchema>(
|
|
72
72
|
tables.set(tableDef.sqliteDef.name, tableDef)
|
73
73
|
}
|
74
74
|
|
75
|
-
const mutations: MutationDefMap =
|
75
|
+
const mutations: MutationDefMap = {
|
76
|
+
map: new Map(),
|
77
|
+
wasProvided: inputSchema.mutations !== undefined,
|
78
|
+
}
|
76
79
|
|
77
80
|
if (isReadonlyArray(inputSchema.mutations)) {
|
78
81
|
for (const mutation of inputSchema.mutations) {
|
79
|
-
mutations.set(mutation.name, mutation)
|
82
|
+
mutations.map.set(mutation.name, mutation)
|
80
83
|
}
|
81
84
|
} else {
|
82
85
|
for (const mutation of Object.values(inputSchema.mutations ?? {})) {
|
83
|
-
if (mutations.has(mutation.name)) {
|
86
|
+
if (mutations.map.has(mutation.name)) {
|
84
87
|
shouldNeverHappen(`Duplicate mutation name: ${mutation.name}. Please use unique names for mutations.`)
|
85
88
|
}
|
86
|
-
mutations.set(mutation.name, mutation)
|
89
|
+
mutations.map.set(mutation.name, mutation)
|
87
90
|
}
|
88
91
|
}
|
89
92
|
|
90
|
-
mutations.set(rawSqlMutation.name, rawSqlMutation)
|
93
|
+
mutations.map.set(rawSqlMutation.name, rawSqlMutation)
|
91
94
|
|
92
95
|
for (const tableDef of tables.values()) {
|
93
96
|
if (tableHasDerivedMutations(tableDef)) {
|
94
97
|
const derivedMutationDefs = makeDerivedMutationDefsForTable(tableDef)
|
95
|
-
mutations.set(derivedMutationDefs.insert.name, derivedMutationDefs.insert)
|
96
|
-
mutations.set(derivedMutationDefs.update.name, derivedMutationDefs.update)
|
97
|
-
mutations.set(derivedMutationDefs.delete.name, derivedMutationDefs.delete)
|
98
|
+
mutations.map.set(derivedMutationDefs.insert.name, derivedMutationDefs.insert)
|
99
|
+
mutations.map.set(derivedMutationDefs.update.name, derivedMutationDefs.update)
|
100
|
+
mutations.map.set(derivedMutationDefs.delete.name, derivedMutationDefs.delete)
|
98
101
|
}
|
99
102
|
}
|
100
103
|
|
@@ -114,6 +117,15 @@ export const makeSchema = <TInputSchema extends InputSchema>(
|
|
114
117
|
} satisfies LiveStoreSchema
|
115
118
|
}
|
116
119
|
|
120
|
+
export const getMutationDef = <TSchema extends LiveStoreSchema>(schema: TSchema, mutationName: string) => {
|
121
|
+
const mutationDef = schema.mutations.map.get(mutationName)
|
122
|
+
if (mutationDef === undefined) {
|
123
|
+
const extraInfo = schema.mutations.wasProvided ? '' : ' Please provide \`mutations\` in the schema options.'
|
124
|
+
return shouldNeverHappen(`No mutation definition found for \`${mutationName}\`.${extraInfo}`)
|
125
|
+
}
|
126
|
+
return mutationDef
|
127
|
+
}
|
128
|
+
|
117
129
|
export namespace FromInputSchema {
|
118
130
|
export type DeriveSchema<TInputSchema extends InputSchema> = LiveStoreSchema<
|
119
131
|
DbSchemaFromInputSchemaTables<TInputSchema['tables']>,
|
@@ -11,7 +11,7 @@ export const validateSchema = (schema: LiveStoreSchema, schemaManager: SchemaMan
|
|
11
11
|
const registeredMutationDefInfos = schemaManager.getMutationDefInfos()
|
12
12
|
|
13
13
|
const missingMutationDefs = registeredMutationDefInfos.filter(
|
14
|
-
(registeredMutationDefInfo) => !schema.mutations.has(registeredMutationDefInfo.mutationName),
|
14
|
+
(registeredMutationDefInfo) => !schema.mutations.map.has(registeredMutationDefInfo.mutationName),
|
15
15
|
)
|
16
16
|
|
17
17
|
if (missingMutationDefs.length > 0) {
|
@@ -20,7 +20,7 @@ export const validateSchema = (schema: LiveStoreSchema, schemaManager: SchemaMan
|
|
20
20
|
})
|
21
21
|
}
|
22
22
|
|
23
|
-
for (const [, mutationDef] of schema.mutations) {
|
23
|
+
for (const [, mutationDef] of schema.mutations.map) {
|
24
24
|
const registeredMutationDefInfo = registeredMutationDefInfos.find(
|
25
25
|
(info) => info.mutationName === mutationDef.name,
|
26
26
|
)
|
@@ -1,11 +1,11 @@
|
|
1
1
|
import { LS_DEV, shouldNeverHappen, TRACE_VERBOSE } from '@livestore/utils'
|
2
2
|
import type { Runtime, Scope } from '@livestore/utils/effect'
|
3
|
-
import { Effect, Queue, Schema, Stream, Subscribable } from '@livestore/utils/effect'
|
3
|
+
import { BucketQueue, Effect, FiberHandle, Queue, Schema, Stream, Subscribable } from '@livestore/utils/effect'
|
4
4
|
import * as otel from '@opentelemetry/api'
|
5
5
|
|
6
6
|
import type { ClientSession, UnexpectedError } from '../adapter-types.js'
|
7
7
|
import * as EventId from '../schema/EventId.js'
|
8
|
-
import { type LiveStoreSchema } from '../schema/mod.js'
|
8
|
+
import { getMutationDef, type LiveStoreSchema } from '../schema/mod.js'
|
9
9
|
import * as MutationEvent from '../schema/MutationEvent.js'
|
10
10
|
import * as SyncState from './syncstate.js'
|
11
11
|
|
@@ -26,6 +26,7 @@ export const makeClientSessionSyncProcessor = ({
|
|
26
26
|
rollback,
|
27
27
|
refreshTables,
|
28
28
|
span,
|
29
|
+
params,
|
29
30
|
}: {
|
30
31
|
schema: LiveStoreSchema
|
31
32
|
clientSession: ClientSession
|
@@ -40,6 +41,9 @@ export const makeClientSessionSyncProcessor = ({
|
|
40
41
|
rollback: (changeset: Uint8Array) => void
|
41
42
|
refreshTables: (tables: Set<string>) => void
|
42
43
|
span: otel.Span
|
44
|
+
params: {
|
45
|
+
leaderPushBatchSize: number
|
46
|
+
}
|
43
47
|
}): ClientSessionSyncProcessor => {
|
44
48
|
const mutationEventSchema = MutationEvent.makeMutationEventSchemaMemo(schema)
|
45
49
|
|
@@ -54,17 +58,18 @@ export const makeClientSessionSyncProcessor = ({
|
|
54
58
|
}
|
55
59
|
|
56
60
|
const syncStateUpdateQueue = Queue.unbounded<SyncState.SyncState>().pipe(Effect.runSync)
|
57
|
-
const isLocalEvent = (mutationEventEncoded: MutationEvent.EncodedWithMeta) =>
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
+
const isLocalEvent = (mutationEventEncoded: MutationEvent.EncodedWithMeta) =>
|
62
|
+
getMutationDef(schema, mutationEventEncoded.mutation).options.clientOnly
|
63
|
+
|
64
|
+
/** We're queuing push requests to reduce the number of messages sent to the leader by batching them */
|
65
|
+
const leaderPushQueue = BucketQueue.make<MutationEvent.EncodedWithMeta>().pipe(Effect.runSync)
|
61
66
|
|
62
67
|
const push: ClientSessionSyncProcessor['push'] = (batch, { otelContext }) => {
|
63
68
|
// TODO validate batch
|
64
69
|
|
65
70
|
let baseEventId = syncStateRef.current.localHead
|
66
71
|
const encodedMutationEvents = batch.map((mutationEvent) => {
|
67
|
-
const mutationDef = schema
|
72
|
+
const mutationDef = getMutationDef(schema, mutationEvent.mutation)
|
68
73
|
const nextIdPair = EventId.nextPair(baseEventId, mutationDef.options.clientOnly)
|
69
74
|
baseEventId = nextIdPair.id
|
70
75
|
return new MutationEvent.EncodedWithMeta(
|
@@ -84,6 +89,10 @@ export const makeClientSessionSyncProcessor = ({
|
|
84
89
|
isEqualEvent: MutationEvent.isEqualEncoded,
|
85
90
|
})
|
86
91
|
|
92
|
+
if (updateResult._tag === 'unexpected-error') {
|
93
|
+
return shouldNeverHappen('Unexpected error in client-session-sync-processor', updateResult.cause)
|
94
|
+
}
|
95
|
+
|
87
96
|
span.addEvent('local-push', {
|
88
97
|
batchSize: encodedMutationEvents.length,
|
89
98
|
updateResult: TRACE_VERBOSE ? JSON.stringify(updateResult) : undefined,
|
@@ -108,13 +117,17 @@ export const makeClientSessionSyncProcessor = ({
|
|
108
117
|
}
|
109
118
|
|
110
119
|
// console.debug('pushToLeader', encodedMutationEvents.length, ...encodedMutationEvents.map((_) => _.toJSON()))
|
111
|
-
|
112
|
-
.push(encodedMutationEvents)
|
113
|
-
.pipe(Effect.tapCauseLogPretty, Effect.provide(runtime), Effect.runFork)
|
120
|
+
BucketQueue.offerAll(leaderPushQueue, encodedMutationEvents).pipe(Effect.runSync)
|
114
121
|
|
115
122
|
return { writeTables }
|
116
123
|
}
|
117
124
|
|
125
|
+
const debugInfo = {
|
126
|
+
rebaseCount: 0,
|
127
|
+
advanceCount: 0,
|
128
|
+
rejectCount: 0,
|
129
|
+
}
|
130
|
+
|
118
131
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
119
132
|
|
120
133
|
const boot: ClientSessionSyncProcessor['boot'] = Effect.gen(function* () {
|
@@ -133,6 +146,20 @@ export const makeClientSessionSyncProcessor = ({
|
|
133
146
|
)
|
134
147
|
}
|
135
148
|
|
149
|
+
const leaderPushingFiberHandle = yield* FiberHandle.make()
|
150
|
+
|
151
|
+
const backgroundLeaderPushing = Effect.gen(function* () {
|
152
|
+
const batch = yield* BucketQueue.takeBetween(leaderPushQueue, 1, params.leaderPushBatchSize)
|
153
|
+
yield* clientSession.leaderThread.mutations.push(batch).pipe(
|
154
|
+
Effect.catchTag('LeaderAheadError', () => {
|
155
|
+
debugInfo.rejectCount++
|
156
|
+
return BucketQueue.clear(leaderPushQueue)
|
157
|
+
}),
|
158
|
+
)
|
159
|
+
}).pipe(Effect.forever, Effect.interruptible, Effect.tapCauseLogPretty)
|
160
|
+
|
161
|
+
yield* FiberHandle.run(leaderPushingFiberHandle, backgroundLeaderPushing)
|
162
|
+
|
136
163
|
yield* clientSession.leaderThread.mutations.pull.pipe(
|
137
164
|
Stream.tap(({ payload, remaining }) =>
|
138
165
|
Effect.gen(function* () {
|
@@ -148,9 +175,10 @@ export const makeClientSessionSyncProcessor = ({
|
|
148
175
|
isEqualEvent: MutationEvent.isEqualEncoded,
|
149
176
|
})
|
150
177
|
|
151
|
-
if (updateResult._tag === '
|
152
|
-
|
153
|
-
|
178
|
+
if (updateResult._tag === 'unexpected-error') {
|
179
|
+
return yield* Effect.fail(updateResult.cause)
|
180
|
+
} else if (updateResult._tag === 'reject') {
|
181
|
+
return shouldNeverHappen('Unexpected reject in client-session-sync-processor', updateResult)
|
154
182
|
}
|
155
183
|
|
156
184
|
syncStateRef.current = updateResult.newSyncState
|
@@ -165,11 +193,21 @@ export const makeClientSessionSyncProcessor = ({
|
|
165
193
|
res: TRACE_VERBOSE ? JSON.stringify(updateResult) : undefined,
|
166
194
|
remaining,
|
167
195
|
})
|
196
|
+
|
197
|
+
debugInfo.rebaseCount++
|
198
|
+
|
199
|
+
yield* FiberHandle.clear(leaderPushingFiberHandle)
|
200
|
+
|
201
|
+
// Reset the leader push queue since we're rebasing and will push again
|
202
|
+
yield* BucketQueue.clear(leaderPushQueue)
|
203
|
+
|
204
|
+
yield* FiberHandle.run(leaderPushingFiberHandle, backgroundLeaderPushing)
|
205
|
+
|
168
206
|
if (LS_DEV) {
|
169
207
|
Effect.logDebug(
|
170
208
|
'pull:rebase: rollback',
|
171
209
|
updateResult.eventsToRollback.length,
|
172
|
-
...updateResult.eventsToRollback.map((_) => _.toJSON()),
|
210
|
+
...updateResult.eventsToRollback.slice(0, 10).map((_) => _.toJSON()),
|
173
211
|
).pipe(Effect.provide(runtime), Effect.runSync)
|
174
212
|
}
|
175
213
|
|
@@ -181,9 +219,7 @@ export const makeClientSessionSyncProcessor = ({
|
|
181
219
|
}
|
182
220
|
}
|
183
221
|
|
184
|
-
|
185
|
-
.push(updateResult.newSyncState.pending)
|
186
|
-
.pipe(Effect.tapCauseLogPretty, Effect.provide(runtime), Effect.runFork)
|
222
|
+
yield* BucketQueue.offerAll(leaderPushQueue, updateResult.newSyncState.pending)
|
187
223
|
} else {
|
188
224
|
span.addEvent('pull:advance', {
|
189
225
|
payloadTag: payload._tag,
|
@@ -192,6 +228,8 @@ export const makeClientSessionSyncProcessor = ({
|
|
192
228
|
res: TRACE_VERBOSE ? JSON.stringify(updateResult) : undefined,
|
193
229
|
remaining,
|
194
230
|
})
|
231
|
+
|
232
|
+
debugInfo.advanceCount++
|
195
233
|
}
|
196
234
|
|
197
235
|
if (updateResult.newEvents.length === 0) return
|
@@ -208,10 +246,14 @@ export const makeClientSessionSyncProcessor = ({
|
|
208
246
|
}
|
209
247
|
|
210
248
|
refreshTables(writeTables)
|
211
|
-
})
|
249
|
+
}).pipe(
|
250
|
+
Effect.tapCauseLogPretty,
|
251
|
+
Effect.catchAllCause((cause) => Effect.sync(() => clientSession.shutdown(cause))),
|
252
|
+
),
|
212
253
|
),
|
213
254
|
Stream.runDrain,
|
214
255
|
Effect.forever, // NOTE Whenever the leader changes, we need to re-start the stream
|
256
|
+
Effect.withSpan('client-session-sync-processor:pull'),
|
215
257
|
Effect.tapCauseLogPretty,
|
216
258
|
Effect.forkScoped,
|
217
259
|
)
|
@@ -228,6 +270,21 @@ export const makeClientSessionSyncProcessor = ({
|
|
228
270
|
}),
|
229
271
|
changes: Stream.fromQueue(syncStateUpdateQueue),
|
230
272
|
}),
|
273
|
+
debug: {
|
274
|
+
print: () =>
|
275
|
+
Effect.gen(function* () {
|
276
|
+
console.log('debugInfo', debugInfo)
|
277
|
+
console.log('syncState', syncStateRef.current)
|
278
|
+
const pushQueueSize = yield* BucketQueue.size(leaderPushQueue)
|
279
|
+
console.log('pushQueueSize', pushQueueSize)
|
280
|
+
const pushQueueItems = yield* BucketQueue.peekAll(leaderPushQueue)
|
281
|
+
console.log(
|
282
|
+
'pushQueueItems',
|
283
|
+
pushQueueItems.map((_) => _.toJSON()),
|
284
|
+
)
|
285
|
+
}).pipe(Effect.provide(runtime), Effect.runSync),
|
286
|
+
debugInfo: () => debugInfo,
|
287
|
+
},
|
231
288
|
} satisfies ClientSessionSyncProcessor
|
232
289
|
}
|
233
290
|
|
@@ -239,6 +296,12 @@ export interface ClientSessionSyncProcessor {
|
|
239
296
|
writeTables: Set<string>
|
240
297
|
}
|
241
298
|
boot: Effect.Effect<void, UnexpectedError, Scope.Scope>
|
242
|
-
|
243
299
|
syncState: Subscribable.Subscribable<SyncState.SyncState>
|
300
|
+
debug: {
|
301
|
+
print: () => void
|
302
|
+
debugInfo: () => {
|
303
|
+
rebaseCount: number
|
304
|
+
advanceCount: number
|
305
|
+
}
|
306
|
+
}
|
244
307
|
}
|
package/src/sync/sync.ts
CHANGED
@@ -67,13 +67,16 @@ export class InvalidPushError extends Schema.TaggedError<InvalidPushError>()('In
|
|
67
67
|
minimumExpectedId: Schema.Number,
|
68
68
|
providedId: Schema.Number,
|
69
69
|
}),
|
70
|
-
Schema.TaggedStruct('LeaderAhead', {
|
71
|
-
minimumExpectedId: EventId.EventId,
|
72
|
-
providedId: EventId.EventId,
|
73
|
-
}),
|
74
70
|
),
|
75
71
|
}) {}
|
76
72
|
|
77
73
|
export class InvalidPullError extends Schema.TaggedError<InvalidPullError>()('InvalidPullError', {
|
78
74
|
message: Schema.String,
|
79
75
|
}) {}
|
76
|
+
|
77
|
+
export class LeaderAheadError extends Schema.TaggedError<LeaderAheadError>()('LeaderAheadError', {
|
78
|
+
minimumExpectedId: EventId.EventId,
|
79
|
+
providedId: EventId.EventId,
|
80
|
+
/** Generation number the client session should use for subsequent pushes */
|
81
|
+
// nextGeneration: Schema.Number,
|
82
|
+
}) {}
|
@@ -17,7 +17,7 @@ class TestEvent extends MutationEvent.EncodedWithMeta {
|
|
17
17
|
parentId: EventId.make(parentId),
|
18
18
|
mutation: 'a',
|
19
19
|
args: payload,
|
20
|
-
|
20
|
+
|
21
21
|
clientId: 'static-local-id',
|
22
22
|
sessionId: undefined,
|
23
23
|
})
|
@@ -87,7 +87,7 @@ describe('syncstate', () => {
|
|
87
87
|
}
|
88
88
|
expect(result.newSyncState.upstreamHead).toMatchObject(e_0_1_e_1_1.id)
|
89
89
|
expect(result.newSyncState.localHead).toMatchObject(e_1_0_e_2_0.id)
|
90
|
-
expectEventArraysEqual(result.newEvents, [e_0_0_e_1_0, e_0_1_e_1_1])
|
90
|
+
expectEventArraysEqual(result.newEvents, [e_0_0_e_1_0, e_0_1_e_1_1, e_1_0_e_2_0])
|
91
91
|
expectEventArraysEqual(result.eventsToRollback, [e_0_0, e_0_1, e_1_0])
|
92
92
|
})
|
93
93
|
|
@@ -118,7 +118,7 @@ describe('syncstate', () => {
|
|
118
118
|
}
|
119
119
|
expect(result.newSyncState.upstreamHead).toMatchObject(e_0_1_e_1_0.id)
|
120
120
|
expect(result.newSyncState.localHead).toMatchObject(e_1_0_e_2_0.id)
|
121
|
-
expectEventArraysEqual(result.newEvents, [e_0_1_e_1_0])
|
121
|
+
expectEventArraysEqual(result.newEvents, [e_0_1_e_1_0, e_1_0_e_2_0])
|
122
122
|
expectEventArraysEqual(result.eventsToRollback, [e_0_1, e_1_0])
|
123
123
|
})
|
124
124
|
|
@@ -148,12 +148,11 @@ describe('syncstate', () => {
|
|
148
148
|
upstreamHead: EventId.ROOT,
|
149
149
|
localHead: e_0_0.id,
|
150
150
|
})
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
).toThrow()
|
151
|
+
const result = run({
|
152
|
+
syncState,
|
153
|
+
payload: { _tag: 'upstream-rebase', rollbackUntil: e_0_0.id, newEvents: [e_1_0] },
|
154
|
+
})
|
155
|
+
expect(result).toMatchObject({ _tag: 'unexpected-error' })
|
157
156
|
})
|
158
157
|
|
159
158
|
it('should work for empty incoming', () => {
|
@@ -185,7 +184,8 @@ describe('syncstate', () => {
|
|
185
184
|
upstreamHead: EventId.ROOT,
|
186
185
|
localHead: e_0_0.id,
|
187
186
|
})
|
188
|
-
|
187
|
+
const result = run({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e_0_1, e_0_0] } })
|
188
|
+
expect(result).toMatchObject({ _tag: 'unexpected-error' })
|
189
189
|
})
|
190
190
|
|
191
191
|
it('should throw error if newEvents are not sorted in ascending order by eventId (global)', () => {
|
@@ -195,7 +195,8 @@ describe('syncstate', () => {
|
|
195
195
|
upstreamHead: EventId.ROOT,
|
196
196
|
localHead: e_0_0.id,
|
197
197
|
})
|
198
|
-
|
198
|
+
const result = run({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e_1_0, e_0_0] } })
|
199
|
+
expect(result).toMatchObject({ _tag: 'unexpected-error' })
|
199
200
|
})
|
200
201
|
|
201
202
|
it('should acknowledge pending event when receiving matching event', () => {
|
@@ -329,6 +330,17 @@ describe('syncstate', () => {
|
|
329
330
|
expect(result.newSyncState.localHead).toMatchObject(e_1_0.id)
|
330
331
|
expect(result.newEvents).toStrictEqual([e_1_0])
|
331
332
|
})
|
333
|
+
|
334
|
+
it('should fail if incoming event is ≤ local head', () => {
|
335
|
+
const syncState = new SyncState.SyncState({
|
336
|
+
pending: [],
|
337
|
+
rollbackTail: [],
|
338
|
+
upstreamHead: e_1_0.id,
|
339
|
+
localHead: e_1_0.id,
|
340
|
+
})
|
341
|
+
const result = run({ syncState, payload: { _tag: 'upstream-advance', newEvents: [e_0_0] } })
|
342
|
+
expect(result).toMatchObject({ _tag: 'unexpected-error' })
|
343
|
+
})
|
332
344
|
})
|
333
345
|
|
334
346
|
describe('upstream-advance: rebase', () => {
|
@@ -513,14 +525,20 @@ const expectEventArraysEqual = (
|
|
513
525
|
})
|
514
526
|
}
|
515
527
|
|
516
|
-
function expectAdvance(
|
528
|
+
function expectAdvance(
|
529
|
+
result: typeof SyncState.UpdateResult.Type,
|
530
|
+
): asserts result is typeof SyncState.UpdateResultAdvance.Type {
|
517
531
|
expect(result._tag).toBe('advance')
|
518
532
|
}
|
519
533
|
|
520
|
-
function expectRebase(
|
534
|
+
function expectRebase(
|
535
|
+
result: typeof SyncState.UpdateResult.Type,
|
536
|
+
): asserts result is typeof SyncState.UpdateResultRebase.Type {
|
521
537
|
expect(result._tag).toBe('rebase')
|
522
538
|
}
|
523
539
|
|
524
|
-
function expectReject(
|
540
|
+
function expectReject(
|
541
|
+
result: typeof SyncState.UpdateResult.Type,
|
542
|
+
): asserts result is typeof SyncState.UpdateResultReject.Type {
|
525
543
|
expect(result._tag).toBe('reject')
|
526
544
|
}
|