@livestore/common 0.3.0-dev.1 → 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
@@ -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)
|
@@ -86,7 +101,7 @@ export const makeApplyMutation: Effect.Effect<ApplyMutation, never, Scope.Scope
|
|
86
101
|
attributes: {
|
87
102
|
mutationName: mutationEventEncoded.mutation,
|
88
103
|
mutationId: mutationEventEncoded.id,
|
89
|
-
'span.label': mutationEventEncoded.mutation
|
104
|
+
'span.label': `(${mutationEventEncoded.id.global},${mutationEventEncoded.id.local}) ${mutationEventEncoded.mutation}`,
|
90
105
|
},
|
91
106
|
}),
|
92
107
|
// Effect.logDuration('@livestore/common:leader-thread:applyMutation'),
|
@@ -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
|
}
|
@@ -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
|
@@ -1,41 +1,43 @@
|
|
1
|
-
import type { HttpClient, Scope
|
2
|
-
import { Deferred, Effect,
|
1
|
+
import type { HttpClient, Scope } from '@livestore/utils/effect'
|
2
|
+
import { Deferred, Effect, Layer, Queue, SubscriptionRef } from '@livestore/utils/effect'
|
3
3
|
|
4
4
|
import type { BootStatus, MakeSynchronousDatabase, SqliteError, SynchronousDatabase } from '../adapter-types.js'
|
5
5
|
import { UnexpectedError } from '../adapter-types.js'
|
6
|
+
import type * as Devtools from '../devtools/index.js'
|
6
7
|
import type { LiveStoreSchema } from '../schema/mod.js'
|
7
8
|
import { EventId, MutationEvent, mutationLogMetaTable, SYNC_STATUS_TABLE, syncStatusTable } from '../schema/mod.js'
|
8
9
|
import { migrateTable } from '../schema-management/migrations.js'
|
9
|
-
import type { InvalidPullError, IsOfflineError,
|
10
|
+
import type { InvalidPullError, IsOfflineError, SyncOptions } from '../sync/sync.js'
|
10
11
|
import { sql } from '../util.js'
|
11
12
|
import { execSql } from './connection.js'
|
12
|
-
import { makeLeaderSyncProcessor } from './leader-sync-processor.js'
|
13
13
|
import { bootDevtools } from './leader-worker-devtools.js'
|
14
|
+
import { makeLeaderSyncProcessor } from './LeaderSyncProcessor.js'
|
14
15
|
import { makePullQueueSet } from './pull-queue-set.js'
|
15
16
|
import { recreateDb } from './recreate-db.js'
|
17
|
+
import type { ShutdownChannel } from './shutdown-channel.js'
|
16
18
|
import type { DevtoolsOptions, InitialBlockingSyncContext, InitialSyncOptions, ShutdownState } from './types.js'
|
17
19
|
import { LeaderThreadCtx } from './types.js'
|
18
20
|
|
19
21
|
export const makeLeaderThreadLayer = ({
|
20
22
|
schema,
|
21
23
|
storeId,
|
22
|
-
|
24
|
+
clientId,
|
23
25
|
makeSyncDb,
|
24
|
-
|
26
|
+
syncOptions,
|
25
27
|
db,
|
26
28
|
dbLog,
|
27
29
|
devtoolsOptions,
|
28
|
-
|
30
|
+
shutdownChannel,
|
29
31
|
}: {
|
30
32
|
storeId: string
|
31
|
-
|
33
|
+
clientId: string
|
32
34
|
schema: LiveStoreSchema
|
33
35
|
makeSyncDb: MakeSynchronousDatabase
|
34
|
-
|
36
|
+
syncOptions: SyncOptions | undefined
|
35
37
|
db: SynchronousDatabase
|
36
38
|
dbLog: SynchronousDatabase
|
37
39
|
devtoolsOptions: DevtoolsOptions
|
38
|
-
|
40
|
+
shutdownChannel: ShutdownChannel
|
39
41
|
}): Layer.Layer<LeaderThreadCtx, UnexpectedError, Scope.Scope | HttpClient.HttpClient> =>
|
40
42
|
Effect.gen(function* () {
|
41
43
|
const bootStatusQueue = yield* Queue.unbounded<BootStatus>().pipe(Effect.acquireRelease(Queue.shutdown))
|
@@ -44,25 +46,34 @@ export const makeLeaderThreadLayer = ({
|
|
44
46
|
// Either happens on initial boot or if schema changes
|
45
47
|
const dbMissing = db.select<{ count: number }>(sql`select count(*) as count from sqlite_master`)[0]!.count === 0
|
46
48
|
|
47
|
-
const syncBackend =
|
49
|
+
const syncBackend = syncOptions === undefined ? undefined : yield* syncOptions.makeBackend({ storeId, clientId })
|
48
50
|
|
49
|
-
const initialBlockingSyncContext = yield* makeInitialBlockingSyncContext({
|
51
|
+
const initialBlockingSyncContext = yield* makeInitialBlockingSyncContext({
|
52
|
+
initialSyncOptions: syncOptions?.initialSyncOptions ?? { _tag: 'Skip' },
|
53
|
+
bootStatusQueue,
|
54
|
+
})
|
50
55
|
|
51
56
|
const syncProcessor = yield* makeLeaderSyncProcessor({ schema, dbMissing, dbLog, initialBlockingSyncContext })
|
52
57
|
|
58
|
+
const extraIncomingMessagesQueue = yield* Queue.unbounded<Devtools.MessageToAppLeader>().pipe(
|
59
|
+
Effect.acquireRelease(Queue.shutdown),
|
60
|
+
)
|
61
|
+
|
53
62
|
const ctx = {
|
54
63
|
schema,
|
55
64
|
bootStatusQueue,
|
56
65
|
storeId,
|
57
|
-
|
66
|
+
clientId,
|
58
67
|
db,
|
59
68
|
dbLog,
|
60
69
|
makeSyncDb,
|
61
70
|
mutationEventSchema: MutationEvent.makeMutationEventSchema(schema),
|
62
71
|
shutdownStateSubRef: yield* SubscriptionRef.make<ShutdownState>('running'),
|
72
|
+
shutdownChannel,
|
63
73
|
syncBackend,
|
64
74
|
syncProcessor,
|
65
75
|
connectedClientSessionPullQueues: yield* makePullQueueSet,
|
76
|
+
extraIncomingMessagesQueue,
|
66
77
|
} satisfies typeof LeaderThreadCtx.Service
|
67
78
|
|
68
79
|
// @ts-expect-error For debugging purposes
|
@@ -2,11 +2,14 @@ import { Effect, Schema } from '@livestore/utils/effect'
|
|
2
2
|
|
3
3
|
import type { SynchronousDatabase } from '../adapter-types.js'
|
4
4
|
import * as EventId from '../schema/EventId.js'
|
5
|
+
import type * as MutationEvent from '../schema/MutationEvent.js'
|
5
6
|
import { MUTATION_LOG_META_TABLE, mutationLogMetaTable, SYNC_STATUS_TABLE } from '../schema/system-tables.js'
|
6
7
|
import { prepareBindValues, sql } from '../util.js'
|
7
8
|
import { LeaderThreadCtx } from './types.js'
|
8
9
|
|
9
|
-
export const getMutationEventsSince = (
|
10
|
+
export const getMutationEventsSince = (
|
11
|
+
since: EventId.EventId,
|
12
|
+
): Effect.Effect<ReadonlyArray<MutationEvent.AnyEncoded>, never, LeaderThreadCtx> =>
|
10
13
|
Effect.gen(function* () {
|
11
14
|
const { dbLog } = yield* LeaderThreadCtx
|
12
15
|
|
@@ -26,16 +29,17 @@ export const getMutationEventsSince = (since: EventId.EventId) =>
|
|
26
29
|
.filter((_) => EventId.compare(_.id, since) > 0)
|
27
30
|
})
|
28
31
|
|
29
|
-
export const getLocalHeadFromDb = (dbLog: SynchronousDatabase) => {
|
30
|
-
const res = dbLog.select<{ idGlobal:
|
32
|
+
export const getLocalHeadFromDb = (dbLog: SynchronousDatabase): EventId.EventId => {
|
33
|
+
const res = dbLog.select<{ idGlobal: EventId.GlobalEventId; idLocal: EventId.LocalEventId }>(
|
31
34
|
sql`select idGlobal, idLocal from ${MUTATION_LOG_META_TABLE} order by idGlobal DESC, idLocal DESC limit 1`,
|
32
35
|
)[0]
|
33
36
|
|
34
37
|
return res ? { global: res.idGlobal, local: res.idLocal } : EventId.ROOT
|
35
38
|
}
|
36
39
|
|
37
|
-
export const getBackendHeadFromDb = (dbLog: SynchronousDatabase) =>
|
38
|
-
dbLog.select<{ head:
|
40
|
+
export const getBackendHeadFromDb = (dbLog: SynchronousDatabase): EventId.GlobalEventId =>
|
41
|
+
dbLog.select<{ head: EventId.GlobalEventId }>(sql`select head from ${SYNC_STATUS_TABLE}`)[0]?.head ??
|
42
|
+
EventId.ROOT.global
|
39
43
|
|
40
44
|
// TODO use prepared statements
|
41
45
|
export const updateBackendHead = (dbLog: SynchronousDatabase, head: EventId.EventId) =>
|
@@ -24,7 +24,9 @@ export const recreateDb: Effect.Effect<
|
|
24
24
|
|
25
25
|
// NOTE to speed up the operations below, we're creating a temporary in-memory database
|
26
26
|
// and later we'll overwrite the persisted database with the new data
|
27
|
-
|
27
|
+
// TODO bring back this optimization
|
28
|
+
// const tmpSyncDb = yield* makeSyncDb({ _tag: 'in-memory' })
|
29
|
+
const tmpSyncDb = db
|
28
30
|
yield* configureConnection(tmpSyncDb, { fkEnabled: true })
|
29
31
|
|
30
32
|
const initDb = (hooks: Partial<MigrationHooks> | undefined) =>
|
@@ -91,17 +93,19 @@ export const recreateDb: Effect.Effect<
|
|
91
93
|
}
|
92
94
|
}
|
93
95
|
|
96
|
+
// TODO bring back
|
94
97
|
// Import the temporary in-memory database into the persistent database
|
95
|
-
yield* Effect.sync(() => db.import(tmpSyncDb)).pipe(
|
96
|
-
|
97
|
-
)
|
98
|
+
// yield* Effect.sync(() => db.import(tmpSyncDb)).pipe(
|
99
|
+
// Effect.withSpan('@livestore/common:leader-thread:recreateDb:import'),
|
100
|
+
// )
|
98
101
|
|
99
102
|
// TODO maybe bring back re-using this initial snapshot to avoid calling `.export()` again
|
100
103
|
// We've disabled this for now as it made the code too complex, as we often run syncing right after
|
101
104
|
// so the snapshot is no longer up to date
|
102
105
|
// const snapshotFromTmpDb = tmpSyncDb.export()
|
103
106
|
|
104
|
-
|
107
|
+
// TODO bring back
|
108
|
+
// tmpSyncDb.close()
|
105
109
|
}).pipe(
|
106
110
|
Effect.scoped, // NOTE we're closing the scope here so finalizers are called when the effect is done
|
107
111
|
Effect.withSpan('@livestore/common:leader-thread:recreateDb'),
|
@@ -6,6 +6,7 @@ import type {
|
|
6
6
|
Option,
|
7
7
|
Queue,
|
8
8
|
Scope,
|
9
|
+
Subscribable,
|
9
10
|
SubscriptionRef,
|
10
11
|
WebChannel,
|
11
12
|
} from '@livestore/utils/effect'
|
@@ -22,7 +23,7 @@ import type {
|
|
22
23
|
UnexpectedError,
|
23
24
|
} from '../index.js'
|
24
25
|
import type { EventId, LiveStoreSchema, MutationEvent } from '../schema/mod.js'
|
25
|
-
import type
|
26
|
+
import type * as SyncState from '../sync/syncstate.js'
|
26
27
|
import type { ShutdownChannel } from './shutdown-channel.js'
|
27
28
|
|
28
29
|
export type ShutdownState = 'running' | 'shutting-down'
|
@@ -67,7 +68,6 @@ export type DevtoolsOptions =
|
|
67
68
|
makeContext: Effect.Effect<
|
68
69
|
{
|
69
70
|
devtoolsWebChannel: WebChannel.WebChannel<Devtools.MessageToAppLeader, Devtools.MessageFromAppLeader>
|
70
|
-
shutdownChannel: ShutdownChannel
|
71
71
|
persistenceInfo: PersistenceInfoPair
|
72
72
|
},
|
73
73
|
UnexpectedError,
|
@@ -80,18 +80,21 @@ export class LeaderThreadCtx extends Context.Tag('LeaderThreadCtx')<
|
|
80
80
|
{
|
81
81
|
schema: LiveStoreSchema
|
82
82
|
storeId: string
|
83
|
-
|
83
|
+
clientId: string
|
84
84
|
makeSyncDb: MakeSynchronousDatabase
|
85
85
|
db: LeaderDatabase
|
86
86
|
dbLog: LeaderDatabase
|
87
87
|
bootStatusQueue: Queue.Queue<BootStatus>
|
88
88
|
// TODO we should find a more elegant way to handle cases which need this ref for their implementation
|
89
89
|
shutdownStateSubRef: SubscriptionRef.SubscriptionRef<ShutdownState>
|
90
|
+
shutdownChannel: ShutdownChannel
|
90
91
|
mutationEventSchema: MutationEvent.ForMutationDefRecord<any>
|
91
92
|
// devtools: DevtoolsContext
|
92
93
|
syncBackend: SyncBackend | undefined
|
93
|
-
syncProcessor:
|
94
|
+
syncProcessor: LeaderSyncProcessor
|
94
95
|
connectedClientSessionPullQueues: PullQueueSet
|
96
|
+
/** e.g. used for `store.__dev` APIs */
|
97
|
+
extraIncomingMessagesQueue: Queue.Queue<Devtools.MessageToAppLeader>
|
95
98
|
}
|
96
99
|
>() {}
|
97
100
|
|
@@ -101,24 +104,28 @@ export type InitialBlockingSyncContext = {
|
|
101
104
|
}
|
102
105
|
|
103
106
|
export type PullQueueItem = {
|
104
|
-
|
105
|
-
// backendHead: number
|
106
|
-
payload: PayloadUpstream
|
107
|
-
// TODO move `remaining` into `PayloadUpstream`
|
107
|
+
payload: SyncState.PayloadUpstream
|
108
108
|
remaining: number
|
109
109
|
}
|
110
110
|
|
111
|
-
export interface
|
111
|
+
export interface LeaderSyncProcessor {
|
112
112
|
push: (
|
113
113
|
/** `batch` needs to follow the same rules as `batch` in `SyncBackend.push` */
|
114
114
|
batch: ReadonlyArray<MutationEvent.EncodedWithMeta>,
|
115
|
-
|
115
|
+
options?: {
|
116
|
+
/**
|
117
|
+
* If true, the effect will only finish when the local push has been processed (i.e. succeeded or was rejected).
|
118
|
+
* @default false
|
119
|
+
*/
|
120
|
+
waitForProcessing?: boolean
|
121
|
+
},
|
122
|
+
) => Effect.Effect<void, InvalidPushError>
|
116
123
|
|
117
124
|
pushPartial: (mutationEvent: MutationEvent.PartialAnyEncoded) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx>
|
118
125
|
boot: (args: {
|
119
126
|
dbReady: Deferred.Deferred<void>
|
120
127
|
}) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx | Scope.Scope | HttpClient.HttpClient>
|
121
|
-
syncState:
|
128
|
+
syncState: Subscribable.Subscribable<SyncState.SyncState>
|
122
129
|
}
|
123
130
|
|
124
131
|
export interface PullQueueSet {
|
package/src/mutation.ts
CHANGED
@@ -8,10 +8,19 @@ import { prepareBindValues } from './util.js'
|
|
8
8
|
|
9
9
|
export const getExecArgsFromMutation = ({
|
10
10
|
mutationDef,
|
11
|
-
|
11
|
+
mutationEvent,
|
12
12
|
}: {
|
13
13
|
mutationDef: MutationDef.Any
|
14
|
-
|
14
|
+
/** Both encoded and decoded mutation events are supported to reduce the number of times we need to decode/encode */
|
15
|
+
mutationEvent:
|
16
|
+
| {
|
17
|
+
decoded: MutationEvent.AnyDecoded | MutationEvent.PartialAnyDecoded
|
18
|
+
encoded: undefined
|
19
|
+
}
|
20
|
+
| {
|
21
|
+
decoded: undefined
|
22
|
+
encoded: MutationEvent.AnyEncoded | MutationEvent.PartialAnyEncoded
|
23
|
+
}
|
15
24
|
}): ReadonlyArray<{
|
16
25
|
statementSql: string
|
17
26
|
bindValues: PreparedBindValues
|
@@ -23,7 +32,9 @@ export const getExecArgsFromMutation = ({
|
|
23
32
|
|
24
33
|
switch (typeof mutationDef.sql) {
|
25
34
|
case 'function': {
|
26
|
-
const
|
35
|
+
const mutationArgsDecoded =
|
36
|
+
mutationEvent.decoded?.args ?? Schema.decodeUnknownSync(mutationDef.schema)(mutationEvent.encoded!.args)
|
37
|
+
const res = mutationDef.sql(mutationArgsDecoded)
|
27
38
|
statementRes = Array.isArray(res) ? res : [res]
|
28
39
|
break
|
29
40
|
}
|
@@ -40,10 +51,9 @@ export const getExecArgsFromMutation = ({
|
|
40
51
|
return statementRes.map((statementRes) => {
|
41
52
|
const statementSql = typeof statementRes === 'string' ? statementRes : statementRes.sql
|
42
53
|
|
43
|
-
const
|
44
|
-
|
45
|
-
|
46
|
-
: statementRes.bindValues
|
54
|
+
const mutationArgsEncoded =
|
55
|
+
mutationEvent.encoded?.args ?? Schema.encodeUnknownSync(mutationDef.schema)(mutationEvent.decoded!.args)
|
56
|
+
const bindValues = typeof statementRes === 'string' ? mutationArgsEncoded : statementRes.bindValues
|
47
57
|
|
48
58
|
const writeTables = typeof statementRes === 'string' ? undefined : statementRes.writeTables
|
49
59
|
|