@livestore/common 0.3.0-dev.11 → 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 +41 -18
- 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/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 +101 -28
- package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-client-session.js +19 -3
- package/dist/devtools/devtools-messages-client-session.js.map +1 -1
- package/dist/devtools/devtools-messages-common.d.ts +24 -32
- package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-common.js +19 -10
- package/dist/devtools/devtools-messages-common.js.map +1 -1
- package/dist/devtools/devtools-messages-leader.d.ts +210 -34
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-leader.js +53 -6
- package/dist/devtools/devtools-messages-leader.js.map +1 -1
- package/dist/devtools/devtools-messages.d.ts +2 -2
- package/dist/devtools/devtools-messages.d.ts.map +1 -1
- package/dist/devtools/devtools-messages.js +2 -2
- package/dist/devtools/devtools-messages.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +26 -13
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/connection.d.ts +31 -3
- package/dist/leader-thread/connection.d.ts.map +1 -1
- package/dist/leader-thread/connection.js +18 -3
- package/dist/leader-thread/connection.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +51 -20
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +17 -5
- package/dist/leader-thread/make-leader-thread-layer.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 +13 -8
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/leader-thread/types.d.ts +15 -12
- 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/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/migrations.d.ts +2 -2
- 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 +3 -5
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +20 -9
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/src/adapter-types.ts +35 -17
- package/src/devtools/devtools-bridge.ts +10 -7
- package/src/devtools/devtools-messages-client-session.ts +26 -10
- package/src/devtools/devtools-messages-common.ts +37 -8
- package/src/devtools/devtools-messages-leader.ts +73 -12
- package/src/devtools/devtools-messages.ts +2 -2
- package/src/leader-thread/LeaderSyncProcessor.ts +31 -16
- package/src/leader-thread/connection.ts +48 -3
- package/src/leader-thread/leader-worker-devtools.ts +80 -23
- package/src/leader-thread/make-leader-thread-layer.ts +20 -8
- package/src/leader-thread/recreate-db.ts +19 -10
- package/src/leader-thread/types.ts +16 -13
- 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/schema/EventId.ts +4 -0
- package/src/schema-management/migrations.ts +9 -5
- package/src/sync/ClientSessionSyncProcessor.ts +22 -11
- package/src/version.ts +1 -1
@@ -1,15 +1,20 @@
|
|
1
1
|
import { Schema, Transferable } from '@livestore/utils/effect'
|
2
2
|
|
3
|
-
import { NetworkStatus } from '../adapter-types.js'
|
3
|
+
import { NetworkStatus, UnexpectedError } from '../adapter-types.js'
|
4
|
+
import { EventId } from '../schema/mod.js'
|
4
5
|
import * as MutationEvent from '../schema/MutationEvent.js'
|
5
|
-
import {
|
6
|
+
import {
|
7
|
+
LeaderReqResMessage,
|
8
|
+
liveStoreVersion,
|
9
|
+
LSDMessage,
|
10
|
+
LSDReqResMessage,
|
11
|
+
requestId,
|
12
|
+
} from './devtools-messages-common.js'
|
6
13
|
|
7
14
|
export class ResetAllDataReq extends LSDReqResMessage('LSD.Leader.ResetAllDataReq', {
|
8
15
|
mode: Schema.Literal('all-data', 'only-app-db'),
|
9
16
|
}) {}
|
10
17
|
|
11
|
-
export class ResetAllDataRes extends LSDReqResMessage('LSD.Leader.ResetAllDataRes', {}) {}
|
12
|
-
|
13
18
|
export class DatabaseFileInfoReq extends LSDReqResMessage('LSD.Leader.DatabaseFileInfoReq', {}) {}
|
14
19
|
|
15
20
|
export class DatabaseFileInfo extends Schema.Struct({
|
@@ -47,6 +52,13 @@ export class SyncHistoryRes extends LSDReqResMessage('LSD.Leader.SyncHistoryRes'
|
|
47
52
|
metadata: Schema.Option(Schema.JsonValue),
|
48
53
|
}) {}
|
49
54
|
|
55
|
+
export class SyncHeadSubscribe extends LSDReqResMessage('LSD.Leader.SyncHeadSubscribe', {}) {}
|
56
|
+
export class SyncHeadUnsubscribe extends LSDReqResMessage('LSD.Leader.SyncHeadUnsubscribe', {}) {}
|
57
|
+
export class SyncHeadRes extends LSDReqResMessage('LSD.Leader.SyncHeadRes', {
|
58
|
+
local: EventId.EventId,
|
59
|
+
upstream: EventId.EventId,
|
60
|
+
}) {}
|
61
|
+
|
50
62
|
export class SnapshotReq extends LSDReqResMessage('LSD.Leader.SnapshotReq', {}) {}
|
51
63
|
|
52
64
|
export class SnapshotRes extends LSDReqResMessage('LSD.Leader.SnapshotRes', {
|
@@ -79,11 +91,55 @@ export class MutationLogRes extends LSDReqResMessage('LSD.Leader.MutationLogRes'
|
|
79
91
|
mutationLog: Transferable.Uint8Array,
|
80
92
|
}) {}
|
81
93
|
|
82
|
-
export
|
94
|
+
export class Ping extends LSDReqResMessage('LSD.Leader.Ping', {}) {}
|
95
|
+
|
96
|
+
export class Pong extends LSDReqResMessage('LSD.Leader.Pong', {}) {}
|
97
|
+
|
98
|
+
export class Disconnect extends LSDReqResMessage('LSD.Leader.Disconnect', {}) {}
|
99
|
+
|
100
|
+
export const SetSyncLatch = LeaderReqResMessage('LSD.Leader.SetSyncLatch', {
|
101
|
+
payload: {
|
102
|
+
closeLatch: Schema.Boolean,
|
103
|
+
},
|
104
|
+
success: {},
|
105
|
+
})
|
106
|
+
|
107
|
+
export const ResetAllData = LeaderReqResMessage('LSD.Leader.ResetAllData', {
|
108
|
+
payload: {
|
109
|
+
mode: Schema.Literal('all-data', 'only-app-db'),
|
110
|
+
},
|
111
|
+
success: {},
|
112
|
+
})
|
113
|
+
|
114
|
+
// TODO move to `Schema.TaggedRequest` once new RPC is ready https://github.com/Effect-TS/effect/pull/4362
|
115
|
+
export class DatabaseFileInfo_ extends Schema.TaggedRequest<DatabaseFileInfo_>()('LSD.Leader.DatabaseFileInfo', {
|
116
|
+
payload: {
|
117
|
+
requestId,
|
118
|
+
liveStoreVersion,
|
119
|
+
},
|
120
|
+
success: DatabaseFileInfo,
|
121
|
+
failure: UnexpectedError,
|
122
|
+
}) {}
|
123
|
+
|
124
|
+
export class NetworkStatus_ extends Schema.TaggedRequest<NetworkStatus_>()('LSD.Leader.NetworkStatus', {
|
125
|
+
payload: {
|
126
|
+
requestId,
|
127
|
+
liveStoreVersion,
|
128
|
+
},
|
129
|
+
success: NetworkStatus,
|
130
|
+
failure: UnexpectedError,
|
131
|
+
}) {}
|
132
|
+
|
133
|
+
export const MessageToApp_ = Schema.Union(DatabaseFileInfo_, NetworkStatus_)
|
134
|
+
|
135
|
+
export type MessageToApp_ = typeof MessageToApp_.Type
|
136
|
+
//
|
137
|
+
|
138
|
+
export const MessageToApp = Schema.Union(
|
83
139
|
SnapshotReq,
|
84
140
|
LoadDatabaseFileReq,
|
85
141
|
MutationLogReq,
|
86
|
-
|
142
|
+
ResetAllData.Request,
|
87
143
|
NetworkStatusSubscribe,
|
88
144
|
NetworkStatusUnsubscribe,
|
89
145
|
Disconnect,
|
@@ -93,15 +149,17 @@ export const MessageToAppLeader = Schema.Union(
|
|
93
149
|
SyncHistorySubscribe,
|
94
150
|
SyncHistoryUnsubscribe,
|
95
151
|
SyncingInfoReq,
|
96
|
-
|
152
|
+
SyncHeadSubscribe,
|
153
|
+
SyncHeadUnsubscribe,
|
154
|
+
SetSyncLatch.Request,
|
155
|
+
).annotations({ identifier: 'LSD.Leader.MessageToApp' })
|
97
156
|
|
98
|
-
export type
|
157
|
+
export type MessageToApp = typeof MessageToApp.Type
|
99
158
|
|
100
|
-
export const
|
159
|
+
export const MessageFromApp = Schema.Union(
|
101
160
|
SnapshotRes,
|
102
161
|
LoadDatabaseFileRes,
|
103
162
|
MutationLogRes,
|
104
|
-
ResetAllDataRes,
|
105
163
|
Disconnect,
|
106
164
|
MutationBroadcast,
|
107
165
|
NetworkStatusRes,
|
@@ -110,6 +168,9 @@ export const MessageFromAppLeader = Schema.Union(
|
|
110
168
|
DatabaseFileInfoRes,
|
111
169
|
SyncHistoryRes,
|
112
170
|
SyncingInfoRes,
|
113
|
-
|
171
|
+
SyncHeadRes,
|
172
|
+
ResetAllData.Response,
|
173
|
+
SetSyncLatch.Response,
|
174
|
+
).annotations({ identifier: 'LSD.Leader.MessageFromApp' })
|
114
175
|
|
115
|
-
export type
|
176
|
+
export type MessageFromApp = typeof MessageFromApp.Type
|
@@ -1,3 +1,3 @@
|
|
1
|
-
export * from './devtools-messages-client-session.js'
|
2
|
-
export * from './devtools-messages-leader.js'
|
1
|
+
export * as ClientSession from './devtools-messages-client-session.js'
|
2
|
+
export * as Leader from './devtools-messages-leader.js'
|
3
3
|
export * from './devtools-messages-common.js'
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { isNotUndefined, shouldNeverHappen, TRACE_VERBOSE } from '@livestore/utils'
|
1
|
+
import { isNotUndefined, LS_DEV, shouldNeverHappen, TRACE_VERBOSE } from '@livestore/utils'
|
2
2
|
import type { HttpClient, Scope, Tracer } from '@livestore/utils/effect'
|
3
3
|
import {
|
4
4
|
BucketQueue,
|
@@ -93,8 +93,7 @@ export const makeLeaderSyncProcessor = ({
|
|
93
93
|
| {
|
94
94
|
otelSpan: otel.Span | undefined
|
95
95
|
span: Tracer.Span
|
96
|
-
|
97
|
-
devtoolsPushLatch: Effect.Latch | undefined
|
96
|
+
devtoolsLatch: Effect.Latch | undefined
|
98
97
|
},
|
99
98
|
}
|
100
99
|
|
@@ -107,10 +106,6 @@ export const makeLeaderSyncProcessor = ({
|
|
107
106
|
// TODO validate batch
|
108
107
|
if (newEvents.length === 0) return
|
109
108
|
|
110
|
-
if (ctxRef.current?.devtoolsPushLatch !== undefined) {
|
111
|
-
yield* ctxRef.current.devtoolsPushLatch.await
|
112
|
-
}
|
113
|
-
|
114
109
|
const waitForProcessing = options?.waitForProcessing ?? false
|
115
110
|
|
116
111
|
if (waitForProcessing) {
|
@@ -164,8 +159,7 @@ export const makeLeaderSyncProcessor = ({
|
|
164
159
|
ctxRef.current = {
|
165
160
|
otelSpan,
|
166
161
|
span,
|
167
|
-
|
168
|
-
devtoolsPushLatch: devtools.enabled ? devtools.syncBackendPushLatch : undefined,
|
162
|
+
devtoolsLatch: devtools.enabled ? devtools.syncBackendLatch : undefined,
|
169
163
|
}
|
170
164
|
|
171
165
|
const initialBackendHead = dbMissing ? EventId.ROOT.global : getBackendHeadFromDb(dbMutationLog)
|
@@ -220,7 +214,12 @@ export const makeLeaderSyncProcessor = ({
|
|
220
214
|
|
221
215
|
yield* FiberHandle.run(
|
222
216
|
backendPushingFiberHandle,
|
223
|
-
backgroundBackendPushing({
|
217
|
+
backgroundBackendPushing({
|
218
|
+
dbReady,
|
219
|
+
syncBackendQueue,
|
220
|
+
otelSpan,
|
221
|
+
devtoolsLatch: ctxRef.current?.devtoolsLatch,
|
222
|
+
}).pipe(Effect.tapCauseLogPretty),
|
224
223
|
)
|
225
224
|
|
226
225
|
yield* backgroundBackendPulling({
|
@@ -239,7 +238,12 @@ export const makeLeaderSyncProcessor = ({
|
|
239
238
|
// Restart pushing fiber
|
240
239
|
yield* FiberHandle.run(
|
241
240
|
backendPushingFiberHandle,
|
242
|
-
backgroundBackendPushing({
|
241
|
+
backgroundBackendPushing({
|
242
|
+
dbReady,
|
243
|
+
syncBackendQueue,
|
244
|
+
otelSpan,
|
245
|
+
devtoolsLatch: ctxRef.current?.devtoolsLatch,
|
246
|
+
}).pipe(Effect.tapCauseLogPretty),
|
243
247
|
)
|
244
248
|
}),
|
245
249
|
syncStateSref,
|
@@ -247,8 +251,10 @@ export const makeLeaderSyncProcessor = ({
|
|
247
251
|
pullLatch,
|
248
252
|
otelSpan,
|
249
253
|
initialBlockingSyncContext,
|
250
|
-
|
254
|
+
devtoolsLatch: ctxRef.current?.devtoolsLatch,
|
251
255
|
}).pipe(Effect.tapCauseLogPretty, Effect.forkScoped)
|
256
|
+
|
257
|
+
return { initialLeaderHead: initialLocalHead }
|
252
258
|
}).pipe(Effect.withSpanScoped('@livestore/common:leader-thread:syncing'))
|
253
259
|
|
254
260
|
return {
|
@@ -431,7 +437,7 @@ const backgroundBackendPulling = ({
|
|
431
437
|
syncStateSref,
|
432
438
|
localPushesLatch,
|
433
439
|
pullLatch,
|
434
|
-
|
440
|
+
devtoolsLatch,
|
435
441
|
initialBlockingSyncContext,
|
436
442
|
}: {
|
437
443
|
dbReady: Deferred.Deferred<void>
|
@@ -444,7 +450,7 @@ const backgroundBackendPulling = ({
|
|
444
450
|
syncStateSref: SubscriptionRef.SubscriptionRef<SyncState.SyncState | undefined>
|
445
451
|
localPushesLatch: Effect.Latch
|
446
452
|
pullLatch: Effect.Latch
|
447
|
-
|
453
|
+
devtoolsLatch: Effect.Latch | undefined
|
448
454
|
initialBlockingSyncContext: InitialBlockingSyncContext
|
449
455
|
}) =>
|
450
456
|
Effect.gen(function* () {
|
@@ -466,8 +472,8 @@ const backgroundBackendPulling = ({
|
|
466
472
|
Effect.gen(function* () {
|
467
473
|
if (newEvents.length === 0) return
|
468
474
|
|
469
|
-
if (
|
470
|
-
yield*
|
475
|
+
if (devtoolsLatch !== undefined) {
|
476
|
+
yield* devtoolsLatch.await
|
471
477
|
}
|
472
478
|
|
473
479
|
// Prevent more local pushes from being processed until this pull is finished
|
@@ -646,10 +652,12 @@ const backgroundBackendPushing = ({
|
|
646
652
|
dbReady,
|
647
653
|
syncBackendQueue,
|
648
654
|
otelSpan,
|
655
|
+
devtoolsLatch,
|
649
656
|
}: {
|
650
657
|
dbReady: Deferred.Deferred<void>
|
651
658
|
syncBackendQueue: BucketQueue.BucketQueue<MutationEvent.EncodedWithMeta>
|
652
659
|
otelSpan: otel.Span | undefined
|
660
|
+
devtoolsLatch: Effect.Latch | undefined
|
653
661
|
}) =>
|
654
662
|
Effect.gen(function* () {
|
655
663
|
const { syncBackend, dbMutationLog } = yield* LeaderThreadCtx
|
@@ -665,6 +673,10 @@ const backgroundBackendPushing = ({
|
|
665
673
|
|
666
674
|
yield* SubscriptionRef.waitUntil(syncBackend.isConnected, (isConnected) => isConnected === true)
|
667
675
|
|
676
|
+
if (devtoolsLatch !== undefined) {
|
677
|
+
yield* devtoolsLatch.await
|
678
|
+
}
|
679
|
+
|
668
680
|
otelSpan?.addEvent('backend-push', {
|
669
681
|
batchSize: queueItems.length,
|
670
682
|
batch: TRACE_VERBOSE ? JSON.stringify(queueItems) : undefined,
|
@@ -674,6 +686,9 @@ const backgroundBackendPushing = ({
|
|
674
686
|
const pushResult = yield* syncBackend.push(queueItems.map((_) => _.toGlobal())).pipe(Effect.either)
|
675
687
|
|
676
688
|
if (pushResult._tag === 'Left') {
|
689
|
+
if (LS_DEV) {
|
690
|
+
yield* Effect.logDebug('backend-push-error', { error: pushResult.left.toString() })
|
691
|
+
}
|
677
692
|
otelSpan?.addEvent('backend-push-error', { error: pushResult.left.toString() })
|
678
693
|
// wait for interrupt caused by background pulling which will then restart pushing
|
679
694
|
return yield* Effect.never
|
@@ -12,13 +12,58 @@ namespace WaSqlite {
|
|
12
12
|
export type SQLiteError = any
|
13
13
|
}
|
14
14
|
|
15
|
-
|
15
|
+
type ConnectionOptions = {
|
16
|
+
/**
|
17
|
+
* The database connection locking mode.
|
18
|
+
*
|
19
|
+
* @remarks
|
20
|
+
*
|
21
|
+
* This **option is ignored** when used on an **in-memory database** as they can only operate in exclusive locking mode.
|
22
|
+
* In-memory databases can’t share state between connections (unless using a
|
23
|
+
* {@link https://www.sqlite.org/sharedcache.html#shared_cache_and_in_memory_databases|shared cache}),
|
24
|
+
* making concurrent access impossible. This is functionally equivalent to exclusive locking.
|
25
|
+
*
|
26
|
+
* @defaultValue
|
27
|
+
* The default is `"NORMAL"` unless it was unless overridden at compile-time using `SQLITE_DEFAULT_LOCKING_MODE`.
|
28
|
+
*
|
29
|
+
* @see {@link https://www.sqlite.org/pragma.html#pragma_locking_mode|`locking_mode` pragma}
|
30
|
+
*/
|
31
|
+
lockingMode?: 'NORMAL' | 'EXCLUSIVE'
|
32
|
+
|
33
|
+
/**
|
34
|
+
* Whether to enforce foreign key constraints.
|
35
|
+
*
|
36
|
+
* @privateRemarks
|
37
|
+
*
|
38
|
+
* We require a value for this option to minimize future problems, as the default value might change in future
|
39
|
+
* versions of SQLite.
|
40
|
+
*
|
41
|
+
* @see {@link https://www.sqlite.org/pragma.html#pragma_foreign_keys|`foreign_keys` pragma}
|
42
|
+
*/
|
43
|
+
foreignKeys: boolean
|
44
|
+
}
|
45
|
+
|
46
|
+
export const configureConnection = (sqliteDb: SqliteDb, { foreignKeys, lockingMode }: ConnectionOptions) =>
|
16
47
|
execSql(
|
17
48
|
sqliteDb,
|
49
|
+
// We use the WAL journal mode is significantly faster in most scenarios than the traditional rollback journal mode.
|
50
|
+
// It specifically significantly improves write performance. However, when using the WAL journal mode, transactions
|
51
|
+
// that involve changes against multiple ATTACHed databases are atomic for each database but are not atomic
|
52
|
+
// across all databases as a set. Additionally, it is not possible to change the page size after entering WAL mode,
|
53
|
+
// whether on an empty database or by using VACUUM or the backup API. To change the page size, we must switch to the
|
54
|
+
// rollback journal mode.
|
55
|
+
//
|
56
|
+
// When connected to an in-memory database, the WAL journal mode option is ignored because an in-memory database can
|
57
|
+
// only be in either the MEMORY or OFF options. By default, an in-memory database is in the MEMORY option, which
|
58
|
+
// means that it stores the rollback journal in volatile RAM. This saves disk I/O but at the expense of safety and
|
59
|
+
// integrity. If the thread using SQLite crashes in the middle of a transaction, then the database file will very
|
60
|
+
// likely go corrupt.
|
18
61
|
sql`
|
62
|
+
-- disable WAL until we have it working properly
|
63
|
+
-- PRAGMA journal_mode=WAL;
|
19
64
|
PRAGMA page_size=8192;
|
20
|
-
PRAGMA
|
21
|
-
${
|
65
|
+
PRAGMA foreign_keys=${foreignKeys ? 'ON' : 'OFF'};
|
66
|
+
${lockingMode === undefined ? '' : sql`PRAGMA locking_mode=${lockingMode};`}
|
22
67
|
`,
|
23
68
|
{},
|
24
69
|
)
|
@@ -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) =>
|
@@ -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,7 +66,7 @@ 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
|
}) =>
|
@@ -79,6 +79,8 @@ const listenToDevtools = ({
|
|
79
79
|
shutdownStateSubRef,
|
80
80
|
shutdownChannel,
|
81
81
|
syncProcessor,
|
82
|
+
clientId,
|
83
|
+
devtools,
|
82
84
|
} = yield* LeaderThreadCtx
|
83
85
|
|
84
86
|
type RequestId = string
|
@@ -89,22 +91,22 @@ const listenToDevtools = ({
|
|
89
91
|
Effect.gen(function* () {
|
90
92
|
// yield* Effect.logDebug('[@livestore/common:leader-thread:devtools] incomingMessage', decodedEvent)
|
91
93
|
|
92
|
-
if (decodedEvent._tag === 'LSD.Disconnect') {
|
94
|
+
if (decodedEvent._tag === 'LSD.Leader.Disconnect') {
|
93
95
|
return
|
94
96
|
}
|
95
97
|
|
96
98
|
const { requestId } = decodedEvent
|
97
|
-
const reqPayload = { requestId, liveStoreVersion }
|
99
|
+
const reqPayload = { requestId, liveStoreVersion, clientId }
|
98
100
|
|
99
101
|
switch (decodedEvent._tag) {
|
100
|
-
case 'LSD.Ping': {
|
101
|
-
yield* sendMessage(Devtools.Pong.make({ ...reqPayload }))
|
102
|
+
case 'LSD.Leader.Ping': {
|
103
|
+
yield* sendMessage(Devtools.Leader.Pong.make({ ...reqPayload }))
|
102
104
|
return
|
103
105
|
}
|
104
106
|
case 'LSD.Leader.SnapshotReq': {
|
105
107
|
const snapshot = dbReadModel.export()
|
106
108
|
|
107
|
-
yield* sendMessage(Devtools.SnapshotRes.make({ snapshot, ...reqPayload }))
|
109
|
+
yield* sendMessage(Devtools.Leader.SnapshotRes.make({ snapshot, ...reqPayload }))
|
108
110
|
|
109
111
|
return
|
110
112
|
}
|
@@ -125,7 +127,9 @@ const listenToDevtools = ({
|
|
125
127
|
tmpDb.close()
|
126
128
|
} catch (e) {
|
127
129
|
yield* Effect.logError(`Error importing database file`, e)
|
128
|
-
yield* sendMessage(
|
130
|
+
yield* sendMessage(
|
131
|
+
Devtools.Leader.LoadDatabaseFileRes.make({ ...reqPayload, status: 'unsupported-file' }),
|
132
|
+
)
|
129
133
|
|
130
134
|
return
|
131
135
|
}
|
@@ -143,17 +147,19 @@ const listenToDevtools = ({
|
|
143
147
|
|
144
148
|
dbMutationLog.destroy()
|
145
149
|
} else {
|
146
|
-
yield* sendMessage(
|
150
|
+
yield* sendMessage(
|
151
|
+
Devtools.Leader.LoadDatabaseFileRes.make({ ...reqPayload, status: 'unsupported-database' }),
|
152
|
+
)
|
147
153
|
return
|
148
154
|
}
|
149
155
|
|
150
|
-
yield* sendMessage(Devtools.LoadDatabaseFileRes.make({ ...reqPayload, status: 'ok' }))
|
156
|
+
yield* sendMessage(Devtools.Leader.LoadDatabaseFileRes.make({ ...reqPayload, status: 'ok' }))
|
151
157
|
|
152
158
|
yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-import' })) ?? Effect.void
|
153
159
|
|
154
160
|
return
|
155
161
|
}
|
156
|
-
case 'LSD.Leader.
|
162
|
+
case 'LSD.Leader.ResetAllData.Request': {
|
157
163
|
const { mode } = decodedEvent
|
158
164
|
|
159
165
|
yield* SubscriptionRef.set(shutdownStateSubRef, 'shutting-down')
|
@@ -164,7 +170,7 @@ const listenToDevtools = ({
|
|
164
170
|
dbMutationLog.destroy()
|
165
171
|
}
|
166
172
|
|
167
|
-
yield* sendMessage(Devtools.
|
173
|
+
yield* sendMessage(Devtools.Leader.ResetAllData.Response.make({ ...reqPayload }))
|
168
174
|
|
169
175
|
yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-reset' })) ?? Effect.void
|
170
176
|
|
@@ -181,7 +187,7 @@ const listenToDevtools = ({
|
|
181
187
|
const mutationLogFileSize = dbMutationLog.select<{ size: number }>(dbSizeQuery, undefined)[0]!.size
|
182
188
|
|
183
189
|
yield* sendMessage(
|
184
|
-
Devtools.DatabaseFileInfoRes.make({
|
190
|
+
Devtools.Leader.DatabaseFileInfoRes.make({
|
185
191
|
readModel: { fileSize: dbFileSize, persistenceInfo: persistenceInfo.readModel },
|
186
192
|
mutationLog: { fileSize: mutationLogFileSize, persistenceInfo: persistenceInfo.mutationLog },
|
187
193
|
...reqPayload,
|
@@ -193,14 +199,14 @@ const listenToDevtools = ({
|
|
193
199
|
case 'LSD.Leader.MutationLogReq': {
|
194
200
|
const mutationLog = dbMutationLog.export()
|
195
201
|
|
196
|
-
yield* sendMessage(Devtools.MutationLogRes.make({ mutationLog, ...reqPayload }))
|
202
|
+
yield* sendMessage(Devtools.Leader.MutationLogRes.make({ mutationLog, ...reqPayload }))
|
197
203
|
|
198
204
|
return
|
199
205
|
}
|
200
206
|
case 'LSD.Leader.RunMutationReq': {
|
201
207
|
yield* syncProcessor.pushPartial(decodedEvent.mutationEventEncoded)
|
202
208
|
|
203
|
-
yield* sendMessage(Devtools.RunMutationRes.make({ ...reqPayload }))
|
209
|
+
yield* sendMessage(Devtools.Leader.RunMutationRes.make({ ...reqPayload }))
|
204
210
|
|
205
211
|
return
|
206
212
|
}
|
@@ -213,7 +219,7 @@ const listenToDevtools = ({
|
|
213
219
|
Stream.map((_) => _.batch),
|
214
220
|
Stream.flattenIterables,
|
215
221
|
Stream.tap(({ mutationEventEncoded, metadata }) =>
|
216
|
-
sendMessage(Devtools.SyncHistoryRes.make({ mutationEventEncoded, metadata, ...reqPayload })),
|
222
|
+
sendMessage(Devtools.Leader.SyncHistoryRes.make({ mutationEventEncoded, metadata, ...reqPayload })),
|
217
223
|
),
|
218
224
|
Stream.runDrain,
|
219
225
|
Effect.acquireRelease(() => Effect.log('syncHistorySubscribe done')),
|
@@ -234,12 +240,12 @@ const listenToDevtools = ({
|
|
234
240
|
return
|
235
241
|
}
|
236
242
|
case 'LSD.Leader.SyncingInfoReq': {
|
237
|
-
const syncingInfo = Devtools.SyncingInfo.make({
|
243
|
+
const syncingInfo = Devtools.Leader.SyncingInfo.make({
|
238
244
|
enabled: syncBackend !== undefined,
|
239
245
|
metadata: {},
|
240
246
|
})
|
241
247
|
|
242
|
-
yield* sendMessage(Devtools.SyncingInfoRes.make({ syncingInfo, ...reqPayload }))
|
248
|
+
yield* sendMessage(Devtools.Leader.SyncingInfoRes.make({ syncingInfo, ...reqPayload }))
|
243
249
|
|
244
250
|
return
|
245
251
|
}
|
@@ -252,11 +258,14 @@ const listenToDevtools = ({
|
|
252
258
|
// This is probably the same "flaky databrowser loading" bug as we're seeing in the playwright tests
|
253
259
|
yield* Effect.sleep(1000)
|
254
260
|
|
255
|
-
yield*
|
256
|
-
|
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 }]) =>
|
257
266
|
sendMessage(
|
258
|
-
Devtools.NetworkStatusRes.make({
|
259
|
-
networkStatus: { isConnected, timestampMs: Date.now() },
|
267
|
+
Devtools.Leader.NetworkStatusRes.make({
|
268
|
+
networkStatus: { isConnected, timestampMs: Date.now(), latchClosed },
|
260
269
|
...reqPayload,
|
261
270
|
}),
|
262
271
|
),
|
@@ -277,6 +286,54 @@ const listenToDevtools = ({
|
|
277
286
|
|
278
287
|
return
|
279
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
|
+
}
|
280
337
|
}
|
281
338
|
}).pipe(Effect.withSpan(`@livestore/common:leader-thread:onDevtoolsMessage:${decodedEvent._tag}`)),
|
282
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, MakeSqliteDb, SqliteError } from '../adapter-types.js'
|
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'
|
@@ -67,15 +67,15 @@ export const makeLeaderThreadLayer = ({
|
|
67
67
|
initialBlockingSyncContext,
|
68
68
|
})
|
69
69
|
|
70
|
-
const extraIncomingMessagesQueue = yield* Queue.unbounded<Devtools.
|
70
|
+
const extraIncomingMessagesQueue = yield* Queue.unbounded<Devtools.Leader.MessageToApp>().pipe(
|
71
71
|
Effect.acquireRelease(Queue.shutdown),
|
72
72
|
)
|
73
73
|
|
74
74
|
const devtoolsContext = devtoolsOptions.enabled
|
75
75
|
? {
|
76
76
|
enabled: true as const,
|
77
|
-
|
78
|
-
|
77
|
+
syncBackendLatch: yield* Effect.makeLatch(true),
|
78
|
+
syncBackendLatchState: yield* SubscriptionRef.make<{ latchClosed: boolean }>({ latchClosed: false }),
|
79
79
|
}
|
80
80
|
: { enabled: false as const }
|
81
81
|
|
@@ -95,6 +95,8 @@ export const makeLeaderThreadLayer = ({
|
|
95
95
|
connectedClientSessionPullQueues: yield* makePullQueueSet,
|
96
96
|
extraIncomingMessagesQueue,
|
97
97
|
devtools: devtoolsContext,
|
98
|
+
// State will be set during `bootLeaderThread`
|
99
|
+
initialState: {} as any as LeaderThreadCtx['Type']['initialState'],
|
98
100
|
} satisfies typeof LeaderThreadCtx.Service
|
99
101
|
|
100
102
|
// @ts-expect-error For debugging purposes
|
@@ -102,7 +104,11 @@ export const makeLeaderThreadLayer = ({
|
|
102
104
|
|
103
105
|
const layer = Layer.succeed(LeaderThreadCtx, ctx)
|
104
106
|
|
105
|
-
yield* bootLeaderThread({
|
107
|
+
ctx.initialState = yield* bootLeaderThread({
|
108
|
+
dbMissing,
|
109
|
+
initialBlockingSyncContext,
|
110
|
+
devtoolsOptions,
|
111
|
+
}).pipe(Effect.provide(layer))
|
106
112
|
|
107
113
|
return layer
|
108
114
|
}).pipe(
|
@@ -172,7 +178,7 @@ const bootLeaderThread = ({
|
|
172
178
|
initialBlockingSyncContext: InitialBlockingSyncContext
|
173
179
|
devtoolsOptions: DevtoolsOptions
|
174
180
|
}): Effect.Effect<
|
175
|
-
|
181
|
+
LeaderThreadCtx['Type']['initialState'],
|
176
182
|
UnexpectedError | SqliteError | IsOfflineError | InvalidPullError,
|
177
183
|
LeaderThreadCtx | Scope.Scope | HttpClient.HttpClient
|
178
184
|
> =>
|
@@ -206,10 +212,14 @@ const bootLeaderThread = ({
|
|
206
212
|
|
207
213
|
// We're already starting pulling from the sync backend concurrently but wait until the db is ready before
|
208
214
|
// processing any incoming mutations
|
209
|
-
yield* syncProcessor.boot({ dbReady })
|
215
|
+
const { initialLeaderHead } = yield* syncProcessor.boot({ dbReady })
|
210
216
|
|
217
|
+
let migrationsReport: MigrationsReport
|
211
218
|
if (dbMissing) {
|
212
|
-
yield* recreateDb
|
219
|
+
const recreateResult = yield* recreateDb
|
220
|
+
migrationsReport = recreateResult.migrationsReport
|
221
|
+
} else {
|
222
|
+
migrationsReport = { migrations: [] }
|
213
223
|
}
|
214
224
|
|
215
225
|
yield* Deferred.succeed(dbReady, void 0)
|
@@ -221,4 +231,6 @@ const bootLeaderThread = ({
|
|
221
231
|
yield* Queue.offer(bootStatusQueue, { stage: 'done' })
|
222
232
|
|
223
233
|
yield* bootDevtools(devtoolsOptions).pipe(Effect.tapCauseLogPretty, Effect.forkScoped)
|
234
|
+
|
235
|
+
return { migrationsReport, leaderHead: initialLeaderHead }
|
224
236
|
})
|