@livestore/common 0.3.0-dev.10 → 0.3.0-dev.2
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 +23 -26
- 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 +1 -2
- package/dist/devtools/devtools-bridge.d.ts.map +1 -1
- package/dist/devtools/devtools-messages.d.ts +110 -98
- package/dist/devtools/devtools-messages.d.ts.map +1 -1
- package/dist/devtools/devtools-messages.js +6 -9
- package/dist/devtools/devtools-messages.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/leader-thread/apply-mutation.d.ts +2 -5
- package/dist/leader-thread/apply-mutation.d.ts.map +1 -1
- package/dist/leader-thread/apply-mutation.js +26 -38
- 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 +12 -20
- 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 +66 -22
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +7 -8
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +5 -11
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/mutationlog.d.ts +17 -4
- package/dist/leader-thread/mutationlog.d.ts.map +1 -1
- package/dist/leader-thread/mutationlog.js +1 -2
- 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 +3 -9
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/leader-thread/types.d.ts +9 -17
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/leader-thread/types.js.map +1 -1
- package/dist/mutation.d.ts +2 -9
- 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 +19 -13
- package/dist/rehydrate-from-mutationlog.js.map +1 -1
- package/dist/schema/EventId.d.ts +14 -16
- package/dist/schema/EventId.d.ts.map +1 -1
- package/dist/schema/EventId.js +7 -15
- package/dist/schema/EventId.js.map +1 -1
- package/dist/schema/MutationEvent.d.ts +80 -49
- package/dist/schema/MutationEvent.d.ts.map +1 -1
- package/dist/schema/MutationEvent.js +15 -32
- package/dist/schema/MutationEvent.js.map +1 -1
- package/dist/schema/system-tables.d.ts +26 -26
- package/dist/schema/system-tables.d.ts.map +1 -1
- package/dist/schema/system-tables.js +11 -19
- package/dist/schema/system-tables.js.map +1 -1
- package/dist/schema-management/migrations.js +6 -6
- package/dist/schema-management/migrations.js.map +1 -1
- package/dist/sync/client-session-sync-processor.d.ts +4 -4
- package/dist/sync/client-session-sync-processor.d.ts.map +1 -1
- package/dist/sync/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 +4 -1
- 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 +2 -3
- 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 +9 -3
- package/dist/sync/next/test/mutation-fixtures.js.map +1 -1
- package/dist/sync/sync.d.ts +11 -21
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/syncstate.d.ts +23 -45
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +12 -56
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +69 -125
- 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 +5 -6
- package/src/adapter-types.ts +24 -22
- package/src/derived-mutations.test.ts +1 -1
- package/src/derived-mutations.ts +5 -9
- package/src/devtools/devtools-bridge.ts +1 -2
- package/src/devtools/devtools-messages.ts +6 -9
- package/src/index.ts +6 -0
- package/src/leader-thread/apply-mutation.ts +31 -49
- package/src/leader-thread/{LeaderSyncProcessor.ts → leader-sync-processor.ts} +230 -235
- package/src/leader-thread/leader-worker-devtools.ts +109 -30
- package/src/leader-thread/make-leader-thread-layer.ts +13 -24
- package/src/leader-thread/mutationlog.ts +5 -9
- package/src/leader-thread/recreate-db.ts +5 -9
- package/src/leader-thread/types.ts +11 -18
- package/src/mutation.ts +7 -17
- package/src/rehydrate-from-mutationlog.ts +23 -15
- package/src/schema/EventId.ts +9 -23
- package/src/schema/MutationEvent.ts +24 -46
- package/src/schema/system-tables.ts +11 -19
- package/src/schema-management/migrations.ts +6 -6
- package/src/sync/{ClientSessionSyncProcessor.ts → client-session-sync-processor.ts} +9 -11
- 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 +10 -3
- package/src/sync/sync.ts +6 -19
- package/src/sync/syncstate.test.ts +67 -127
- package/src/sync/syncstate.ts +19 -21
- package/src/sync/validate-push-payload.ts +4 -7
- package/src/version.ts +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +0 -37
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +0 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +0 -417
- package/dist/leader-thread/LeaderSyncProcessor.js.map +0 -1
- package/dist/schema/EventId.test.d.ts +0 -2
- package/dist/schema/EventId.test.d.ts.map +0 -1
- package/dist/schema/EventId.test.js +0 -11
- package/dist/schema/EventId.test.js.map +0 -1
- package/dist/schema/MutationEvent.test.d.ts +0 -2
- package/dist/schema/MutationEvent.test.d.ts.map +0 -1
- package/dist/schema/MutationEvent.test.js +0 -2
- package/dist/schema/MutationEvent.test.js.map +0 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +0 -45
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +0 -1
- package/dist/sync/ClientSessionSyncProcessor.js +0 -134
- package/dist/sync/ClientSessionSyncProcessor.js.map +0 -1
- package/src/schema/EventId.test.ts +0 -12
@@ -1,11 +1,18 @@
|
|
1
|
-
import { Effect, FiberMap, Option, Stream, SubscriptionRef } from '@livestore/utils/effect'
|
1
|
+
import { Effect, FiberMap, Option, PubSub, Queue, 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'
|
5
6
|
import type { DevtoolsOptions, PersistenceInfoPair } from './types.js'
|
6
7
|
import { LeaderThreadCtx } from './types.js'
|
7
8
|
|
8
|
-
type SendMessageToDevtools = (
|
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>
|
9
16
|
|
10
17
|
// TODO bind scope to the webchannel lifetime
|
11
18
|
export const bootDevtools = (options: DevtoolsOptions) =>
|
@@ -14,24 +21,44 @@ export const bootDevtools = (options: DevtoolsOptions) =>
|
|
14
21
|
return
|
15
22
|
}
|
16
23
|
|
17
|
-
const {
|
24
|
+
const { persistenceInfo, shutdownChannel, devtoolsWebChannel } = yield* options.makeContext
|
18
25
|
|
19
|
-
yield*
|
20
|
-
|
21
|
-
|
22
|
-
|
26
|
+
const isConnected = yield* SubscriptionRef.make(true)
|
27
|
+
|
28
|
+
const incomingMessagesPubSub = yield* PubSub.unbounded<Devtools.MessageToAppLeader>().pipe(
|
29
|
+
Effect.acquireRelease(PubSub.shutdown),
|
30
|
+
)
|
23
31
|
|
24
|
-
const
|
32
|
+
const incomingMessages = Stream.fromPubSub(incomingMessagesPubSub)
|
25
33
|
|
26
|
-
const
|
27
|
-
|
28
|
-
|
29
|
-
.pipe(
|
30
|
-
Effect.withSpan('@livestore/common:leader-thread:devtools:sendToDevtools'),
|
31
|
-
Effect.interruptible,
|
32
|
-
Effect.ignoreLogged,
|
33
|
-
)
|
34
|
+
const outgoingMessagesQueue = yield* Queue.unbounded<Devtools.MessageFromAppLeader>().pipe(
|
35
|
+
Effect.acquireRelease(Queue.shutdown),
|
36
|
+
)
|
34
37
|
|
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
|
35
62
|
const { localHead } = yield* syncProcessor.syncState
|
36
63
|
|
37
64
|
// TODO close queue when devtools disconnects
|
@@ -42,8 +69,13 @@ export const bootDevtools = (options: DevtoolsOptions) =>
|
|
42
69
|
Effect.gen(function* () {
|
43
70
|
if (msg.payload._tag === 'upstream-advance') {
|
44
71
|
for (const mutationEventEncoded of msg.payload.newEvents) {
|
45
|
-
|
46
|
-
|
72
|
+
yield* sendMessage(
|
73
|
+
Devtools.MutationBroadcast.make({
|
74
|
+
mutationEventEncoded,
|
75
|
+
|
76
|
+
liveStoreVersion,
|
77
|
+
}),
|
78
|
+
)
|
47
79
|
}
|
48
80
|
} else {
|
49
81
|
yield* Effect.logWarning('TODO implement rebases in devtools')
|
@@ -54,25 +86,68 @@ export const bootDevtools = (options: DevtoolsOptions) =>
|
|
54
86
|
Effect.forkScoped,
|
55
87
|
)
|
56
88
|
|
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
|
+
|
57
114
|
yield* listenToDevtools({
|
58
|
-
incomingMessages
|
115
|
+
incomingMessages,
|
59
116
|
sendMessage,
|
117
|
+
// isConnected,
|
118
|
+
// disconnect,
|
119
|
+
// storeId,
|
120
|
+
// appHostId,
|
121
|
+
// isLeader,
|
60
122
|
persistenceInfo,
|
61
|
-
|
123
|
+
shutdownChannel,
|
124
|
+
})
|
62
125
|
}).pipe(Effect.withSpan('@livestore/common:leader-thread:devtools:boot'))
|
63
126
|
|
64
127
|
const listenToDevtools = ({
|
65
128
|
incomingMessages,
|
66
129
|
sendMessage,
|
130
|
+
// isConnected,
|
131
|
+
// disconnect,
|
132
|
+
// appHostId,
|
133
|
+
// storeId,
|
134
|
+
// isLeader,
|
67
135
|
persistenceInfo,
|
136
|
+
shutdownChannel,
|
68
137
|
}: {
|
69
138
|
incomingMessages: Stream.Stream<Devtools.MessageToAppLeader>
|
70
139
|
sendMessage: SendMessageToDevtools
|
71
|
-
|
140
|
+
// isConnected: SubscriptionRef.SubscriptionRef<boolean>
|
141
|
+
// disconnect: Effect.Effect<void>
|
142
|
+
// appHostId: string
|
143
|
+
// storeId: string
|
144
|
+
// isLeader: boolean
|
145
|
+
persistenceInfo: PersistenceInfoPair
|
146
|
+
shutdownChannel: ShutdownChannel
|
72
147
|
}) =>
|
73
148
|
Effect.gen(function* () {
|
74
|
-
const
|
75
|
-
|
149
|
+
const innerWorkerCtx = yield* LeaderThreadCtx
|
150
|
+
const { syncBackend, makeSyncDb, db, dbLog, shutdownStateSubRef, syncProcessor } = innerWorkerCtx
|
76
151
|
|
77
152
|
type RequestId = string
|
78
153
|
const subscriptionFiberMap = yield* FiberMap.make<RequestId>()
|
@@ -83,6 +158,15 @@ const listenToDevtools = ({
|
|
83
158
|
// yield* Effect.logDebug('[@livestore/common:leader-thread:devtools] incomingMessage', decodedEvent)
|
84
159
|
|
85
160
|
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
|
+
|
86
170
|
return
|
87
171
|
}
|
88
172
|
|
@@ -142,7 +226,7 @@ const listenToDevtools = ({
|
|
142
226
|
|
143
227
|
yield* sendMessage(Devtools.LoadDatabaseFileRes.make({ ...reqPayload, status: 'ok' }))
|
144
228
|
|
145
|
-
yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-import' }))
|
229
|
+
yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-import' }))
|
146
230
|
|
147
231
|
return
|
148
232
|
}
|
@@ -159,16 +243,11 @@ const listenToDevtools = ({
|
|
159
243
|
|
160
244
|
yield* sendMessage(Devtools.ResetAllDataRes.make({ ...reqPayload }))
|
161
245
|
|
162
|
-
yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-reset' }))
|
246
|
+
yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-reset' }))
|
163
247
|
|
164
248
|
return
|
165
249
|
}
|
166
250
|
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
|
-
|
172
251
|
const dbSizeQuery = `SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size();`
|
173
252
|
const dbFileSize = db.select<{ size: number }>(dbSizeQuery, undefined)[0]!.size
|
174
253
|
const mutationLogFileSize = dbLog.select<{ size: number }>(dbSizeQuery, undefined)[0]!.size
|
@@ -1,43 +1,41 @@
|
|
1
|
-
import type { HttpClient, Scope } from '@livestore/utils/effect'
|
2
|
-
import { Deferred, Effect, Layer, Queue, SubscriptionRef } from '@livestore/utils/effect'
|
1
|
+
import type { HttpClient, Scope, WebChannel } from '@livestore/utils/effect'
|
2
|
+
import { Deferred, Effect, FiberSet, 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'
|
7
6
|
import type { LiveStoreSchema } from '../schema/mod.js'
|
8
7
|
import { EventId, MutationEvent, mutationLogMetaTable, SYNC_STATUS_TABLE, syncStatusTable } from '../schema/mod.js'
|
9
8
|
import { migrateTable } from '../schema-management/migrations.js'
|
10
|
-
import type { InvalidPullError, IsOfflineError,
|
9
|
+
import type { InvalidPullError, IsOfflineError, SyncBackend } from '../sync/sync.js'
|
11
10
|
import { sql } from '../util.js'
|
12
11
|
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'
|
15
14
|
import { makePullQueueSet } from './pull-queue-set.js'
|
16
15
|
import { recreateDb } from './recreate-db.js'
|
17
|
-
import type { ShutdownChannel } from './shutdown-channel.js'
|
18
16
|
import type { DevtoolsOptions, InitialBlockingSyncContext, InitialSyncOptions, ShutdownState } from './types.js'
|
19
17
|
import { LeaderThreadCtx } from './types.js'
|
20
18
|
|
21
19
|
export const makeLeaderThreadLayer = ({
|
22
20
|
schema,
|
23
21
|
storeId,
|
24
|
-
|
22
|
+
originId,
|
25
23
|
makeSyncDb,
|
26
|
-
|
24
|
+
makeSyncBackend,
|
27
25
|
db,
|
28
26
|
dbLog,
|
29
27
|
devtoolsOptions,
|
30
|
-
|
28
|
+
initialSyncOptions = { _tag: 'Skip' },
|
31
29
|
}: {
|
32
30
|
storeId: string
|
33
|
-
|
31
|
+
originId: string
|
34
32
|
schema: LiveStoreSchema
|
35
33
|
makeSyncDb: MakeSynchronousDatabase
|
36
|
-
|
34
|
+
makeSyncBackend: Effect.Effect<SyncBackend, UnexpectedError, Scope.Scope> | undefined
|
37
35
|
db: SynchronousDatabase
|
38
36
|
dbLog: SynchronousDatabase
|
39
37
|
devtoolsOptions: DevtoolsOptions
|
40
|
-
|
38
|
+
initialSyncOptions: InitialSyncOptions | undefined
|
41
39
|
}): Layer.Layer<LeaderThreadCtx, UnexpectedError, Scope.Scope | HttpClient.HttpClient> =>
|
42
40
|
Effect.gen(function* () {
|
43
41
|
const bootStatusQueue = yield* Queue.unbounded<BootStatus>().pipe(Effect.acquireRelease(Queue.shutdown))
|
@@ -46,34 +44,25 @@ export const makeLeaderThreadLayer = ({
|
|
46
44
|
// Either happens on initial boot or if schema changes
|
47
45
|
const dbMissing = db.select<{ count: number }>(sql`select count(*) as count from sqlite_master`)[0]!.count === 0
|
48
46
|
|
49
|
-
const syncBackend =
|
47
|
+
const syncBackend = makeSyncBackend === undefined ? undefined : yield* makeSyncBackend
|
50
48
|
|
51
|
-
const initialBlockingSyncContext = yield* makeInitialBlockingSyncContext({
|
52
|
-
initialSyncOptions: syncOptions?.initialSyncOptions ?? { _tag: 'Skip' },
|
53
|
-
bootStatusQueue,
|
54
|
-
})
|
49
|
+
const initialBlockingSyncContext = yield* makeInitialBlockingSyncContext({ initialSyncOptions, bootStatusQueue })
|
55
50
|
|
56
51
|
const syncProcessor = yield* makeLeaderSyncProcessor({ schema, dbMissing, dbLog, initialBlockingSyncContext })
|
57
52
|
|
58
|
-
const extraIncomingMessagesQueue = yield* Queue.unbounded<Devtools.MessageToAppLeader>().pipe(
|
59
|
-
Effect.acquireRelease(Queue.shutdown),
|
60
|
-
)
|
61
|
-
|
62
53
|
const ctx = {
|
63
54
|
schema,
|
64
55
|
bootStatusQueue,
|
65
56
|
storeId,
|
66
|
-
|
57
|
+
originId,
|
67
58
|
db,
|
68
59
|
dbLog,
|
69
60
|
makeSyncDb,
|
70
61
|
mutationEventSchema: MutationEvent.makeMutationEventSchema(schema),
|
71
62
|
shutdownStateSubRef: yield* SubscriptionRef.make<ShutdownState>('running'),
|
72
|
-
shutdownChannel,
|
73
63
|
syncBackend,
|
74
64
|
syncProcessor,
|
75
65
|
connectedClientSessionPullQueues: yield* makePullQueueSet,
|
76
|
-
extraIncomingMessagesQueue,
|
77
66
|
} satisfies typeof LeaderThreadCtx.Service
|
78
67
|
|
79
68
|
// @ts-expect-error For debugging purposes
|
@@ -2,14 +2,11 @@ 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'
|
6
5
|
import { MUTATION_LOG_META_TABLE, mutationLogMetaTable, SYNC_STATUS_TABLE } from '../schema/system-tables.js'
|
7
6
|
import { prepareBindValues, sql } from '../util.js'
|
8
7
|
import { LeaderThreadCtx } from './types.js'
|
9
8
|
|
10
|
-
export const getMutationEventsSince = (
|
11
|
-
since: EventId.EventId,
|
12
|
-
): Effect.Effect<ReadonlyArray<MutationEvent.AnyEncoded>, never, LeaderThreadCtx> =>
|
9
|
+
export const getMutationEventsSince = (since: EventId.EventId) =>
|
13
10
|
Effect.gen(function* () {
|
14
11
|
const { dbLog } = yield* LeaderThreadCtx
|
15
12
|
|
@@ -29,17 +26,16 @@ export const getMutationEventsSince = (
|
|
29
26
|
.filter((_) => EventId.compare(_.id, since) > 0)
|
30
27
|
})
|
31
28
|
|
32
|
-
export const getLocalHeadFromDb = (dbLog: SynchronousDatabase)
|
33
|
-
const res = dbLog.select<{ idGlobal:
|
29
|
+
export const getLocalHeadFromDb = (dbLog: SynchronousDatabase) => {
|
30
|
+
const res = dbLog.select<{ idGlobal: number; idLocal: number }>(
|
34
31
|
sql`select idGlobal, idLocal from ${MUTATION_LOG_META_TABLE} order by idGlobal DESC, idLocal DESC limit 1`,
|
35
32
|
)[0]
|
36
33
|
|
37
34
|
return res ? { global: res.idGlobal, local: res.idLocal } : EventId.ROOT
|
38
35
|
}
|
39
36
|
|
40
|
-
export const getBackendHeadFromDb = (dbLog: SynchronousDatabase)
|
41
|
-
dbLog.select<{ head:
|
42
|
-
EventId.ROOT.global
|
37
|
+
export const getBackendHeadFromDb = (dbLog: SynchronousDatabase) =>
|
38
|
+
dbLog.select<{ head: number }>(sql`select head from ${SYNC_STATUS_TABLE}`)[0]?.head ?? EventId.ROOT.global
|
43
39
|
|
44
40
|
// TODO use prepared statements
|
45
41
|
export const updateBackendHead = (dbLog: SynchronousDatabase, head: EventId.EventId) =>
|
@@ -24,9 +24,7 @@ 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
|
-
|
28
|
-
// const tmpSyncDb = yield* makeSyncDb({ _tag: 'in-memory' })
|
29
|
-
const tmpSyncDb = db
|
27
|
+
const tmpSyncDb = yield* makeSyncDb({ _tag: 'in-memory' })
|
30
28
|
yield* configureConnection(tmpSyncDb, { fkEnabled: true })
|
31
29
|
|
32
30
|
const initDb = (hooks: Partial<MigrationHooks> | undefined) =>
|
@@ -93,19 +91,17 @@ export const recreateDb: Effect.Effect<
|
|
93
91
|
}
|
94
92
|
}
|
95
93
|
|
96
|
-
// TODO bring back
|
97
94
|
// Import the temporary in-memory database into the persistent database
|
98
|
-
|
99
|
-
|
100
|
-
|
95
|
+
yield* Effect.sync(() => db.import(tmpSyncDb)).pipe(
|
96
|
+
Effect.withSpan('@livestore/common:leader-thread:recreateDb:import'),
|
97
|
+
)
|
101
98
|
|
102
99
|
// TODO maybe bring back re-using this initial snapshot to avoid calling `.export()` again
|
103
100
|
// We've disabled this for now as it made the code too complex, as we often run syncing right after
|
104
101
|
// so the snapshot is no longer up to date
|
105
102
|
// const snapshotFromTmpDb = tmpSyncDb.export()
|
106
103
|
|
107
|
-
|
108
|
-
// tmpSyncDb.close()
|
104
|
+
tmpSyncDb.close()
|
109
105
|
}).pipe(
|
110
106
|
Effect.scoped, // NOTE we're closing the scope here so finalizers are called when the effect is done
|
111
107
|
Effect.withSpan('@livestore/common:leader-thread:recreateDb'),
|
@@ -6,7 +6,6 @@ import type {
|
|
6
6
|
Option,
|
7
7
|
Queue,
|
8
8
|
Scope,
|
9
|
-
Subscribable,
|
10
9
|
SubscriptionRef,
|
11
10
|
WebChannel,
|
12
11
|
} from '@livestore/utils/effect'
|
@@ -23,7 +22,7 @@ import type {
|
|
23
22
|
UnexpectedError,
|
24
23
|
} from '../index.js'
|
25
24
|
import type { EventId, LiveStoreSchema, MutationEvent } from '../schema/mod.js'
|
26
|
-
import type
|
25
|
+
import type { PayloadUpstream, SyncState } from '../sync/syncstate.js'
|
27
26
|
import type { ShutdownChannel } from './shutdown-channel.js'
|
28
27
|
|
29
28
|
export type ShutdownState = 'running' | 'shutting-down'
|
@@ -68,6 +67,7 @@ export type DevtoolsOptions =
|
|
68
67
|
makeContext: Effect.Effect<
|
69
68
|
{
|
70
69
|
devtoolsWebChannel: WebChannel.WebChannel<Devtools.MessageToAppLeader, Devtools.MessageFromAppLeader>
|
70
|
+
shutdownChannel: ShutdownChannel
|
71
71
|
persistenceInfo: PersistenceInfoPair
|
72
72
|
},
|
73
73
|
UnexpectedError,
|
@@ -80,21 +80,18 @@ export class LeaderThreadCtx extends Context.Tag('LeaderThreadCtx')<
|
|
80
80
|
{
|
81
81
|
schema: LiveStoreSchema
|
82
82
|
storeId: string
|
83
|
-
|
83
|
+
originId: 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
|
91
90
|
mutationEventSchema: MutationEvent.ForMutationDefRecord<any>
|
92
91
|
// devtools: DevtoolsContext
|
93
92
|
syncBackend: SyncBackend | undefined
|
94
|
-
syncProcessor:
|
93
|
+
syncProcessor: SyncProcessor
|
95
94
|
connectedClientSessionPullQueues: PullQueueSet
|
96
|
-
/** e.g. used for `store.__dev` APIs */
|
97
|
-
extraIncomingMessagesQueue: Queue.Queue<Devtools.MessageToAppLeader>
|
98
95
|
}
|
99
96
|
>() {}
|
100
97
|
|
@@ -104,28 +101,24 @@ export type InitialBlockingSyncContext = {
|
|
104
101
|
}
|
105
102
|
|
106
103
|
export type PullQueueItem = {
|
107
|
-
|
104
|
+
// mutationEvents: ReadonlyArray<MutationEvent.AnyEncoded>
|
105
|
+
// backendHead: number
|
106
|
+
payload: PayloadUpstream
|
107
|
+
// TODO move `remaining` into `PayloadUpstream`
|
108
108
|
remaining: number
|
109
109
|
}
|
110
110
|
|
111
|
-
export interface
|
111
|
+
export interface SyncProcessor {
|
112
112
|
push: (
|
113
113
|
/** `batch` needs to follow the same rules as `batch` in `SyncBackend.push` */
|
114
114
|
batch: ReadonlyArray<MutationEvent.EncodedWithMeta>,
|
115
|
-
|
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>
|
115
|
+
) => Effect.Effect<void, UnexpectedError | InvalidPushError, HttpClient.HttpClient | LeaderThreadCtx>
|
123
116
|
|
124
117
|
pushPartial: (mutationEvent: MutationEvent.PartialAnyEncoded) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx>
|
125
118
|
boot: (args: {
|
126
119
|
dbReady: Deferred.Deferred<void>
|
127
120
|
}) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx | Scope.Scope | HttpClient.HttpClient>
|
128
|
-
syncState:
|
121
|
+
syncState: Effect.Effect<SyncState, UnexpectedError>
|
129
122
|
}
|
130
123
|
|
131
124
|
export interface PullQueueSet {
|
package/src/mutation.ts
CHANGED
@@ -8,19 +8,10 @@ import { prepareBindValues } from './util.js'
|
|
8
8
|
|
9
9
|
export const getExecArgsFromMutation = ({
|
10
10
|
mutationDef,
|
11
|
-
|
11
|
+
mutationEventDecoded,
|
12
12
|
}: {
|
13
13
|
mutationDef: MutationDef.Any
|
14
|
-
|
15
|
-
mutationEvent:
|
16
|
-
| {
|
17
|
-
decoded: MutationEvent.AnyDecoded | MutationEvent.PartialAnyDecoded
|
18
|
-
encoded: undefined
|
19
|
-
}
|
20
|
-
| {
|
21
|
-
decoded: undefined
|
22
|
-
encoded: MutationEvent.AnyEncoded | MutationEvent.PartialAnyEncoded
|
23
|
-
}
|
14
|
+
mutationEventDecoded: MutationEvent.Any | MutationEvent.PartialAny
|
24
15
|
}): ReadonlyArray<{
|
25
16
|
statementSql: string
|
26
17
|
bindValues: PreparedBindValues
|
@@ -32,9 +23,7 @@ export const getExecArgsFromMutation = ({
|
|
32
23
|
|
33
24
|
switch (typeof mutationDef.sql) {
|
34
25
|
case 'function': {
|
35
|
-
const
|
36
|
-
mutationEvent.decoded?.args ?? Schema.decodeUnknownSync(mutationDef.schema)(mutationEvent.encoded!.args)
|
37
|
-
const res = mutationDef.sql(mutationArgsDecoded)
|
26
|
+
const res = mutationDef.sql(mutationEventDecoded.args)
|
38
27
|
statementRes = Array.isArray(res) ? res : [res]
|
39
28
|
break
|
40
29
|
}
|
@@ -51,9 +40,10 @@ export const getExecArgsFromMutation = ({
|
|
51
40
|
return statementRes.map((statementRes) => {
|
52
41
|
const statementSql = typeof statementRes === 'string' ? statementRes : statementRes.sql
|
53
42
|
|
54
|
-
const
|
55
|
-
|
56
|
-
|
43
|
+
const bindValues =
|
44
|
+
typeof statementRes === 'string'
|
45
|
+
? Schema.encodeUnknownSync(mutationDef.schema)(mutationEventDecoded.args)
|
46
|
+
: statementRes.bindValues
|
57
47
|
|
58
48
|
const writeTables = typeof statementRes === 'string' ? undefined : statementRes.writeTables
|
59
49
|
|
@@ -1,8 +1,8 @@
|
|
1
|
-
import { memoizeByRef, shouldNeverHappen } from '@livestore/utils'
|
1
|
+
import { isDevEnv, memoizeByRef, shouldNeverHappen } from '@livestore/utils'
|
2
2
|
import { Chunk, Effect, Option, Schema, Stream } from '@livestore/utils/effect'
|
3
3
|
|
4
4
|
import { type MigrationOptionsFromMutationLog, type SynchronousDatabase, UnexpectedError } from './adapter-types.js'
|
5
|
-
import {
|
5
|
+
import { getExecArgsFromMutation } from './mutation.js'
|
6
6
|
import type { LiveStoreSchema, MutationDef, MutationEvent, MutationLogMetaRow } from './schema/mod.js'
|
7
7
|
import { EventId, MUTATION_LOG_META_TABLE } from './schema/mod.js'
|
8
8
|
import type { PreparedBindValues } from './util.js'
|
@@ -10,8 +10,7 @@ import { sql } from './util.js'
|
|
10
10
|
|
11
11
|
export const rehydrateFromMutationLog = ({
|
12
12
|
logDb,
|
13
|
-
|
14
|
-
// db,
|
13
|
+
db,
|
15
14
|
schema,
|
16
15
|
migrationOptions,
|
17
16
|
onProgress,
|
@@ -29,8 +28,6 @@ export const rehydrateFromMutationLog = ({
|
|
29
28
|
|
30
29
|
const hashMutation = memoizeByRef((mutation: MutationDef.Any) => Schema.hash(mutation.schema))
|
31
30
|
|
32
|
-
const applyMutation = yield* makeApplyMutation
|
33
|
-
|
34
31
|
const processMutation = (row: MutationLogMetaRow) =>
|
35
32
|
Effect.gen(function* () {
|
36
33
|
const mutationDef = schema.mutations.get(row.mutation) ?? shouldNeverHappen(`Unknown mutation ${row.mutation}`)
|
@@ -43,10 +40,7 @@ export const rehydrateFromMutationLog = ({
|
|
43
40
|
)
|
44
41
|
}
|
45
42
|
|
46
|
-
const
|
47
|
-
|
48
|
-
// Checking whether the schema has changed in an incompatible way
|
49
|
-
yield* Schema.decodeUnknown(mutationDef.schema)(args).pipe(
|
43
|
+
const argsDecoded = yield* Schema.decodeUnknown(Schema.parseJson(mutationDef.schema))(row.argsJson).pipe(
|
50
44
|
Effect.mapError((cause) =>
|
51
45
|
UnexpectedError.make({
|
52
46
|
cause,
|
@@ -59,14 +53,28 @@ This likely means the schema has changed in an incompatible way.
|
|
59
53
|
),
|
60
54
|
)
|
61
55
|
|
62
|
-
const
|
56
|
+
const mutationEventDecoded = {
|
63
57
|
id: { global: row.idGlobal, local: row.idLocal },
|
64
58
|
parentId: { global: row.parentIdGlobal, local: row.parentIdLocal },
|
65
59
|
mutation: row.mutation,
|
66
|
-
args,
|
67
|
-
} satisfies MutationEvent.
|
68
|
-
|
69
|
-
|
60
|
+
args: argsDecoded,
|
61
|
+
} satisfies MutationEvent.Any
|
62
|
+
|
63
|
+
const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
64
|
+
|
65
|
+
const makeExecuteOptions = (statementSql: string, bindValues: any) => ({
|
66
|
+
onRowsChanged: (rowsChanged: number) => {
|
67
|
+
if (rowsChanged === 0 && migrationOptions.logging?.excludeAffectedRows?.(statementSql) !== true) {
|
68
|
+
console.warn(`Mutation "${mutationDef.name}" did not affect any rows:`, statementSql, bindValues)
|
69
|
+
}
|
70
|
+
},
|
71
|
+
})
|
72
|
+
|
73
|
+
for (const { statementSql, bindValues } of execArgsArr) {
|
74
|
+
// TODO cache prepared statements for mutations
|
75
|
+
db.execute(statementSql, bindValues, isDevEnv() ? makeExecuteOptions(statementSql, bindValues) : undefined)
|
76
|
+
// console.log(`Re-executed mutation ${mutationSql}`, bindValues)
|
77
|
+
}
|
70
78
|
}).pipe(Effect.withSpan(`@livestore/common:rehydrateFromMutationLog:processMutation`))
|
71
79
|
|
72
80
|
const CHUNK_SIZE = 100
|
package/src/schema/EventId.ts
CHANGED
@@ -1,14 +1,4 @@
|
|
1
|
-
import {
|
2
|
-
|
3
|
-
export type LocalEventId = Brand.Branded<number, 'LocalEventId'>
|
4
|
-
export const localEventId = Brand.nominal<LocalEventId>()
|
5
|
-
export const LocalEventId = Schema.fromBrand(localEventId)(Schema.Int)
|
6
|
-
|
7
|
-
export type GlobalEventId = Brand.Branded<number, 'GlobalEventId'>
|
8
|
-
export const globalEventId = Brand.nominal<GlobalEventId>()
|
9
|
-
export const GlobalEventId = Schema.fromBrand(globalEventId)(Schema.Int)
|
10
|
-
|
11
|
-
export const localDefault = 0 as any as LocalEventId
|
1
|
+
import { Schema } from '@livestore/utils/effect'
|
12
2
|
|
13
3
|
/**
|
14
4
|
* LiveStore event id value consisting of a globally unique event sequence number
|
@@ -16,11 +6,11 @@ export const localDefault = 0 as any as LocalEventId
|
|
16
6
|
*
|
17
7
|
* The local sequence number is only used for localOnly mutations and starts from 0 for each global sequence number.
|
18
8
|
*/
|
19
|
-
export type EventId = { global:
|
9
|
+
export type EventId = { global: number; local: number }
|
20
10
|
|
21
11
|
export const EventId = Schema.Struct({
|
22
|
-
global:
|
23
|
-
local:
|
12
|
+
global: Schema.Number,
|
13
|
+
local: Schema.Number,
|
24
14
|
}).annotations({ title: 'LiveStore.EventId' })
|
25
15
|
|
26
16
|
/**
|
@@ -37,24 +27,20 @@ export const isEqual = (a: EventId, b: EventId) => a.global === b.global && a.lo
|
|
37
27
|
|
38
28
|
export type EventIdPair = { id: EventId; parentId: EventId }
|
39
29
|
|
40
|
-
export const ROOT = { global: -1
|
30
|
+
export const ROOT = { global: -1, local: 0 } satisfies EventId
|
41
31
|
|
42
32
|
export const isGreaterThan = (a: EventId, b: EventId) => {
|
43
33
|
return a.global > b.global || (a.global === b.global && a.local > b.local)
|
44
34
|
}
|
45
35
|
|
46
|
-
export const
|
47
|
-
return Schema.is(EventId)(id) ? id : Schema.decodeSync(EventId)(id)
|
48
|
-
}
|
49
|
-
|
50
|
-
export const nextPair = (id: EventId, isLocal: boolean): EventIdPair => {
|
36
|
+
export const nextPair = (id: EventId, isLocal: boolean) => {
|
51
37
|
if (isLocal) {
|
52
|
-
return { id: { global: id.global, local:
|
38
|
+
return { id: { global: id.global, local: id.local + 1 }, parentId: id }
|
53
39
|
}
|
54
40
|
|
55
41
|
return {
|
56
|
-
id: { global:
|
42
|
+
id: { global: id.global + 1, local: 0 },
|
57
43
|
// NOTE we always point to `local: 0` for non-localOnly mutations
|
58
|
-
parentId: { global: id.global, local:
|
44
|
+
parentId: { global: id.global, local: 0 },
|
59
45
|
}
|
60
46
|
}
|