@livestore/common 0.3.0-dev.10 → 0.3.0-dev.12
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 +62 -30
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js +12 -0
- package/dist/adapter-types.js.map +1 -1
- package/dist/devtools/devtool-message-leader.d.ts +2 -0
- package/dist/devtools/devtool-message-leader.d.ts.map +1 -0
- package/dist/devtools/devtool-message-leader.js +2 -0
- package/dist/devtools/devtool-message-leader.js.map +1 -0
- package/dist/devtools/devtools-bridge.d.ts +10 -7
- package/dist/devtools/devtools-bridge.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +370 -0
- package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -0
- package/dist/devtools/devtools-messages-client-session.js +77 -0
- package/dist/devtools/devtools-messages-client-session.js.map +1 -0
- package/dist/devtools/devtools-messages-common.d.ts +57 -0
- package/dist/devtools/devtools-messages-common.d.ts.map +1 -0
- package/dist/devtools/devtools-messages-common.js +44 -0
- package/dist/devtools/devtools-messages-common.js.map +1 -0
- package/dist/devtools/devtools-messages-leader.d.ts +437 -0
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -0
- package/dist/devtools/devtools-messages-leader.js +132 -0
- package/dist/devtools/devtools-messages-leader.js.map +1 -0
- package/dist/devtools/devtools-messages.d.ts +3 -580
- package/dist/devtools/devtools-messages.d.ts.map +1 -1
- package/dist/devtools/devtools-messages.js +3 -174
- package/dist/devtools/devtools-messages.js.map +1 -1
- package/dist/init-singleton-tables.d.ts +2 -2
- package/dist/init-singleton-tables.d.ts.map +1 -1
- package/dist/init-singleton-tables.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +4 -4
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +64 -36
- 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 +4 -4
- package/dist/leader-thread/apply-mutation.js.map +1 -1
- package/dist/leader-thread/connection.d.ts +34 -6
- package/dist/leader-thread/connection.d.ts.map +1 -1
- package/dist/leader-thread/connection.js +22 -7
- package/dist/leader-thread/connection.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +67 -36
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +6 -6
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +38 -13
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/mutationlog.d.ts +4 -4
- package/dist/leader-thread/mutationlog.d.ts.map +1 -1
- package/dist/leader-thread/mutationlog.js +6 -6
- package/dist/leader-thread/mutationlog.js.map +1 -1
- package/dist/leader-thread/pull-queue-set.d.ts.map +1 -1
- package/dist/leader-thread/recreate-db.d.ts +4 -2
- package/dist/leader-thread/recreate-db.d.ts.map +1 -1
- package/dist/leader-thread/recreate-db.js +27 -22
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/leader-thread/types.d.ts +32 -17
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/leader-thread/types.js +0 -2
- package/dist/leader-thread/types.js.map +1 -1
- package/dist/query-builder/api.d.ts +2 -2
- package/dist/query-builder/api.d.ts.map +1 -1
- package/dist/query-builder/impl.js.map +1 -1
- package/dist/query-builder/impl.test.js +16 -1
- package/dist/query-builder/impl.test.js.map +1 -1
- package/dist/query-info.d.ts +3 -3
- package/dist/query-info.d.ts.map +1 -1
- package/dist/rehydrate-from-mutationlog.d.ts +3 -3
- package/dist/rehydrate-from-mutationlog.d.ts.map +1 -1
- package/dist/rehydrate-from-mutationlog.js.map +1 -1
- package/dist/schema/EventId.d.ts +1 -0
- package/dist/schema/EventId.d.ts.map +1 -1
- package/dist/schema/EventId.js +3 -0
- package/dist/schema/EventId.js.map +1 -1
- package/dist/schema/mutations.d.ts +1 -1
- package/dist/schema/system-tables.d.ts +1 -1
- package/dist/schema-management/common.d.ts +3 -3
- package/dist/schema-management/common.d.ts.map +1 -1
- package/dist/schema-management/common.js.map +1 -1
- package/dist/schema-management/migrations.d.ts +5 -5
- package/dist/schema-management/migrations.d.ts.map +1 -1
- package/dist/schema-management/migrations.js +6 -1
- package/dist/schema-management/migrations.js.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +8 -12
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +31 -13
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/next/test/mutation-fixtures.d.ts +7 -7
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/src/adapter-types.ts +52 -33
- package/src/devtools/devtools-bridge.ts +10 -7
- package/src/devtools/devtools-messages-client-session.ts +125 -0
- package/src/devtools/devtools-messages-common.ts +81 -0
- package/src/devtools/devtools-messages-leader.ts +176 -0
- package/src/devtools/devtools-messages.ts +3 -246
- package/src/init-singleton-tables.ts +2 -2
- package/src/leader-thread/LeaderSyncProcessor.ts +94 -46
- package/src/leader-thread/apply-mutation.ts +5 -5
- package/src/leader-thread/connection.ts +54 -9
- package/src/leader-thread/leader-worker-devtools.ts +105 -41
- package/src/leader-thread/make-leader-thread-layer.ts +55 -22
- package/src/leader-thread/mutationlog.ts +9 -9
- package/src/leader-thread/recreate-db.ts +33 -24
- package/src/leader-thread/types.ts +38 -21
- package/src/query-builder/api.ts +3 -3
- package/src/query-builder/impl.test.ts +22 -1
- package/src/query-builder/impl.ts +2 -2
- package/src/query-info.ts +3 -3
- package/src/rehydrate-from-mutationlog.ts +3 -3
- package/src/schema/EventId.ts +4 -0
- package/src/schema-management/common.ts +3 -3
- package/src/schema-management/migrations.ts +12 -8
- package/src/sync/ClientSessionSyncProcessor.ts +38 -22
- package/src/version.ts +1 -1
@@ -5,7 +5,7 @@ import { MUTATION_LOG_META_TABLE, SCHEMA_META_TABLE, SCHEMA_MUTATIONS_META_TABLE
|
|
5
5
|
import type { DevtoolsOptions, PersistenceInfoPair } from './types.js'
|
6
6
|
import { LeaderThreadCtx } from './types.js'
|
7
7
|
|
8
|
-
type SendMessageToDevtools = (message: Devtools.
|
8
|
+
type SendMessageToDevtools = (message: Devtools.Leader.MessageFromApp) => Effect.Effect<void>
|
9
9
|
|
10
10
|
// TODO bind scope to the webchannel lifetime
|
11
11
|
export const bootDevtools = (options: DevtoolsOptions) =>
|
@@ -21,7 +21,7 @@ export const bootDevtools = (options: DevtoolsOptions) =>
|
|
21
21
|
sendMessage: () => Effect.void,
|
22
22
|
}).pipe(Effect.tapCauseLogPretty, Effect.forkScoped)
|
23
23
|
|
24
|
-
const { persistenceInfo, devtoolsWebChannel } = yield* options.
|
24
|
+
const { persistenceInfo, devtoolsWebChannel } = yield* options.makeBootContext
|
25
25
|
|
26
26
|
const sendMessage: SendMessageToDevtools = (message) =>
|
27
27
|
devtoolsWebChannel
|
@@ -43,7 +43,7 @@ export const bootDevtools = (options: DevtoolsOptions) =>
|
|
43
43
|
if (msg.payload._tag === 'upstream-advance') {
|
44
44
|
for (const mutationEventEncoded of msg.payload.newEvents) {
|
45
45
|
// TODO refactor with push semantics
|
46
|
-
yield* sendMessage(Devtools.MutationBroadcast.make({ mutationEventEncoded, liveStoreVersion }))
|
46
|
+
yield* sendMessage(Devtools.Leader.MutationBroadcast.make({ mutationEventEncoded, liveStoreVersion }))
|
47
47
|
}
|
48
48
|
} else {
|
49
49
|
yield* Effect.logWarning('TODO implement rebases in devtools')
|
@@ -66,13 +66,22 @@ const listenToDevtools = ({
|
|
66
66
|
sendMessage,
|
67
67
|
persistenceInfo,
|
68
68
|
}: {
|
69
|
-
incomingMessages: Stream.Stream<Devtools.
|
69
|
+
incomingMessages: Stream.Stream<Devtools.Leader.MessageToApp>
|
70
70
|
sendMessage: SendMessageToDevtools
|
71
71
|
persistenceInfo?: PersistenceInfoPair
|
72
72
|
}) =>
|
73
73
|
Effect.gen(function* () {
|
74
|
-
const {
|
75
|
-
|
74
|
+
const {
|
75
|
+
syncBackend,
|
76
|
+
makeSqliteDb,
|
77
|
+
dbReadModel,
|
78
|
+
dbMutationLog,
|
79
|
+
shutdownStateSubRef,
|
80
|
+
shutdownChannel,
|
81
|
+
syncProcessor,
|
82
|
+
clientId,
|
83
|
+
devtools,
|
84
|
+
} = yield* LeaderThreadCtx
|
76
85
|
|
77
86
|
type RequestId = string
|
78
87
|
const subscriptionFiberMap = yield* FiberMap.make<RequestId>()
|
@@ -82,22 +91,22 @@ const listenToDevtools = ({
|
|
82
91
|
Effect.gen(function* () {
|
83
92
|
// yield* Effect.logDebug('[@livestore/common:leader-thread:devtools] incomingMessage', decodedEvent)
|
84
93
|
|
85
|
-
if (decodedEvent._tag === 'LSD.Disconnect') {
|
94
|
+
if (decodedEvent._tag === 'LSD.Leader.Disconnect') {
|
86
95
|
return
|
87
96
|
}
|
88
97
|
|
89
98
|
const { requestId } = decodedEvent
|
90
|
-
const reqPayload = { requestId, liveStoreVersion }
|
99
|
+
const reqPayload = { requestId, liveStoreVersion, clientId }
|
91
100
|
|
92
101
|
switch (decodedEvent._tag) {
|
93
|
-
case 'LSD.Ping': {
|
94
|
-
yield* sendMessage(Devtools.Pong.make({ ...reqPayload }))
|
102
|
+
case 'LSD.Leader.Ping': {
|
103
|
+
yield* sendMessage(Devtools.Leader.Pong.make({ ...reqPayload }))
|
95
104
|
return
|
96
105
|
}
|
97
106
|
case 'LSD.Leader.SnapshotReq': {
|
98
|
-
const snapshot =
|
107
|
+
const snapshot = dbReadModel.export()
|
99
108
|
|
100
|
-
yield* sendMessage(Devtools.SnapshotRes.make({ snapshot, ...reqPayload }))
|
109
|
+
yield* sendMessage(Devtools.Leader.SnapshotRes.make({ snapshot, ...reqPayload }))
|
101
110
|
|
102
111
|
return
|
103
112
|
}
|
@@ -107,18 +116,20 @@ const listenToDevtools = ({
|
|
107
116
|
let tableNames: Set<string>
|
108
117
|
|
109
118
|
try {
|
110
|
-
const
|
111
|
-
|
112
|
-
const tableNameResults =
|
119
|
+
const tmpDb = yield* makeSqliteDb({ _tag: 'in-memory' })
|
120
|
+
tmpDb.import(data)
|
121
|
+
const tableNameResults = tmpDb.select<{ name: string }>(
|
113
122
|
`select name from sqlite_master where type = 'table'`,
|
114
123
|
)
|
115
124
|
|
116
125
|
tableNames = new Set(tableNameResults.map((_) => _.name))
|
117
126
|
|
118
|
-
|
127
|
+
tmpDb.close()
|
119
128
|
} catch (e) {
|
120
129
|
yield* Effect.logError(`Error importing database file`, e)
|
121
|
-
yield* sendMessage(
|
130
|
+
yield* sendMessage(
|
131
|
+
Devtools.Leader.LoadDatabaseFileRes.make({ ...reqPayload, status: 'unsupported-file' }),
|
132
|
+
)
|
122
133
|
|
123
134
|
return
|
124
135
|
}
|
@@ -126,38 +137,40 @@ const listenToDevtools = ({
|
|
126
137
|
if (tableNames.has(MUTATION_LOG_META_TABLE)) {
|
127
138
|
yield* SubscriptionRef.set(shutdownStateSubRef, 'shutting-down')
|
128
139
|
|
129
|
-
|
140
|
+
dbMutationLog.import(data)
|
130
141
|
|
131
|
-
|
142
|
+
dbReadModel.destroy()
|
132
143
|
} else if (tableNames.has(SCHEMA_META_TABLE) && tableNames.has(SCHEMA_MUTATIONS_META_TABLE)) {
|
133
144
|
yield* SubscriptionRef.set(shutdownStateSubRef, 'shutting-down')
|
134
145
|
|
135
|
-
|
146
|
+
dbReadModel.import(data)
|
136
147
|
|
137
|
-
|
148
|
+
dbMutationLog.destroy()
|
138
149
|
} else {
|
139
|
-
yield* sendMessage(
|
150
|
+
yield* sendMessage(
|
151
|
+
Devtools.Leader.LoadDatabaseFileRes.make({ ...reqPayload, status: 'unsupported-database' }),
|
152
|
+
)
|
140
153
|
return
|
141
154
|
}
|
142
155
|
|
143
|
-
yield* sendMessage(Devtools.LoadDatabaseFileRes.make({ ...reqPayload, status: 'ok' }))
|
156
|
+
yield* sendMessage(Devtools.Leader.LoadDatabaseFileRes.make({ ...reqPayload, status: 'ok' }))
|
144
157
|
|
145
158
|
yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-import' })) ?? Effect.void
|
146
159
|
|
147
160
|
return
|
148
161
|
}
|
149
|
-
case 'LSD.Leader.
|
162
|
+
case 'LSD.Leader.ResetAllData.Request': {
|
150
163
|
const { mode } = decodedEvent
|
151
164
|
|
152
165
|
yield* SubscriptionRef.set(shutdownStateSubRef, 'shutting-down')
|
153
166
|
|
154
|
-
|
167
|
+
dbReadModel.destroy()
|
155
168
|
|
156
169
|
if (mode === 'all-data') {
|
157
|
-
|
170
|
+
dbMutationLog.destroy()
|
158
171
|
}
|
159
172
|
|
160
|
-
yield* sendMessage(Devtools.
|
173
|
+
yield* sendMessage(Devtools.Leader.ResetAllData.Response.make({ ...reqPayload }))
|
161
174
|
|
162
175
|
yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-reset' })) ?? Effect.void
|
163
176
|
|
@@ -170,12 +183,12 @@ const listenToDevtools = ({
|
|
170
183
|
}
|
171
184
|
|
172
185
|
const dbSizeQuery = `SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size();`
|
173
|
-
const dbFileSize =
|
174
|
-
const mutationLogFileSize =
|
186
|
+
const dbFileSize = dbReadModel.select<{ size: number }>(dbSizeQuery, undefined)[0]!.size
|
187
|
+
const mutationLogFileSize = dbMutationLog.select<{ size: number }>(dbSizeQuery, undefined)[0]!.size
|
175
188
|
|
176
189
|
yield* sendMessage(
|
177
|
-
Devtools.DatabaseFileInfoRes.make({
|
178
|
-
|
190
|
+
Devtools.Leader.DatabaseFileInfoRes.make({
|
191
|
+
readModel: { fileSize: dbFileSize, persistenceInfo: persistenceInfo.readModel },
|
179
192
|
mutationLog: { fileSize: mutationLogFileSize, persistenceInfo: persistenceInfo.mutationLog },
|
180
193
|
...reqPayload,
|
181
194
|
}),
|
@@ -184,16 +197,16 @@ const listenToDevtools = ({
|
|
184
197
|
return
|
185
198
|
}
|
186
199
|
case 'LSD.Leader.MutationLogReq': {
|
187
|
-
const mutationLog =
|
200
|
+
const mutationLog = dbMutationLog.export()
|
188
201
|
|
189
|
-
yield* sendMessage(Devtools.MutationLogRes.make({ mutationLog, ...reqPayload }))
|
202
|
+
yield* sendMessage(Devtools.Leader.MutationLogRes.make({ mutationLog, ...reqPayload }))
|
190
203
|
|
191
204
|
return
|
192
205
|
}
|
193
206
|
case 'LSD.Leader.RunMutationReq': {
|
194
207
|
yield* syncProcessor.pushPartial(decodedEvent.mutationEventEncoded)
|
195
208
|
|
196
|
-
yield* sendMessage(Devtools.RunMutationRes.make({ ...reqPayload }))
|
209
|
+
yield* sendMessage(Devtools.Leader.RunMutationRes.make({ ...reqPayload }))
|
197
210
|
|
198
211
|
return
|
199
212
|
}
|
@@ -206,7 +219,7 @@ const listenToDevtools = ({
|
|
206
219
|
Stream.map((_) => _.batch),
|
207
220
|
Stream.flattenIterables,
|
208
221
|
Stream.tap(({ mutationEventEncoded, metadata }) =>
|
209
|
-
sendMessage(Devtools.SyncHistoryRes.make({ mutationEventEncoded, metadata, ...reqPayload })),
|
222
|
+
sendMessage(Devtools.Leader.SyncHistoryRes.make({ mutationEventEncoded, metadata, ...reqPayload })),
|
210
223
|
),
|
211
224
|
Stream.runDrain,
|
212
225
|
Effect.acquireRelease(() => Effect.log('syncHistorySubscribe done')),
|
@@ -227,12 +240,12 @@ const listenToDevtools = ({
|
|
227
240
|
return
|
228
241
|
}
|
229
242
|
case 'LSD.Leader.SyncingInfoReq': {
|
230
|
-
const syncingInfo = Devtools.SyncingInfo.make({
|
243
|
+
const syncingInfo = Devtools.Leader.SyncingInfo.make({
|
231
244
|
enabled: syncBackend !== undefined,
|
232
245
|
metadata: {},
|
233
246
|
})
|
234
247
|
|
235
|
-
yield* sendMessage(Devtools.SyncingInfoRes.make({ syncingInfo, ...reqPayload }))
|
248
|
+
yield* sendMessage(Devtools.Leader.SyncingInfoRes.make({ syncingInfo, ...reqPayload }))
|
236
249
|
|
237
250
|
return
|
238
251
|
}
|
@@ -245,11 +258,14 @@ const listenToDevtools = ({
|
|
245
258
|
// This is probably the same "flaky databrowser loading" bug as we're seeing in the playwright tests
|
246
259
|
yield* Effect.sleep(1000)
|
247
260
|
|
248
|
-
yield*
|
249
|
-
|
261
|
+
yield* Stream.zipLatest(
|
262
|
+
syncBackend.isConnected.changes,
|
263
|
+
devtools.enabled ? devtools.syncBackendLatchState.changes : Stream.make({ latchClosed: false }),
|
264
|
+
).pipe(
|
265
|
+
Stream.tap(([isConnected, { latchClosed }]) =>
|
250
266
|
sendMessage(
|
251
|
-
Devtools.NetworkStatusRes.make({
|
252
|
-
networkStatus: { isConnected, timestampMs: Date.now() },
|
267
|
+
Devtools.Leader.NetworkStatusRes.make({
|
268
|
+
networkStatus: { isConnected, timestampMs: Date.now(), latchClosed },
|
253
269
|
...reqPayload,
|
254
270
|
}),
|
255
271
|
),
|
@@ -270,6 +286,54 @@ const listenToDevtools = ({
|
|
270
286
|
|
271
287
|
return
|
272
288
|
}
|
289
|
+
case 'LSD.Leader.SyncHeadSubscribe': {
|
290
|
+
const { requestId } = decodedEvent
|
291
|
+
|
292
|
+
yield* syncProcessor.syncState.changes.pipe(
|
293
|
+
Stream.tap((syncState) =>
|
294
|
+
sendMessage(
|
295
|
+
Devtools.Leader.SyncHeadRes.make({
|
296
|
+
local: syncState.localHead,
|
297
|
+
upstream: syncState.upstreamHead,
|
298
|
+
...reqPayload,
|
299
|
+
}),
|
300
|
+
),
|
301
|
+
),
|
302
|
+
Stream.runDrain,
|
303
|
+
Effect.interruptible,
|
304
|
+
Effect.tapCauseLogPretty,
|
305
|
+
FiberMap.run(subscriptionFiberMap, requestId),
|
306
|
+
)
|
307
|
+
|
308
|
+
return
|
309
|
+
}
|
310
|
+
case 'LSD.Leader.SyncHeadUnsubscribe': {
|
311
|
+
const { requestId } = decodedEvent
|
312
|
+
|
313
|
+
yield* FiberMap.remove(subscriptionFiberMap, requestId)
|
314
|
+
|
315
|
+
return
|
316
|
+
}
|
317
|
+
case 'LSD.Leader.SetSyncLatch.Request': {
|
318
|
+
const { closeLatch } = decodedEvent
|
319
|
+
|
320
|
+
if (devtools.enabled === false) return
|
321
|
+
|
322
|
+
if (closeLatch === true) {
|
323
|
+
yield* devtools.syncBackendLatch.close
|
324
|
+
} else {
|
325
|
+
yield* devtools.syncBackendLatch.open
|
326
|
+
}
|
327
|
+
|
328
|
+
yield* SubscriptionRef.set(devtools.syncBackendLatchState, { latchClosed: closeLatch })
|
329
|
+
|
330
|
+
yield* sendMessage(Devtools.Leader.SetSyncLatch.Response.make({ ...reqPayload }))
|
331
|
+
|
332
|
+
return
|
333
|
+
}
|
334
|
+
default: {
|
335
|
+
yield* Effect.logWarning(`TODO implement devtools message`, decodedEvent)
|
336
|
+
}
|
273
337
|
}
|
274
338
|
}).pipe(Effect.withSpan(`@livestore/common:leader-thread:onDevtoolsMessage:${decodedEvent._tag}`)),
|
275
339
|
),
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import type { HttpClient, Scope } from '@livestore/utils/effect'
|
2
2
|
import { Deferred, Effect, Layer, Queue, SubscriptionRef } from '@livestore/utils/effect'
|
3
3
|
|
4
|
-
import type { BootStatus,
|
4
|
+
import type { BootStatus, MakeSqliteDb, MigrationsReport, SqliteError } from '../adapter-types.js'
|
5
5
|
import { UnexpectedError } from '../adapter-types.js'
|
6
6
|
import type * as Devtools from '../devtools/index.js'
|
7
7
|
import type { LiveStoreSchema } from '../schema/mod.js'
|
@@ -15,27 +15,33 @@ import { makeLeaderSyncProcessor } from './LeaderSyncProcessor.js'
|
|
15
15
|
import { makePullQueueSet } from './pull-queue-set.js'
|
16
16
|
import { recreateDb } from './recreate-db.js'
|
17
17
|
import type { ShutdownChannel } from './shutdown-channel.js'
|
18
|
-
import type {
|
18
|
+
import type {
|
19
|
+
DevtoolsOptions,
|
20
|
+
InitialBlockingSyncContext,
|
21
|
+
InitialSyncOptions,
|
22
|
+
LeaderSqliteDb,
|
23
|
+
ShutdownState,
|
24
|
+
} from './types.js'
|
19
25
|
import { LeaderThreadCtx } from './types.js'
|
20
26
|
|
21
27
|
export const makeLeaderThreadLayer = ({
|
22
28
|
schema,
|
23
29
|
storeId,
|
24
30
|
clientId,
|
25
|
-
|
31
|
+
makeSqliteDb,
|
26
32
|
syncOptions,
|
27
|
-
|
28
|
-
|
33
|
+
dbReadModel,
|
34
|
+
dbMutationLog,
|
29
35
|
devtoolsOptions,
|
30
36
|
shutdownChannel,
|
31
37
|
}: {
|
32
38
|
storeId: string
|
33
39
|
clientId: string
|
34
40
|
schema: LiveStoreSchema
|
35
|
-
|
41
|
+
makeSqliteDb: MakeSqliteDb
|
36
42
|
syncOptions: SyncOptions | undefined
|
37
|
-
|
38
|
-
|
43
|
+
dbReadModel: LeaderSqliteDb
|
44
|
+
dbMutationLog: LeaderSqliteDb
|
39
45
|
devtoolsOptions: DevtoolsOptions
|
40
46
|
shutdownChannel: ShutdownChannel
|
41
47
|
}): Layer.Layer<LeaderThreadCtx, UnexpectedError, Scope.Scope | HttpClient.HttpClient> =>
|
@@ -44,7 +50,8 @@ export const makeLeaderThreadLayer = ({
|
|
44
50
|
|
45
51
|
// TODO do more validation here than just checking the count of tables
|
46
52
|
// Either happens on initial boot or if schema changes
|
47
|
-
const dbMissing =
|
53
|
+
const dbMissing =
|
54
|
+
dbReadModel.select<{ count: number }>(sql`select count(*) as count from sqlite_master`)[0]!.count === 0
|
48
55
|
|
49
56
|
const syncBackend = syncOptions === undefined ? undefined : yield* syncOptions.makeBackend({ storeId, clientId })
|
50
57
|
|
@@ -53,20 +60,33 @@ export const makeLeaderThreadLayer = ({
|
|
53
60
|
bootStatusQueue,
|
54
61
|
})
|
55
62
|
|
56
|
-
const syncProcessor = yield* makeLeaderSyncProcessor({
|
63
|
+
const syncProcessor = yield* makeLeaderSyncProcessor({
|
64
|
+
schema,
|
65
|
+
dbMissing,
|
66
|
+
dbMutationLog,
|
67
|
+
initialBlockingSyncContext,
|
68
|
+
})
|
57
69
|
|
58
|
-
const extraIncomingMessagesQueue = yield* Queue.unbounded<Devtools.
|
70
|
+
const extraIncomingMessagesQueue = yield* Queue.unbounded<Devtools.Leader.MessageToApp>().pipe(
|
59
71
|
Effect.acquireRelease(Queue.shutdown),
|
60
72
|
)
|
61
73
|
|
74
|
+
const devtoolsContext = devtoolsOptions.enabled
|
75
|
+
? {
|
76
|
+
enabled: true as const,
|
77
|
+
syncBackendLatch: yield* Effect.makeLatch(true),
|
78
|
+
syncBackendLatchState: yield* SubscriptionRef.make<{ latchClosed: boolean }>({ latchClosed: false }),
|
79
|
+
}
|
80
|
+
: { enabled: false as const }
|
81
|
+
|
62
82
|
const ctx = {
|
63
83
|
schema,
|
64
84
|
bootStatusQueue,
|
65
85
|
storeId,
|
66
86
|
clientId,
|
67
|
-
|
68
|
-
|
69
|
-
|
87
|
+
dbReadModel,
|
88
|
+
dbMutationLog,
|
89
|
+
makeSqliteDb,
|
70
90
|
mutationEventSchema: MutationEvent.makeMutationEventSchema(schema),
|
71
91
|
shutdownStateSubRef: yield* SubscriptionRef.make<ShutdownState>('running'),
|
72
92
|
shutdownChannel,
|
@@ -74,6 +94,9 @@ export const makeLeaderThreadLayer = ({
|
|
74
94
|
syncProcessor,
|
75
95
|
connectedClientSessionPullQueues: yield* makePullQueueSet,
|
76
96
|
extraIncomingMessagesQueue,
|
97
|
+
devtools: devtoolsContext,
|
98
|
+
// State will be set during `bootLeaderThread`
|
99
|
+
initialState: {} as any as LeaderThreadCtx['Type']['initialState'],
|
77
100
|
} satisfies typeof LeaderThreadCtx.Service
|
78
101
|
|
79
102
|
// @ts-expect-error For debugging purposes
|
@@ -81,7 +104,11 @@ export const makeLeaderThreadLayer = ({
|
|
81
104
|
|
82
105
|
const layer = Layer.succeed(LeaderThreadCtx, ctx)
|
83
106
|
|
84
|
-
yield* bootLeaderThread({
|
107
|
+
ctx.initialState = yield* bootLeaderThread({
|
108
|
+
dbMissing,
|
109
|
+
initialBlockingSyncContext,
|
110
|
+
devtoolsOptions,
|
111
|
+
}).pipe(Effect.provide(layer))
|
85
112
|
|
86
113
|
return layer
|
87
114
|
}).pipe(
|
@@ -151,22 +178,22 @@ const bootLeaderThread = ({
|
|
151
178
|
initialBlockingSyncContext: InitialBlockingSyncContext
|
152
179
|
devtoolsOptions: DevtoolsOptions
|
153
180
|
}): Effect.Effect<
|
154
|
-
|
181
|
+
LeaderThreadCtx['Type']['initialState'],
|
155
182
|
UnexpectedError | SqliteError | IsOfflineError | InvalidPullError,
|
156
183
|
LeaderThreadCtx | Scope.Scope | HttpClient.HttpClient
|
157
184
|
> =>
|
158
185
|
Effect.gen(function* () {
|
159
|
-
const {
|
186
|
+
const { dbMutationLog, bootStatusQueue, syncProcessor } = yield* LeaderThreadCtx
|
160
187
|
|
161
188
|
yield* migrateTable({
|
162
|
-
db:
|
189
|
+
db: dbMutationLog,
|
163
190
|
behaviour: 'create-if-not-exists',
|
164
191
|
tableAst: mutationLogMetaTable.sqliteDef.ast,
|
165
192
|
skipMetaTable: true,
|
166
193
|
})
|
167
194
|
|
168
195
|
yield* migrateTable({
|
169
|
-
db:
|
196
|
+
db: dbMutationLog,
|
170
197
|
behaviour: 'create-if-not-exists',
|
171
198
|
tableAst: syncStatusTable.sqliteDef.ast,
|
172
199
|
skipMetaTable: true,
|
@@ -174,7 +201,7 @@ const bootLeaderThread = ({
|
|
174
201
|
|
175
202
|
// Create sync status row if it doesn't exist
|
176
203
|
yield* execSql(
|
177
|
-
|
204
|
+
dbMutationLog,
|
178
205
|
sql`INSERT INTO ${SYNC_STATUS_TABLE} (head)
|
179
206
|
SELECT ${EventId.ROOT.global}
|
180
207
|
WHERE NOT EXISTS (SELECT 1 FROM ${SYNC_STATUS_TABLE})`,
|
@@ -185,10 +212,14 @@ const bootLeaderThread = ({
|
|
185
212
|
|
186
213
|
// We're already starting pulling from the sync backend concurrently but wait until the db is ready before
|
187
214
|
// processing any incoming mutations
|
188
|
-
yield* syncProcessor.boot({ dbReady })
|
215
|
+
const { initialLeaderHead } = yield* syncProcessor.boot({ dbReady })
|
189
216
|
|
217
|
+
let migrationsReport: MigrationsReport
|
190
218
|
if (dbMissing) {
|
191
|
-
yield* recreateDb
|
219
|
+
const recreateResult = yield* recreateDb
|
220
|
+
migrationsReport = recreateResult.migrationsReport
|
221
|
+
} else {
|
222
|
+
migrationsReport = { migrations: [] }
|
192
223
|
}
|
193
224
|
|
194
225
|
yield* Deferred.succeed(dbReady, void 0)
|
@@ -200,4 +231,6 @@ const bootLeaderThread = ({
|
|
200
231
|
yield* Queue.offer(bootStatusQueue, { stage: 'done' })
|
201
232
|
|
202
233
|
yield* bootDevtools(devtoolsOptions).pipe(Effect.tapCauseLogPretty, Effect.forkScoped)
|
234
|
+
|
235
|
+
return { migrationsReport, leaderHead: initialLeaderHead }
|
203
236
|
})
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { Effect, Schema } from '@livestore/utils/effect'
|
2
2
|
|
3
|
-
import type {
|
3
|
+
import type { SqliteDb } from '../adapter-types.js'
|
4
4
|
import * as EventId from '../schema/EventId.js'
|
5
5
|
import type * as MutationEvent from '../schema/MutationEvent.js'
|
6
6
|
import { MUTATION_LOG_META_TABLE, mutationLogMetaTable, SYNC_STATUS_TABLE } from '../schema/system-tables.js'
|
@@ -11,10 +11,10 @@ export const getMutationEventsSince = (
|
|
11
11
|
since: EventId.EventId,
|
12
12
|
): Effect.Effect<ReadonlyArray<MutationEvent.AnyEncoded>, never, LeaderThreadCtx> =>
|
13
13
|
Effect.gen(function* () {
|
14
|
-
const {
|
14
|
+
const { dbMutationLog } = yield* LeaderThreadCtx
|
15
15
|
|
16
16
|
const query = mutationLogMetaTable.query.where('idGlobal', '>=', since.global).asSql()
|
17
|
-
const pendingMutationEventsRaw =
|
17
|
+
const pendingMutationEventsRaw = dbMutationLog.select(query.query, prepareBindValues(query.bindValues, query.query))
|
18
18
|
const pendingMutationEvents = Schema.decodeUnknownSync(mutationLogMetaTable.schema.pipe(Schema.Array))(
|
19
19
|
pendingMutationEventsRaw,
|
20
20
|
)
|
@@ -29,18 +29,18 @@ export const getMutationEventsSince = (
|
|
29
29
|
.filter((_) => EventId.compare(_.id, since) > 0)
|
30
30
|
})
|
31
31
|
|
32
|
-
export const getLocalHeadFromDb = (
|
33
|
-
const res =
|
32
|
+
export const getLocalHeadFromDb = (dbMutationLog: SqliteDb): EventId.EventId => {
|
33
|
+
const res = dbMutationLog.select<{ idGlobal: EventId.GlobalEventId; idLocal: EventId.LocalEventId }>(
|
34
34
|
sql`select idGlobal, idLocal from ${MUTATION_LOG_META_TABLE} order by idGlobal DESC, idLocal DESC limit 1`,
|
35
35
|
)[0]
|
36
36
|
|
37
37
|
return res ? { global: res.idGlobal, local: res.idLocal } : EventId.ROOT
|
38
38
|
}
|
39
39
|
|
40
|
-
export const getBackendHeadFromDb = (
|
41
|
-
|
40
|
+
export const getBackendHeadFromDb = (dbMutationLog: SqliteDb): EventId.GlobalEventId =>
|
41
|
+
dbMutationLog.select<{ head: EventId.GlobalEventId }>(sql`select head from ${SYNC_STATUS_TABLE}`)[0]?.head ??
|
42
42
|
EventId.ROOT.global
|
43
43
|
|
44
44
|
// TODO use prepared statements
|
45
|
-
export const updateBackendHead = (
|
46
|
-
|
45
|
+
export const updateBackendHead = (dbMutationLog: SqliteDb, head: EventId.EventId) =>
|
46
|
+
dbMutationLog.execute(sql`UPDATE ${SYNC_STATUS_TABLE} SET head = ${head.global}`)
|
@@ -2,87 +2,94 @@ import { casesHandled } from '@livestore/utils'
|
|
2
2
|
import type { HttpClient } from '@livestore/utils/effect'
|
3
3
|
import { Effect, Queue } from '@livestore/utils/effect'
|
4
4
|
|
5
|
-
import type { InvalidPullError, IsOfflineError, MigrationHooks, SqliteError } from '../index.js'
|
5
|
+
import type { InvalidPullError, IsOfflineError, MigrationHooks, MigrationsReport, SqliteError } from '../index.js'
|
6
6
|
import { initializeSingletonTables, migrateDb, rehydrateFromMutationLog, UnexpectedError } from '../index.js'
|
7
7
|
import { configureConnection } from './connection.js'
|
8
8
|
import { LeaderThreadCtx } from './types.js'
|
9
9
|
|
10
10
|
export const recreateDb: Effect.Effect<
|
11
|
-
|
11
|
+
{ migrationsReport: MigrationsReport },
|
12
12
|
UnexpectedError | SqliteError | IsOfflineError | InvalidPullError,
|
13
13
|
LeaderThreadCtx | HttpClient.HttpClient
|
14
14
|
> = Effect.gen(function* () {
|
15
|
-
const {
|
15
|
+
const { dbReadModel, dbMutationLog, schema, bootStatusQueue } = yield* LeaderThreadCtx
|
16
16
|
|
17
17
|
const migrationOptions = schema.migrationOptions
|
18
|
+
let migrationsReport: MigrationsReport
|
18
19
|
|
19
20
|
yield* Effect.addFinalizer(
|
20
21
|
Effect.fn('recreateDb:finalizer')(function* (ex) {
|
21
|
-
if (ex._tag === 'Failure')
|
22
|
+
if (ex._tag === 'Failure') dbReadModel.destroy()
|
22
23
|
}),
|
23
24
|
)
|
24
25
|
|
25
26
|
// NOTE to speed up the operations below, we're creating a temporary in-memory database
|
26
27
|
// and later we'll overwrite the persisted database with the new data
|
27
28
|
// TODO bring back this optimization
|
28
|
-
// const
|
29
|
-
const
|
30
|
-
yield* configureConnection(
|
29
|
+
// const tmpDb = yield* makeSqliteDb({ _tag: 'in-memory' })
|
30
|
+
const tmpDb = dbReadModel
|
31
|
+
yield* configureConnection(tmpDb, { foreignKeys: true })
|
31
32
|
|
32
33
|
const initDb = (hooks: Partial<MigrationHooks> | undefined) =>
|
33
34
|
Effect.gen(function* () {
|
34
|
-
yield* Effect.tryAll(() => hooks?.init?.(
|
35
|
+
yield* Effect.tryAll(() => hooks?.init?.(tmpDb)).pipe(UnexpectedError.mapToUnexpectedError)
|
35
36
|
|
36
|
-
yield* migrateDb({
|
37
|
-
db:
|
37
|
+
const migrationsReport = yield* migrateDb({
|
38
|
+
db: tmpDb,
|
38
39
|
schema,
|
39
40
|
onProgress: ({ done, total }) =>
|
40
41
|
Queue.offer(bootStatusQueue, { stage: 'migrating', progress: { done, total } }),
|
41
42
|
})
|
42
43
|
|
43
|
-
initializeSingletonTables(schema,
|
44
|
+
initializeSingletonTables(schema, tmpDb)
|
44
45
|
|
45
|
-
yield* Effect.tryAll(() => hooks?.pre?.(
|
46
|
+
yield* Effect.tryAll(() => hooks?.pre?.(tmpDb)).pipe(UnexpectedError.mapToUnexpectedError)
|
46
47
|
|
47
|
-
return
|
48
|
+
return { migrationsReport, tmpDb }
|
48
49
|
})
|
49
50
|
|
50
51
|
switch (migrationOptions.strategy) {
|
51
52
|
case 'from-mutation-log': {
|
52
53
|
const hooks = migrationOptions.hooks
|
53
|
-
const
|
54
|
+
const initResult = yield* initDb(hooks)
|
55
|
+
|
56
|
+
migrationsReport = initResult.migrationsReport
|
54
57
|
|
55
58
|
yield* rehydrateFromMutationLog({
|
56
|
-
db:
|
57
|
-
logDb:
|
59
|
+
db: initResult.tmpDb,
|
60
|
+
logDb: dbMutationLog,
|
58
61
|
schema,
|
59
62
|
migrationOptions,
|
60
63
|
onProgress: ({ done, total }) =>
|
61
64
|
Queue.offer(bootStatusQueue, { stage: 'rehydrating', progress: { done, total } }),
|
62
65
|
})
|
63
66
|
|
64
|
-
yield* Effect.tryAll(() => hooks?.post?.(
|
67
|
+
yield* Effect.tryAll(() => hooks?.post?.(initResult.tmpDb)).pipe(UnexpectedError.mapToUnexpectedError)
|
65
68
|
|
66
69
|
break
|
67
70
|
}
|
68
71
|
case 'hard-reset': {
|
69
72
|
const hooks = migrationOptions.hooks
|
70
|
-
const
|
73
|
+
const initResult = yield* initDb(hooks)
|
74
|
+
|
75
|
+
migrationsReport = initResult.migrationsReport
|
71
76
|
|
72
77
|
// The database is migrated but empty now, so nothing else to do
|
73
78
|
|
74
|
-
yield* Effect.tryAll(() => hooks?.post?.(
|
79
|
+
yield* Effect.tryAll(() => hooks?.post?.(initResult.tmpDb)).pipe(UnexpectedError.mapToUnexpectedError)
|
75
80
|
|
76
81
|
break
|
77
82
|
}
|
78
83
|
case 'manual': {
|
79
|
-
const oldDbData =
|
84
|
+
const oldDbData = dbReadModel.export()
|
85
|
+
|
86
|
+
migrationsReport = { migrations: [] }
|
80
87
|
|
81
88
|
const newDbData = yield* Effect.tryAll(() => migrationOptions.migrate(oldDbData)).pipe(
|
82
89
|
UnexpectedError.mapToUnexpectedError,
|
83
90
|
)
|
84
91
|
|
85
|
-
|
92
|
+
tmpDb.import(newDbData)
|
86
93
|
|
87
94
|
// TODO validate schema
|
88
95
|
|
@@ -95,17 +102,19 @@ export const recreateDb: Effect.Effect<
|
|
95
102
|
|
96
103
|
// TODO bring back
|
97
104
|
// Import the temporary in-memory database into the persistent database
|
98
|
-
// yield* Effect.sync(() => db.import(
|
105
|
+
// yield* Effect.sync(() => db.import(tmpDb)).pipe(
|
99
106
|
// Effect.withSpan('@livestore/common:leader-thread:recreateDb:import'),
|
100
107
|
// )
|
101
108
|
|
102
109
|
// TODO maybe bring back re-using this initial snapshot to avoid calling `.export()` again
|
103
110
|
// We've disabled this for now as it made the code too complex, as we often run syncing right after
|
104
111
|
// so the snapshot is no longer up to date
|
105
|
-
// const snapshotFromTmpDb =
|
112
|
+
// const snapshotFromTmpDb = tmpDb.export()
|
106
113
|
|
107
114
|
// TODO bring back
|
108
|
-
//
|
115
|
+
// tmpDb.close()
|
116
|
+
|
117
|
+
return { migrationsReport }
|
109
118
|
}).pipe(
|
110
119
|
Effect.scoped, // NOTE we're closing the scope here so finalizers are called when the effect is done
|
111
120
|
Effect.withSpan('@livestore/common:leader-thread:recreateDb'),
|