@livestore/common 0.3.0-dev.4 → 0.3.0-dev.6
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 +90 -102
- 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/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 +37 -25
- package/dist/leader-thread/apply-mutation.js.map +1 -1
- 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.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 +4 -2
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +5 -2
- 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 +7 -5
- 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/MutationEvent.d.ts +49 -74
- package/dist/schema/MutationEvent.d.ts.map +1 -1
- package/dist/schema/MutationEvent.js +29 -13
- package/dist/schema/MutationEvent.js.map +1 -1
- 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/client-session-sync-processor.d.ts +4 -4
- package/dist/sync/client-session-sync-processor.d.ts.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 +6 -6
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/syncstate.d.ts +10 -10
- package/dist/sync/syncstate.test.js +2 -6
- 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.js +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/leader-thread/apply-mutation.ts +48 -30
- package/src/leader-thread/leader-sync-processor.ts +26 -15
- package/src/leader-thread/leader-worker-devtools.ts +30 -109
- package/src/leader-thread/make-leader-thread-layer.ts +15 -5
- package/src/leader-thread/mutationlog.ts +9 -5
- package/src/leader-thread/recreate-db.ts +9 -5
- package/src/leader-thread/types.ts +7 -8
- package/src/mutation.ts +17 -7
- package/src/rehydrate-from-mutationlog.ts +15 -23
- package/src/schema/EventId.ts +23 -9
- package/src/schema/MutationEvent.ts +40 -20
- package/src/schema/system-tables.ts +19 -11
- package/src/schema-management/migrations.ts +6 -6
- package/src/sync/client-session-sync-processor.ts +4 -4
- 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 +4 -2
- package/src/sync/syncstate.test.ts +4 -4
- package/src/sync/validate-push-payload.ts +7 -4
- package/src/version.ts +1 -1
package/src/adapter-types.ts
CHANGED
@@ -29,8 +29,28 @@ export type SynchronousDatabaseChangeset = {
|
|
29
29
|
export type ClientSession = {
|
30
30
|
/** SQLite database with synchronous API running in the same thread (usually in-memory) */
|
31
31
|
syncDb: SynchronousDatabase
|
32
|
-
|
33
|
-
|
32
|
+
devtools: { enabled: boolean }
|
33
|
+
clientId: string
|
34
|
+
sessionId: string
|
35
|
+
/** Status info whether current session is leader or not */
|
36
|
+
lockStatus: SubscriptionRef.SubscriptionRef<LockStatus>
|
37
|
+
shutdown: (cause: Cause.Cause<UnexpectedError | IntentionalShutdownCause>) => Effect.Effect<void>
|
38
|
+
/** A proxy API to communicate with the leader thread */
|
39
|
+
leaderThread: ClientSessionLeaderThreadProxy
|
40
|
+
}
|
41
|
+
|
42
|
+
export type ClientSessionLeaderThreadProxy = {
|
43
|
+
mutations: {
|
44
|
+
pull: Stream.Stream<{ payload: PayloadUpstream; remaining: number }, UnexpectedError>
|
45
|
+
push(batch: ReadonlyArray<MutationEvent.AnyEncoded>): Effect.Effect<void, UnexpectedError | InvalidPushError>
|
46
|
+
initialMutationEventId: EventId
|
47
|
+
}
|
48
|
+
export: Effect.Effect<Uint8Array, UnexpectedError>
|
49
|
+
getMutationLogData: Effect.Effect<Uint8Array, UnexpectedError>
|
50
|
+
getSyncState: Effect.Effect<SyncState, UnexpectedError>
|
51
|
+
networkStatus: SubscriptionRef.SubscriptionRef<NetworkStatus>
|
52
|
+
/** For debugging purposes it can be useful to manually trigger devtools messages (e.g. to reset the database) */
|
53
|
+
sendDevtoolsMessage: (message: Devtools.MessageToAppLeader) => Effect.Effect<void, UnexpectedError>
|
34
54
|
}
|
35
55
|
|
36
56
|
export type SynchronousDatabase<TReq = any, TMetadata extends TReq = TReq> = {
|
@@ -103,28 +123,6 @@ export const BootStatus = Schema.Union(
|
|
103
123
|
|
104
124
|
export type BootStatus = typeof BootStatus.Type
|
105
125
|
|
106
|
-
// TODO refactor `Coordinator` to embrace more of the "leader semantics"
|
107
|
-
export type Coordinator = {
|
108
|
-
devtools: {
|
109
|
-
enabled: boolean
|
110
|
-
// TODO incorporate sessionId and rethink appHostId
|
111
|
-
appHostId: string
|
112
|
-
}
|
113
|
-
sessionId: string
|
114
|
-
// TODO is exposing the lock status really needed (or only relevant for web adapter?)
|
115
|
-
lockStatus: SubscriptionRef.SubscriptionRef<LockStatus>
|
116
|
-
mutations: {
|
117
|
-
pull: Stream.Stream<{ payload: PayloadUpstream; remaining: number }, UnexpectedError>
|
118
|
-
push(batch: ReadonlyArray<MutationEvent.AnyEncoded>): Effect.Effect<void, UnexpectedError | InvalidPushError>
|
119
|
-
initialMutationEventId: EventId
|
120
|
-
}
|
121
|
-
export: Effect.Effect<Uint8Array, UnexpectedError>
|
122
|
-
getMutationLogData: Effect.Effect<Uint8Array, UnexpectedError>
|
123
|
-
getLeaderSyncState: Effect.Effect<SyncState, UnexpectedError>
|
124
|
-
networkStatus: SubscriptionRef.SubscriptionRef<NetworkStatus>
|
125
|
-
shutdown: (cause: Cause.Cause<UnexpectedError | IntentionalShutdownCause>) => Effect.Effect<void>
|
126
|
-
}
|
127
|
-
|
128
126
|
/**
|
129
127
|
* Can be used in queries to refer to the current session id.
|
130
128
|
* Will be replaced with the actual session id at runtime
|
@@ -94,7 +94,7 @@ describe('derived mutations', () => {
|
|
94
94
|
})
|
95
95
|
})
|
96
96
|
|
97
|
-
const patchId = (muationEvent: MutationEvent.
|
97
|
+
const patchId = (muationEvent: MutationEvent.PartialAnyDecoded) => {
|
98
98
|
// TODO use new id paradigm
|
99
99
|
const id = `00000000-0000-0000-0000-000000000000`
|
100
100
|
return { ...muationEvent, id }
|
package/src/derived-mutations.ts
CHANGED
@@ -136,8 +136,10 @@ export namespace DerivedMutationHelperFns {
|
|
136
136
|
> = SqliteDsl.AnyIfConstained<
|
137
137
|
TColumns,
|
138
138
|
UseShortcut<TOptions> extends true
|
139
|
-
? (
|
140
|
-
|
139
|
+
? (
|
140
|
+
values?: GetValForKey<SqliteDsl.FromColumns.InsertRowDecoded<TColumns>, 'value'>,
|
141
|
+
) => MutationEvent.PartialAnyDecoded
|
142
|
+
: (values: SqliteDsl.FromColumns.InsertRowDecoded<TColumns>) => MutationEvent.PartialAnyDecoded
|
141
143
|
>
|
142
144
|
|
143
145
|
export type UpdateMutationFn<
|
@@ -146,17 +148,19 @@ export namespace DerivedMutationHelperFns {
|
|
146
148
|
> = SqliteDsl.AnyIfConstained<
|
147
149
|
TColumns,
|
148
150
|
UseShortcut<TOptions> extends true
|
149
|
-
? (
|
151
|
+
? (
|
152
|
+
values: Partial<GetValForKey<SqliteDsl.FromColumns.RowDecoded<TColumns>, 'value'>>,
|
153
|
+
) => MutationEvent.PartialAnyDecoded
|
150
154
|
: (args: {
|
151
155
|
where: Partial<SqliteDsl.FromColumns.RowDecoded<TColumns>>
|
152
156
|
values: Partial<SqliteDsl.FromColumns.RowDecoded<TColumns>>
|
153
|
-
}) => MutationEvent.
|
157
|
+
}) => MutationEvent.PartialAnyDecoded
|
154
158
|
>
|
155
159
|
|
156
160
|
export type DeleteMutationFn<
|
157
161
|
TColumns extends SqliteDsl.ConstraintColumns,
|
158
162
|
_TOptions extends DbSchema.TableOptions,
|
159
|
-
> = (args: { where: Partial<SqliteDsl.FromColumns.RowDecoded<TColumns>> }) => MutationEvent.
|
163
|
+
> = (args: { where: Partial<SqliteDsl.FromColumns.RowDecoded<TColumns>> }) => MutationEvent.PartialAnyDecoded
|
160
164
|
|
161
165
|
type UseShortcut<TOptions extends DbSchema.TableOptions> = TOptions['isSingleColumn'] extends true
|
162
166
|
? TOptions['isSingleton'] extends true
|
@@ -6,7 +6,8 @@ export type PrepareDevtoolsBridge = {
|
|
6
6
|
/** Messages coming from the app host (usually responses to requests) */
|
7
7
|
responsePubSub: PubSub.PubSub<Devtools.MessageFromAppLeader | Devtools.MessageFromAppClientSession>
|
8
8
|
sendToAppHost: (msg: Devtools.MessageToAppLeader | Devtools.MessageToAppClientSession) => Effect.Effect<void>
|
9
|
-
|
9
|
+
clientId: string
|
10
|
+
sessionId: string
|
10
11
|
copyToClipboard: (text: string) => Effect.Effect<void>
|
11
12
|
sendEscapeKey?: Effect.Effect<void>
|
12
13
|
isLeader: boolean
|
@@ -7,7 +7,8 @@ import { PreparedBindValues } from '../util.js'
|
|
7
7
|
import { liveStoreVersion as pkgVersion } from '../version.js'
|
8
8
|
|
9
9
|
const requestId = Schema.String
|
10
|
-
const
|
10
|
+
const clientId = Schema.String
|
11
|
+
const sessionId = Schema.String
|
11
12
|
const liveStoreVersion = Schema.Literal(pkgVersion)
|
12
13
|
|
13
14
|
const LSDMessage = <Tag extends string, Fields extends Schema.Struct.Fields>(tag: Tag, fields: Fields) =>
|
@@ -23,13 +24,15 @@ const LSDChannelMessage = <Tag extends string, Fields extends Schema.Struct.Fiel
|
|
23
24
|
|
24
25
|
const LSDStoreChannelMessage = <Tag extends string, Fields extends Schema.Struct.Fields>(tag: Tag, fields: Fields) =>
|
25
26
|
LSDMessage(tag, {
|
26
|
-
|
27
|
+
clientId,
|
28
|
+
sessionId,
|
27
29
|
...fields,
|
28
30
|
})
|
29
31
|
|
30
32
|
const LSDStoreReqResMessage = <Tag extends string, Fields extends Schema.Struct.Fields>(tag: Tag, fields: Fields) =>
|
31
33
|
LSDMessage(tag, {
|
32
|
-
|
34
|
+
clientId,
|
35
|
+
sessionId,
|
33
36
|
requestId,
|
34
37
|
...fields,
|
35
38
|
})
|
@@ -82,12 +85,12 @@ export class DebugInfoRerunQueryRes extends LSDStoreReqResMessage('LSD.DebugInfo
|
|
82
85
|
|
83
86
|
// TODO refactor this to use push/pull semantics
|
84
87
|
export class MutationBroadcast extends LSDMessage('LSD.Leader.MutationBroadcast', {
|
85
|
-
mutationEventEncoded: MutationEvent.
|
88
|
+
mutationEventEncoded: MutationEvent.AnyEncoded,
|
86
89
|
}) {}
|
87
90
|
|
88
91
|
// TODO refactor this to use push/pull semantics
|
89
92
|
export class RunMutationReq extends LSDReqResMessage('LSD.Leader.RunMutationReq', {
|
90
|
-
mutationEventEncoded: MutationEvent.
|
93
|
+
mutationEventEncoded: MutationEvent.AnyEncoded.pipe(Schema.omit('id', 'parentId')),
|
91
94
|
}) {}
|
92
95
|
|
93
96
|
export class RunMutationRes extends LSDReqResMessage('LSD.Leader.RunMutationRes', {}) {}
|
@@ -167,7 +170,7 @@ export class SyncingInfoRes extends LSDReqResMessage('LSD.Leader.SyncingInfoRes'
|
|
167
170
|
export class SyncHistorySubscribe extends LSDReqResMessage('LSD.Leader.SyncHistorySubscribe', {}) {}
|
168
171
|
export class SyncHistoryUnsubscribe extends LSDReqResMessage('LSD.Leader.SyncHistoryUnsubscribe', {}) {}
|
169
172
|
export class SyncHistoryRes extends LSDReqResMessage('LSD.Leader.SyncHistoryRes', {
|
170
|
-
mutationEventEncoded: MutationEvent.
|
173
|
+
mutationEventEncoded: MutationEvent.AnyEncodedGlobal,
|
171
174
|
metadata: Schema.Option(Schema.JsonValue),
|
172
175
|
}) {}
|
173
176
|
|
@@ -3,20 +3,25 @@ import type { Scope } from '@livestore/utils/effect'
|
|
3
3
|
import { Effect, Option, Schema } from '@livestore/utils/effect'
|
4
4
|
|
5
5
|
import type { SqliteError, SynchronousDatabase, UnexpectedError } from '../index.js'
|
6
|
+
import { getExecArgsFromMutation } from '../mutation.js'
|
6
7
|
import {
|
7
|
-
|
8
|
+
type LiveStoreSchema,
|
8
9
|
MUTATION_LOG_META_TABLE,
|
10
|
+
type MutationEvent,
|
9
11
|
mutationLogMetaTable,
|
10
12
|
SESSION_CHANGESET_META_TABLE,
|
11
13
|
sessionChangesetMetaTable,
|
12
|
-
} from '../
|
13
|
-
import type { LiveStoreSchema, MutationEvent } from '../schema/mod.js'
|
14
|
+
} from '../schema/mod.js'
|
14
15
|
import { insertRow } from '../sql-queries/index.js'
|
15
16
|
import { execSql, execSqlPrepared } from './connection.js'
|
16
17
|
import { LeaderThreadCtx } from './types.js'
|
17
18
|
|
18
19
|
export type ApplyMutation = (
|
19
20
|
mutationEventEncoded: MutationEvent.AnyEncoded,
|
21
|
+
options?: {
|
22
|
+
/** Needed for rehydrateFromMutationLog */
|
23
|
+
skipMutationLog?: boolean
|
24
|
+
},
|
20
25
|
) => Effect.Effect<void, SqliteError | UnexpectedError>
|
21
26
|
|
22
27
|
export const makeApplyMutation: Effect.Effect<ApplyMutation, never, Scope.Scope | LeaderThreadCtx> = Effect.gen(
|
@@ -31,15 +36,27 @@ export const makeApplyMutation: Effect.Effect<ApplyMutation, never, Scope.Scope
|
|
31
36
|
[...leaderThreadCtx.schema.mutations.entries()].map(([k, v]) => [k, Schema.hash(v.schema)] as const),
|
32
37
|
)
|
33
38
|
|
34
|
-
return (mutationEventEncoded) =>
|
39
|
+
return (mutationEventEncoded, options) =>
|
35
40
|
Effect.gen(function* () {
|
36
|
-
const {
|
37
|
-
const
|
41
|
+
const { schema, db, dbLog } = leaderThreadCtx
|
42
|
+
const skipMutationLog = options?.skipMutationLog ?? false
|
38
43
|
|
39
|
-
const mutationName =
|
44
|
+
const mutationName = mutationEventEncoded.mutation
|
40
45
|
const mutationDef = schema.mutations.get(mutationName) ?? shouldNeverHappen(`Unknown mutation: ${mutationName}`)
|
41
46
|
|
42
|
-
const execArgsArr = getExecArgsFromMutation({
|
47
|
+
const execArgsArr = getExecArgsFromMutation({
|
48
|
+
mutationDef,
|
49
|
+
mutationEvent: { decoded: undefined, encoded: mutationEventEncoded },
|
50
|
+
})
|
51
|
+
|
52
|
+
// NOTE we might want to bring this back if we want to debug no-op mutations
|
53
|
+
// const makeExecuteOptions = (statementSql: string, bindValues: any) => ({
|
54
|
+
// onRowsChanged: (rowsChanged: number) => {
|
55
|
+
// if (rowsChanged === 0) {
|
56
|
+
// console.warn(`Mutation "${mutationDef.name}" did not affect any rows:`, statementSql, bindValues)
|
57
|
+
// }
|
58
|
+
// },
|
59
|
+
// })
|
43
60
|
|
44
61
|
// console.group('[@livestore/common:leader-thread:applyMutation]', { mutationName })
|
45
62
|
|
@@ -53,30 +70,28 @@ export const makeApplyMutation: Effect.Effect<ApplyMutation, never, Scope.Scope
|
|
53
70
|
|
54
71
|
const changeset = session.changeset()
|
55
72
|
session.finish()
|
56
|
-
|
57
|
-
// TODO
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
)
|
73
|
-
}
|
73
|
+
|
74
|
+
// TODO use prepared statements
|
75
|
+
yield* execSql(
|
76
|
+
db,
|
77
|
+
...insertRow({
|
78
|
+
tableName: SESSION_CHANGESET_META_TABLE,
|
79
|
+
columns: sessionChangesetMetaTable.sqliteDef.columns,
|
80
|
+
values: {
|
81
|
+
idGlobal: mutationEventEncoded.id.global,
|
82
|
+
idLocal: mutationEventEncoded.id.local,
|
83
|
+
// NOTE the changeset will be empty (i.e. null) for no-op mutations
|
84
|
+
changeset: changeset ?? null,
|
85
|
+
debug: execArgsArr,
|
86
|
+
},
|
87
|
+
}),
|
88
|
+
)
|
74
89
|
|
75
90
|
// console.groupEnd()
|
76
91
|
|
77
92
|
// write to mutation_log
|
78
|
-
const excludeFromMutationLog = shouldExcludeMutationFromLog(mutationName,
|
79
|
-
if (excludeFromMutationLog === false) {
|
93
|
+
const excludeFromMutationLog = shouldExcludeMutationFromLog(mutationName, mutationEventEncoded)
|
94
|
+
if (skipMutationLog === false && excludeFromMutationLog === false) {
|
80
95
|
yield* insertIntoMutationLog(mutationEventEncoded, dbLog, mutationDefSchemaHashMap)
|
81
96
|
} else {
|
82
97
|
// console.debug('[@livestore/common:leader-thread] skipping mutation log write', mutation, statementSql, bindValues)
|
@@ -132,11 +147,14 @@ const makeShouldExcludeMutationFromLog = memoizeByRef((schema: LiveStoreSchema)
|
|
132
147
|
? (migrationOptions.excludeMutations ?? new Set(['livestore.RawSql']))
|
133
148
|
: new Set(['livestore.RawSql'])
|
134
149
|
|
135
|
-
return (mutationName: string,
|
150
|
+
return (mutationName: string, mutationEventEncoded: MutationEvent.AnyEncoded): boolean => {
|
136
151
|
if (mutationLogExclude.has(mutationName)) return true
|
137
152
|
|
138
153
|
const mutationDef = schema.mutations.get(mutationName) ?? shouldNeverHappen(`Unknown mutation: ${mutationName}`)
|
139
|
-
const execArgsArr = getExecArgsFromMutation({
|
154
|
+
const execArgsArr = getExecArgsFromMutation({
|
155
|
+
mutationDef,
|
156
|
+
mutationEvent: { decoded: undefined, encoded: mutationEventEncoded },
|
157
|
+
})
|
140
158
|
|
141
159
|
return execArgsArr.some((_) => _.statementSql.includes('__livestore'))
|
142
160
|
}
|
@@ -9,6 +9,7 @@ import {
|
|
9
9
|
FiberHandle,
|
10
10
|
Option,
|
11
11
|
OtelTracer,
|
12
|
+
ReadonlyArray,
|
12
13
|
Ref,
|
13
14
|
Schema,
|
14
15
|
Stream,
|
@@ -102,7 +103,7 @@ export const makeLeaderSyncProcessor = ({
|
|
102
103
|
initialBlockingSyncContext: InitialBlockingSyncContext
|
103
104
|
}): Effect.Effect<SyncProcessor, UnexpectedError, Scope.Scope> =>
|
104
105
|
Effect.gen(function* () {
|
105
|
-
const syncBackendQueue = yield* BucketQueue.make<MutationEvent.
|
106
|
+
const syncBackendQueue = yield* BucketQueue.make<MutationEvent.EncodedWithMeta>()
|
106
107
|
|
107
108
|
const stateRef = yield* Ref.make<ProcessorState>({ _tag: 'init' })
|
108
109
|
|
@@ -257,13 +258,16 @@ export const makeLeaderSyncProcessor = ({
|
|
257
258
|
)
|
258
259
|
}
|
259
260
|
|
260
|
-
const pendingMutationEvents = yield* getMutationEventsSince({
|
261
|
+
const pendingMutationEvents = yield* getMutationEventsSince({
|
262
|
+
global: initialBackendHead,
|
263
|
+
local: EventId.localDefault,
|
264
|
+
}).pipe(Effect.map(ReadonlyArray.map((_) => new MutationEvent.EncodedWithMeta(_))))
|
261
265
|
|
262
266
|
const initialSyncState = {
|
263
|
-
pending: pendingMutationEvents
|
267
|
+
pending: pendingMutationEvents,
|
264
268
|
// On the leader we don't need a rollback tail beyond `pending` items
|
265
269
|
rollbackTail: [],
|
266
|
-
upstreamHead: { global: initialBackendHead, local:
|
270
|
+
upstreamHead: { global: initialBackendHead, local: EventId.localDefault },
|
267
271
|
localHead: initialLocalHead,
|
268
272
|
} as SyncState.SyncState
|
269
273
|
|
@@ -410,7 +414,7 @@ const backgroundBackendPulling = ({
|
|
410
414
|
initialBlockingSyncContext,
|
411
415
|
}: {
|
412
416
|
dbReady: Deferred.Deferred<void>
|
413
|
-
initialBackendHead:
|
417
|
+
initialBackendHead: EventId.GlobalEventId
|
414
418
|
isLocalEvent: (mutationEventEncoded: MutationEvent.EncodedWithMeta) => boolean
|
415
419
|
restartBackendPushing: (
|
416
420
|
filteredRebasedPending: ReadonlyArray<MutationEvent.EncodedWithMeta>,
|
@@ -505,9 +509,9 @@ const backgroundBackendPulling = ({
|
|
505
509
|
})
|
506
510
|
}
|
507
511
|
|
508
|
-
|
509
|
-
|
510
|
-
}).pipe(Effect.fork)
|
512
|
+
trimChangesetRows(db, newBackendHead)
|
513
|
+
|
514
|
+
const fiber = yield* applyMutationItemsRef.current!({ batchItems: updateResult.newEvents }).pipe(Effect.fork)
|
511
515
|
|
512
516
|
yield* Ref.set(stateRef, {
|
513
517
|
_tag: 'applying-syncstate-advance',
|
@@ -515,7 +519,6 @@ const backgroundBackendPulling = ({
|
|
515
519
|
syncState: updateResult.newSyncState,
|
516
520
|
fiber,
|
517
521
|
})
|
518
|
-
// console.log('setRef:applying-syncstate-advance after backgroundBackendPulling', -1)
|
519
522
|
})
|
520
523
|
|
521
524
|
yield* syncBackend.pull(cursorInfo).pipe(
|
@@ -538,7 +541,7 @@ const backgroundBackendPulling = ({
|
|
538
541
|
yield* SubscriptionRef.waitUntil(syncBackend.isConnected, (isConnected) => isConnected === true)
|
539
542
|
|
540
543
|
yield* onNewPullChunk(
|
541
|
-
batch.map((_) =>
|
544
|
+
batch.map((_) => MutationEvent.EncodedWithMeta.fromGlobal(_.mutationEventEncoded)),
|
542
545
|
remaining,
|
543
546
|
)
|
544
547
|
|
@@ -570,7 +573,9 @@ const rollback = ({
|
|
570
573
|
// Apply changesets in reverse order
|
571
574
|
for (let i = rollbackEvents.length - 1; i >= 0; i--) {
|
572
575
|
const { changeset } = rollbackEvents[i]!
|
573
|
-
|
576
|
+
if (changeset !== null) {
|
577
|
+
db.makeChangeset(changeset).invert().apply()
|
578
|
+
}
|
574
579
|
}
|
575
580
|
|
576
581
|
// Delete the changeset rows
|
@@ -588,7 +593,7 @@ const rollback = ({
|
|
588
593
|
}),
|
589
594
|
)
|
590
595
|
|
591
|
-
const getCursorInfo = (remoteHead:
|
596
|
+
const getCursorInfo = (remoteHead: EventId.GlobalEventId) =>
|
592
597
|
Effect.gen(function* () {
|
593
598
|
const { dbLog } = yield* LeaderThreadCtx
|
594
599
|
|
@@ -605,7 +610,7 @@ const getCursorInfo = (remoteHead: number) =>
|
|
605
610
|
).pipe(Effect.andThen(Schema.decode(MutationlogQuerySchema)), Effect.map(Option.flatten), Effect.orDie)
|
606
611
|
|
607
612
|
return Option.some({
|
608
|
-
cursor: { global: remoteHead, local:
|
613
|
+
cursor: { global: remoteHead, local: EventId.localDefault },
|
609
614
|
metadata: syncMetadataOption,
|
610
615
|
}) satisfies InitialSyncInfo
|
611
616
|
}).pipe(Effect.withSpan('@livestore/common:leader-thread:syncing:getCursorInfo', { attributes: { remoteHead } }))
|
@@ -616,7 +621,7 @@ const backgroundBackendPushing = ({
|
|
616
621
|
span,
|
617
622
|
}: {
|
618
623
|
dbReady: Deferred.Deferred<void>
|
619
|
-
syncBackendQueue: BucketQueue.BucketQueue<MutationEvent.
|
624
|
+
syncBackendQueue: BucketQueue.BucketQueue<MutationEvent.EncodedWithMeta>
|
620
625
|
span: otel.Span | undefined
|
621
626
|
}) =>
|
622
627
|
Effect.gen(function* () {
|
@@ -639,7 +644,7 @@ const backgroundBackendPushing = ({
|
|
639
644
|
})
|
640
645
|
|
641
646
|
// TODO handle push errors (should only happen during concurrent pull+push)
|
642
|
-
const pushResult = yield* syncBackend.push(queueItems).pipe(Effect.either)
|
647
|
+
const pushResult = yield* syncBackend.push(queueItems.map((_) => _.toGlobal())).pipe(Effect.either)
|
643
648
|
|
644
649
|
if (pushResult._tag === 'Left') {
|
645
650
|
span?.addEvent('backend-push-error', { error: pushResult.left.toString() })
|
@@ -664,3 +669,9 @@ const backgroundBackendPushing = ({
|
|
664
669
|
}
|
665
670
|
}
|
666
671
|
}).pipe(Effect.interruptible, Effect.withSpan('@livestore/common:leader-thread:syncing:backend-pushing'))
|
672
|
+
|
673
|
+
const trimChangesetRows = (db: SynchronousDatabase, newHead: EventId.EventId) => {
|
674
|
+
// Since we're using the session changeset rows to query for the current head,
|
675
|
+
// we're keeping at least one row for the current head, and thus are using `<` instead of `<=`
|
676
|
+
db.execute(sql`DELETE FROM ${SESSION_CHANGESET_META_TABLE} WHERE idGlobal < ${newHead.global}`)
|
677
|
+
}
|
@@ -1,18 +1,11 @@
|
|
1
|
-
import { Effect, FiberMap, Option,
|
1
|
+
import { Effect, FiberMap, Option, Stream, SubscriptionRef } from '@livestore/utils/effect'
|
2
2
|
|
3
3
|
import { Devtools, IntentionalShutdownCause, liveStoreVersion, UnexpectedError } from '../index.js'
|
4
4
|
import { MUTATION_LOG_META_TABLE, SCHEMA_META_TABLE, SCHEMA_MUTATIONS_META_TABLE } from '../schema/mod.js'
|
5
|
-
import type { ShutdownChannel } from './shutdown-channel.js'
|
6
5
|
import type { DevtoolsOptions, PersistenceInfoPair } from './types.js'
|
7
6
|
import { LeaderThreadCtx } from './types.js'
|
8
7
|
|
9
|
-
type SendMessageToDevtools = (
|
10
|
-
message: Devtools.MessageFromAppLeader,
|
11
|
-
options?: {
|
12
|
-
/** Send message even if not connected (e.g. for initial broadcast messages) */
|
13
|
-
force: boolean
|
14
|
-
},
|
15
|
-
) => Effect.Effect<void>
|
8
|
+
type SendMessageToDevtools = (message: Devtools.MessageFromAppLeader) => Effect.Effect<void>
|
16
9
|
|
17
10
|
// TODO bind scope to the webchannel lifetime
|
18
11
|
export const bootDevtools = (options: DevtoolsOptions) =>
|
@@ -21,44 +14,24 @@ export const bootDevtools = (options: DevtoolsOptions) =>
|
|
21
14
|
return
|
22
15
|
}
|
23
16
|
|
24
|
-
const {
|
17
|
+
const { connectedClientSessionPullQueues, syncProcessor, extraIncomingMessagesQueue } = yield* LeaderThreadCtx
|
25
18
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
)
|
19
|
+
yield* listenToDevtools({
|
20
|
+
incomingMessages: Stream.fromQueue(extraIncomingMessagesQueue),
|
21
|
+
sendMessage: () => Effect.void,
|
22
|
+
}).pipe(Effect.tapCauseLogPretty, Effect.forkScoped)
|
31
23
|
|
32
|
-
const
|
24
|
+
const { persistenceInfo, devtoolsWebChannel } = yield* options.makeContext
|
33
25
|
|
34
|
-
const
|
35
|
-
|
36
|
-
|
26
|
+
const sendMessage: SendMessageToDevtools = (message) =>
|
27
|
+
devtoolsWebChannel
|
28
|
+
.send(message)
|
29
|
+
.pipe(
|
30
|
+
Effect.withSpan('@livestore/common:leader-thread:devtools:sendToDevtools'),
|
31
|
+
Effect.interruptible,
|
32
|
+
Effect.ignoreLogged,
|
33
|
+
)
|
37
34
|
|
38
|
-
const devtoolsCoordinatorChannel = devtoolsWebChannel
|
39
|
-
// coordinatorMessagePortOrChannel instanceof MessagePort
|
40
|
-
// ? yield* WebChannel.messagePortChannel({
|
41
|
-
// port: coordinatorMessagePortOrChannel,
|
42
|
-
// schema: { send: Devtools.MessageFromAppLeader, listen: Devtools.MessageToAppLeader },
|
43
|
-
// })
|
44
|
-
// : coordinatorMessagePortOrChannel
|
45
|
-
|
46
|
-
const sendMessage: SendMessageToDevtools = (message, options) =>
|
47
|
-
Effect.gen(function* () {
|
48
|
-
if (options?.force === true || (yield* isConnected)) {
|
49
|
-
yield* devtoolsCoordinatorChannel.send(message)
|
50
|
-
} else {
|
51
|
-
yield* Queue.offer(outgoingMessagesQueue, message)
|
52
|
-
}
|
53
|
-
}).pipe(
|
54
|
-
Effect.withSpan('@livestore/common:leader-thread:devtools:sendToDevtools'),
|
55
|
-
Effect.interruptible,
|
56
|
-
Effect.ignoreLogged,
|
57
|
-
)
|
58
|
-
|
59
|
-
// broadcastCallbacks.add((message) => sendMessage(message))
|
60
|
-
|
61
|
-
const { connectedClientSessionPullQueues, syncProcessor } = yield* LeaderThreadCtx
|
62
35
|
const { localHead } = yield* syncProcessor.syncState
|
63
36
|
|
64
37
|
// TODO close queue when devtools disconnects
|
@@ -69,13 +42,8 @@ export const bootDevtools = (options: DevtoolsOptions) =>
|
|
69
42
|
Effect.gen(function* () {
|
70
43
|
if (msg.payload._tag === 'upstream-advance') {
|
71
44
|
for (const mutationEventEncoded of msg.payload.newEvents) {
|
72
|
-
|
73
|
-
|
74
|
-
mutationEventEncoded,
|
75
|
-
|
76
|
-
liveStoreVersion,
|
77
|
-
}),
|
78
|
-
)
|
45
|
+
// TODO refactor with push semantics
|
46
|
+
yield* sendMessage(Devtools.MutationBroadcast.make({ mutationEventEncoded, liveStoreVersion }))
|
79
47
|
}
|
80
48
|
} else {
|
81
49
|
yield* Effect.logWarning('TODO implement rebases in devtools')
|
@@ -86,68 +54,25 @@ export const bootDevtools = (options: DevtoolsOptions) =>
|
|
86
54
|
Effect.forkScoped,
|
87
55
|
)
|
88
56
|
|
89
|
-
yield* devtoolsCoordinatorChannel.listen.pipe(
|
90
|
-
Stream.flatten(),
|
91
|
-
// Stream.tapLogWithLabel('@livestore/common:leader-thread:devtools:onPortMessage'),
|
92
|
-
Stream.tap((msg) =>
|
93
|
-
Effect.gen(function* () {
|
94
|
-
// yield* Effect.logDebug(`[@livestore/common:leader-thread:devtools] message from port: ${msg._tag}`, msg)
|
95
|
-
// if (msg._tag === 'LSD.MessagePortForStoreRes') {
|
96
|
-
// yield* Deferred.succeed(storeMessagePortDeferred, msg.port)
|
97
|
-
// } else {
|
98
|
-
yield* PubSub.publish(incomingMessagesPubSub, msg)
|
99
|
-
// }
|
100
|
-
}),
|
101
|
-
),
|
102
|
-
Stream.runDrain,
|
103
|
-
Effect.withSpan(`@livestore/common:leader-thread:devtools:onPortMessage`),
|
104
|
-
Effect.ignoreLogged,
|
105
|
-
Effect.forkScoped,
|
106
|
-
)
|
107
|
-
|
108
|
-
// yield* sendMessage(Devtools.AppHostReady.make({ appHostId, liveStoreVersion, isLeader }), { force: true })
|
109
|
-
|
110
|
-
// yield* sendMessage(Devtools.MessagePortForStoreReq.make({ appHostId, liveStoreVersion, requestId: nanoid() }), {
|
111
|
-
// force: true,
|
112
|
-
// })
|
113
|
-
|
114
57
|
yield* listenToDevtools({
|
115
|
-
incomingMessages,
|
58
|
+
incomingMessages: devtoolsWebChannel.listen.pipe(Stream.flatten(), Stream.orDie),
|
116
59
|
sendMessage,
|
117
|
-
// isConnected,
|
118
|
-
// disconnect,
|
119
|
-
// storeId,
|
120
|
-
// appHostId,
|
121
|
-
// isLeader,
|
122
60
|
persistenceInfo,
|
123
|
-
|
124
|
-
})
|
61
|
+
}).pipe(Effect.tapCauseLogPretty, Effect.forkScoped)
|
125
62
|
}).pipe(Effect.withSpan('@livestore/common:leader-thread:devtools:boot'))
|
126
63
|
|
127
64
|
const listenToDevtools = ({
|
128
65
|
incomingMessages,
|
129
66
|
sendMessage,
|
130
|
-
// isConnected,
|
131
|
-
// disconnect,
|
132
|
-
// appHostId,
|
133
|
-
// storeId,
|
134
|
-
// isLeader,
|
135
67
|
persistenceInfo,
|
136
|
-
shutdownChannel,
|
137
68
|
}: {
|
138
69
|
incomingMessages: Stream.Stream<Devtools.MessageToAppLeader>
|
139
70
|
sendMessage: SendMessageToDevtools
|
140
|
-
|
141
|
-
// disconnect: Effect.Effect<void>
|
142
|
-
// appHostId: string
|
143
|
-
// storeId: string
|
144
|
-
// isLeader: boolean
|
145
|
-
persistenceInfo: PersistenceInfoPair
|
146
|
-
shutdownChannel: ShutdownChannel
|
71
|
+
persistenceInfo?: PersistenceInfoPair
|
147
72
|
}) =>
|
148
73
|
Effect.gen(function* () {
|
149
|
-
const
|
150
|
-
|
74
|
+
const { syncBackend, makeSyncDb, db, dbLog, shutdownStateSubRef, shutdownChannel, syncProcessor } =
|
75
|
+
yield* LeaderThreadCtx
|
151
76
|
|
152
77
|
type RequestId = string
|
153
78
|
const subscriptionFiberMap = yield* FiberMap.make<RequestId>()
|
@@ -158,15 +83,6 @@ const listenToDevtools = ({
|
|
158
83
|
// yield* Effect.logDebug('[@livestore/common:leader-thread:devtools] incomingMessage', decodedEvent)
|
159
84
|
|
160
85
|
if (decodedEvent._tag === 'LSD.Disconnect') {
|
161
|
-
// yield* SubscriptionRef.set(isConnected, false)
|
162
|
-
|
163
|
-
// yield* disconnect
|
164
|
-
|
165
|
-
// TODO is there a better place for this?
|
166
|
-
// yield* sendMessage(Devtools.AppHostReady.make({ appHostId, liveStoreVersion, isLeader }), {
|
167
|
-
// force: true,
|
168
|
-
// })
|
169
|
-
|
170
86
|
return
|
171
87
|
}
|
172
88
|
|
@@ -226,7 +142,7 @@ const listenToDevtools = ({
|
|
226
142
|
|
227
143
|
yield* sendMessage(Devtools.LoadDatabaseFileRes.make({ ...reqPayload, status: 'ok' }))
|
228
144
|
|
229
|
-
yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-import' }))
|
145
|
+
yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-import' })) ?? Effect.void
|
230
146
|
|
231
147
|
return
|
232
148
|
}
|
@@ -243,11 +159,16 @@ const listenToDevtools = ({
|
|
243
159
|
|
244
160
|
yield* sendMessage(Devtools.ResetAllDataRes.make({ ...reqPayload }))
|
245
161
|
|
246
|
-
yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-reset' }))
|
162
|
+
yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-reset' })) ?? Effect.void
|
247
163
|
|
248
164
|
return
|
249
165
|
}
|
250
166
|
case 'LSD.Leader.DatabaseFileInfoReq': {
|
167
|
+
if (persistenceInfo === undefined) {
|
168
|
+
console.log('[@livestore/common:leader-thread:devtools] persistenceInfo is required for this request')
|
169
|
+
return
|
170
|
+
}
|
171
|
+
|
251
172
|
const dbSizeQuery = `SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size();`
|
252
173
|
const dbFileSize = db.select<{ size: number }>(dbSizeQuery, undefined)[0]!.size
|
253
174
|
const mutationLogFileSize = dbLog.select<{ size: number }>(dbSizeQuery, undefined)[0]!.size
|