@livestore/common 0.3.0-dev.3 → 0.3.0-dev.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapter-types.d.ts +26 -23
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js.map +1 -1
- package/dist/devtools/devtools-bridge.d.ts +2 -1
- package/dist/devtools/devtools-bridge.d.ts.map +1 -1
- package/dist/devtools/devtools-messages.d.ts +90 -102
- package/dist/devtools/devtools-messages.d.ts.map +1 -1
- package/dist/devtools/devtools-messages.js +9 -6
- package/dist/devtools/devtools-messages.js.map +1 -1
- package/dist/leader-thread/apply-mutation.js.map +1 -1
- package/dist/leader-thread/leader-sync-processor.d.ts.map +1 -1
- package/dist/leader-thread/leader-sync-processor.js +10 -7
- package/dist/leader-thread/leader-sync-processor.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +22 -66
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +4 -2
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +5 -2
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/mutationlog.d.ts +4 -17
- package/dist/leader-thread/mutationlog.d.ts.map +1 -1
- package/dist/leader-thread/mutationlog.js +2 -1
- package/dist/leader-thread/mutationlog.js.map +1 -1
- package/dist/leader-thread/pull-queue-set.d.ts.map +1 -1
- package/dist/leader-thread/types.d.ts +7 -5
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/leader-thread/types.js.map +1 -1
- package/dist/mutation.d.ts +1 -1
- package/dist/mutation.d.ts.map +1 -1
- package/dist/rehydrate-from-mutationlog.js.map +1 -1
- package/dist/schema/EventId.d.ts +16 -14
- package/dist/schema/EventId.d.ts.map +1 -1
- package/dist/schema/EventId.js +15 -7
- package/dist/schema/EventId.js.map +1 -1
- package/dist/schema/MutationEvent.d.ts +51 -76
- package/dist/schema/MutationEvent.d.ts.map +1 -1
- package/dist/schema/MutationEvent.js +29 -13
- package/dist/schema/MutationEvent.js.map +1 -1
- package/dist/schema/system-tables.d.ts +17 -17
- package/dist/schema/system-tables.d.ts.map +1 -1
- package/dist/schema/system-tables.js +14 -7
- 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 +2 -2
- package/dist/sync/client-session-sync-processor.d.ts.map +1 -1
- package/dist/sync/next/history-dag-common.d.ts +1 -4
- package/dist/sync/next/history-dag-common.d.ts.map +1 -1
- package/dist/sync/next/history-dag-common.js +1 -1
- package/dist/sync/next/history-dag-common.js.map +1 -1
- package/dist/sync/next/rebase-events.d.ts +1 -1
- package/dist/sync/next/rebase-events.d.ts.map +1 -1
- package/dist/sync/next/rebase-events.js +3 -2
- package/dist/sync/next/rebase-events.js.map +1 -1
- package/dist/sync/next/test/mutation-fixtures.d.ts +7 -7
- package/dist/sync/next/test/mutation-fixtures.d.ts.map +1 -1
- package/dist/sync/next/test/mutation-fixtures.js +3 -9
- package/dist/sync/next/test/mutation-fixtures.js.map +1 -1
- package/dist/sync/sync.d.ts +6 -6
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/syncstate.d.ts +10 -10
- package/dist/sync/syncstate.test.js +2 -6
- package/dist/sync/syncstate.test.js.map +1 -1
- package/dist/sync/validate-push-payload.d.ts +2 -2
- package/dist/sync/validate-push-payload.d.ts.map +1 -1
- package/dist/sync/validate-push-payload.js +2 -2
- package/dist/sync/validate-push-payload.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/src/adapter-types.ts +22 -24
- package/src/devtools/devtools-bridge.ts +2 -1
- package/src/devtools/devtools-messages.ts +9 -6
- package/src/leader-thread/apply-mutation.ts +1 -1
- package/src/leader-thread/leader-sync-processor.ts +14 -10
- package/src/leader-thread/leader-worker-devtools.ts +30 -109
- package/src/leader-thread/make-leader-thread-layer.ts +15 -5
- package/src/leader-thread/mutationlog.ts +9 -5
- package/src/leader-thread/types.ts +7 -8
- package/src/mutation.ts +1 -1
- package/src/rehydrate-from-mutationlog.ts +1 -1
- package/src/schema/EventId.ts +23 -9
- package/src/schema/MutationEvent.ts +37 -18
- package/src/schema/system-tables.ts +14 -7
- package/src/schema-management/migrations.ts +6 -6
- package/src/sync/client-session-sync-processor.ts +2 -2
- package/src/sync/next/history-dag-common.ts +1 -1
- package/src/sync/next/rebase-events.ts +5 -5
- package/src/sync/next/test/mutation-fixtures.ts +3 -10
- package/src/sync/sync.ts +4 -2
- package/src/sync/syncstate.test.ts +4 -4
- package/src/sync/validate-push-payload.ts +7 -4
- package/src/version.ts +1 -1
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Effect } from '@livestore/utils/effect';
|
2
|
-
import type { MutationEvent } from '../schema/mod.js';
|
2
|
+
import type { EventId, MutationEvent } from '../schema/mod.js';
|
3
3
|
import { InvalidPushError } from './sync.js';
|
4
|
-
export declare const validatePushPayload: (batch: ReadonlyArray<MutationEvent.
|
4
|
+
export declare const validatePushPayload: (batch: ReadonlyArray<MutationEvent.AnyEncodedGlobal>, currentEventId: EventId.GlobalEventId) => Effect.Effect<undefined, InvalidPushError, never>;
|
5
5
|
//# sourceMappingURL=validate-push-payload.d.ts.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"validate-push-payload.d.ts","sourceRoot":"","sources":["../../src/sync/validate-push-payload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAEhD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;
|
1
|
+
{"version":3,"file":"validate-push-payload.d.ts","sourceRoot":"","sources":["../../src/sync/validate-push-payload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAEhD,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAG5C,eAAO,MAAM,mBAAmB,UACvB,aAAa,CAAC,aAAa,CAAC,gBAAgB,CAAC,kBACpC,OAAO,CAAC,aAAa,sDAYnC,CAAA"}
|
@@ -2,12 +2,12 @@ import { Effect } from '@livestore/utils/effect';
|
|
2
2
|
import { InvalidPushError } from './sync.js';
|
3
3
|
// TODO proper batch validation
|
4
4
|
export const validatePushPayload = (batch, currentEventId) => Effect.gen(function* () {
|
5
|
-
if (batch[0].id
|
5
|
+
if (batch[0].id <= currentEventId) {
|
6
6
|
return yield* InvalidPushError.make({
|
7
7
|
reason: {
|
8
8
|
_tag: 'ServerAhead',
|
9
9
|
minimumExpectedId: currentEventId + 1,
|
10
|
-
providedId: batch[0].id
|
10
|
+
providedId: batch[0].id,
|
11
11
|
},
|
12
12
|
});
|
13
13
|
}
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"validate-push-payload.js","sourceRoot":"","sources":["../../src/sync/validate-push-payload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAGhD,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAE5C,+BAA+B;AAC/B,MAAM,CAAC,MAAM,mBAAmB,GAAG,
|
1
|
+
{"version":3,"file":"validate-push-payload.js","sourceRoot":"","sources":["../../src/sync/validate-push-payload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAGhD,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAE5C,+BAA+B;AAC/B,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,KAAoD,EACpD,cAAqC,EACrC,EAAE,CACF,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,IAAI,KAAK,CAAC,CAAC,CAAE,CAAC,EAAE,IAAI,cAAc,EAAE,CAAC;QACnC,OAAO,KAAK,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC;YAClC,MAAM,EAAE;gBACN,IAAI,EAAE,aAAa;gBACnB,iBAAiB,EAAE,cAAc,GAAG,CAAC;gBACrC,UAAU,EAAE,KAAK,CAAC,CAAC,CAAE,CAAC,EAAE;aACzB;SACF,CAAC,CAAA;IACJ,CAAC;AACH,CAAC,CAAC,CAAA"}
|
package/dist/version.d.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
export declare const liveStoreVersion: "0.3.0-dev.
|
1
|
+
export declare const liveStoreVersion: "0.3.0-dev.4";
|
2
2
|
/**
|
3
3
|
* This version number is incremented whenever the internal storage format changes in a breaking way.
|
4
4
|
* Whenever this version changes, LiveStore will start with fresh database files. Old database files are not deleted.
|
package/dist/version.js
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
// TODO bring back when Expo and Playwright supports `with` imports
|
2
2
|
// import packageJson from '../package.json' with { type: 'json' }
|
3
3
|
// export const liveStoreVersion = packageJson.version
|
4
|
-
export const liveStoreVersion = '0.3.0-dev.
|
4
|
+
export const liveStoreVersion = '0.3.0-dev.4';
|
5
5
|
/**
|
6
6
|
* This version number is incremented whenever the internal storage format changes in a breaking way.
|
7
7
|
* Whenever this version changes, LiveStore will start with fresh database files. Old database files are not deleted.
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@livestore/common",
|
3
|
-
"version": "0.3.0-dev.
|
3
|
+
"version": "0.3.0-dev.5",
|
4
4
|
"type": "module",
|
5
5
|
"exports": {
|
6
6
|
".": {
|
@@ -53,8 +53,8 @@
|
|
53
53
|
"graphology": "0.26.0-alpha1",
|
54
54
|
"graphology-dag": "0.4.1",
|
55
55
|
"graphology-types": "0.24.7",
|
56
|
-
"@livestore/
|
57
|
-
"@livestore/
|
56
|
+
"@livestore/utils": "0.3.0-dev.5",
|
57
|
+
"@livestore/db-schema": "0.3.0-dev.5"
|
58
58
|
},
|
59
59
|
"devDependencies": {
|
60
60
|
"vitest": "^2.1.4"
|
package/src/adapter-types.ts
CHANGED
@@ -29,8 +29,28 @@ export type SynchronousDatabaseChangeset = {
|
|
29
29
|
export type ClientSession = {
|
30
30
|
/** SQLite database with synchronous API running in the same thread (usually in-memory) */
|
31
31
|
syncDb: SynchronousDatabase
|
32
|
-
|
33
|
-
|
32
|
+
devtools: { enabled: boolean }
|
33
|
+
clientId: string
|
34
|
+
sessionId: string
|
35
|
+
/** Status info whether current session is leader or not */
|
36
|
+
lockStatus: SubscriptionRef.SubscriptionRef<LockStatus>
|
37
|
+
shutdown: (cause: Cause.Cause<UnexpectedError | IntentionalShutdownCause>) => Effect.Effect<void>
|
38
|
+
/** A proxy API to communicate with the leader thread */
|
39
|
+
leaderThread: ClientSessionLeaderThreadProxy
|
40
|
+
}
|
41
|
+
|
42
|
+
export type ClientSessionLeaderThreadProxy = {
|
43
|
+
mutations: {
|
44
|
+
pull: Stream.Stream<{ payload: PayloadUpstream; remaining: number }, UnexpectedError>
|
45
|
+
push(batch: ReadonlyArray<MutationEvent.AnyEncoded>): Effect.Effect<void, UnexpectedError | InvalidPushError>
|
46
|
+
initialMutationEventId: EventId
|
47
|
+
}
|
48
|
+
export: Effect.Effect<Uint8Array, UnexpectedError>
|
49
|
+
getMutationLogData: Effect.Effect<Uint8Array, UnexpectedError>
|
50
|
+
getSyncState: Effect.Effect<SyncState, UnexpectedError>
|
51
|
+
networkStatus: SubscriptionRef.SubscriptionRef<NetworkStatus>
|
52
|
+
/** For debugging purposes it can be useful to manually trigger devtools messages (e.g. to reset the database) */
|
53
|
+
sendDevtoolsMessage: (message: Devtools.MessageToAppLeader) => Effect.Effect<void, UnexpectedError>
|
34
54
|
}
|
35
55
|
|
36
56
|
export type SynchronousDatabase<TReq = any, TMetadata extends TReq = TReq> = {
|
@@ -103,28 +123,6 @@ export const BootStatus = Schema.Union(
|
|
103
123
|
|
104
124
|
export type BootStatus = typeof BootStatus.Type
|
105
125
|
|
106
|
-
// TODO refactor `Coordinator` to embrace more of the "leader semantics"
|
107
|
-
export type Coordinator = {
|
108
|
-
devtools: {
|
109
|
-
enabled: boolean
|
110
|
-
// TODO incorporate sessionId and rethink appHostId
|
111
|
-
appHostId: string
|
112
|
-
}
|
113
|
-
sessionId: string
|
114
|
-
// TODO is exposing the lock status really needed (or only relevant for web adapter?)
|
115
|
-
lockStatus: SubscriptionRef.SubscriptionRef<LockStatus>
|
116
|
-
mutations: {
|
117
|
-
pull: Stream.Stream<{ payload: PayloadUpstream; remaining: number }, UnexpectedError>
|
118
|
-
push(batch: ReadonlyArray<MutationEvent.AnyEncoded>): Effect.Effect<void, UnexpectedError | InvalidPushError>
|
119
|
-
initialMutationEventId: EventId
|
120
|
-
}
|
121
|
-
export: Effect.Effect<Uint8Array, UnexpectedError>
|
122
|
-
getMutationLogData: Effect.Effect<Uint8Array, UnexpectedError>
|
123
|
-
getLeaderSyncState: Effect.Effect<SyncState, UnexpectedError>
|
124
|
-
networkStatus: SubscriptionRef.SubscriptionRef<NetworkStatus>
|
125
|
-
shutdown: (cause: Cause.Cause<UnexpectedError | IntentionalShutdownCause>) => Effect.Effect<void>
|
126
|
-
}
|
127
|
-
|
128
126
|
/**
|
129
127
|
* Can be used in queries to refer to the current session id.
|
130
128
|
* Will be replaced with the actual session id at runtime
|
@@ -6,7 +6,8 @@ export type PrepareDevtoolsBridge = {
|
|
6
6
|
/** Messages coming from the app host (usually responses to requests) */
|
7
7
|
responsePubSub: PubSub.PubSub<Devtools.MessageFromAppLeader | Devtools.MessageFromAppClientSession>
|
8
8
|
sendToAppHost: (msg: Devtools.MessageToAppLeader | Devtools.MessageToAppClientSession) => Effect.Effect<void>
|
9
|
-
|
9
|
+
clientId: string
|
10
|
+
sessionId: string
|
10
11
|
copyToClipboard: (text: string) => Effect.Effect<void>
|
11
12
|
sendEscapeKey?: Effect.Effect<void>
|
12
13
|
isLeader: boolean
|
@@ -7,7 +7,8 @@ import { PreparedBindValues } from '../util.js'
|
|
7
7
|
import { liveStoreVersion as pkgVersion } from '../version.js'
|
8
8
|
|
9
9
|
const requestId = Schema.String
|
10
|
-
const
|
10
|
+
const clientId = Schema.String
|
11
|
+
const sessionId = Schema.String
|
11
12
|
const liveStoreVersion = Schema.Literal(pkgVersion)
|
12
13
|
|
13
14
|
const LSDMessage = <Tag extends string, Fields extends Schema.Struct.Fields>(tag: Tag, fields: Fields) =>
|
@@ -23,13 +24,15 @@ const LSDChannelMessage = <Tag extends string, Fields extends Schema.Struct.Fiel
|
|
23
24
|
|
24
25
|
const LSDStoreChannelMessage = <Tag extends string, Fields extends Schema.Struct.Fields>(tag: Tag, fields: Fields) =>
|
25
26
|
LSDMessage(tag, {
|
26
|
-
|
27
|
+
clientId,
|
28
|
+
sessionId,
|
27
29
|
...fields,
|
28
30
|
})
|
29
31
|
|
30
32
|
const LSDStoreReqResMessage = <Tag extends string, Fields extends Schema.Struct.Fields>(tag: Tag, fields: Fields) =>
|
31
33
|
LSDMessage(tag, {
|
32
|
-
|
34
|
+
clientId,
|
35
|
+
sessionId,
|
33
36
|
requestId,
|
34
37
|
...fields,
|
35
38
|
})
|
@@ -82,12 +85,12 @@ export class DebugInfoRerunQueryRes extends LSDStoreReqResMessage('LSD.DebugInfo
|
|
82
85
|
|
83
86
|
// TODO refactor this to use push/pull semantics
|
84
87
|
export class MutationBroadcast extends LSDMessage('LSD.Leader.MutationBroadcast', {
|
85
|
-
mutationEventEncoded: MutationEvent.
|
88
|
+
mutationEventEncoded: MutationEvent.AnyEncoded,
|
86
89
|
}) {}
|
87
90
|
|
88
91
|
// TODO refactor this to use push/pull semantics
|
89
92
|
export class RunMutationReq extends LSDReqResMessage('LSD.Leader.RunMutationReq', {
|
90
|
-
mutationEventEncoded: MutationEvent.
|
93
|
+
mutationEventEncoded: MutationEvent.AnyEncoded.pipe(Schema.omit('id', 'parentId')),
|
91
94
|
}) {}
|
92
95
|
|
93
96
|
export class RunMutationRes extends LSDReqResMessage('LSD.Leader.RunMutationRes', {}) {}
|
@@ -167,7 +170,7 @@ export class SyncingInfoRes extends LSDReqResMessage('LSD.Leader.SyncingInfoRes'
|
|
167
170
|
export class SyncHistorySubscribe extends LSDReqResMessage('LSD.Leader.SyncHistorySubscribe', {}) {}
|
168
171
|
export class SyncHistoryUnsubscribe extends LSDReqResMessage('LSD.Leader.SyncHistoryUnsubscribe', {}) {}
|
169
172
|
export class SyncHistoryRes extends LSDReqResMessage('LSD.Leader.SyncHistoryRes', {
|
170
|
-
mutationEventEncoded: MutationEvent.
|
173
|
+
mutationEventEncoded: MutationEvent.AnyEncodedGlobal,
|
171
174
|
metadata: Schema.Option(Schema.JsonValue),
|
172
175
|
}) {}
|
173
176
|
|
@@ -132,7 +132,7 @@ const makeShouldExcludeMutationFromLog = memoizeByRef((schema: LiveStoreSchema)
|
|
132
132
|
? (migrationOptions.excludeMutations ?? new Set(['livestore.RawSql']))
|
133
133
|
: new Set(['livestore.RawSql'])
|
134
134
|
|
135
|
-
return (mutationName: string, mutationEventDecoded: MutationEvent.
|
135
|
+
return (mutationName: string, mutationEventDecoded: MutationEvent.AnyDecoded): boolean => {
|
136
136
|
if (mutationLogExclude.has(mutationName)) return true
|
137
137
|
|
138
138
|
const mutationDef = schema.mutations.get(mutationName) ?? shouldNeverHappen(`Unknown mutation: ${mutationName}`)
|
@@ -9,6 +9,7 @@ import {
|
|
9
9
|
FiberHandle,
|
10
10
|
Option,
|
11
11
|
OtelTracer,
|
12
|
+
ReadonlyArray,
|
12
13
|
Ref,
|
13
14
|
Schema,
|
14
15
|
Stream,
|
@@ -102,7 +103,7 @@ export const makeLeaderSyncProcessor = ({
|
|
102
103
|
initialBlockingSyncContext: InitialBlockingSyncContext
|
103
104
|
}): Effect.Effect<SyncProcessor, UnexpectedError, Scope.Scope> =>
|
104
105
|
Effect.gen(function* () {
|
105
|
-
const syncBackendQueue = yield* BucketQueue.make<MutationEvent.
|
106
|
+
const syncBackendQueue = yield* BucketQueue.make<MutationEvent.EncodedWithMeta>()
|
106
107
|
|
107
108
|
const stateRef = yield* Ref.make<ProcessorState>({ _tag: 'init' })
|
108
109
|
|
@@ -257,13 +258,16 @@ export const makeLeaderSyncProcessor = ({
|
|
257
258
|
)
|
258
259
|
}
|
259
260
|
|
260
|
-
const pendingMutationEvents = yield* getMutationEventsSince({
|
261
|
+
const pendingMutationEvents = yield* getMutationEventsSince({
|
262
|
+
global: initialBackendHead,
|
263
|
+
local: EventId.localDefault,
|
264
|
+
}).pipe(Effect.map(ReadonlyArray.map((_) => new MutationEvent.EncodedWithMeta(_))))
|
261
265
|
|
262
266
|
const initialSyncState = {
|
263
|
-
pending: pendingMutationEvents
|
267
|
+
pending: pendingMutationEvents,
|
264
268
|
// On the leader we don't need a rollback tail beyond `pending` items
|
265
269
|
rollbackTail: [],
|
266
|
-
upstreamHead: { global: initialBackendHead, local:
|
270
|
+
upstreamHead: { global: initialBackendHead, local: EventId.localDefault },
|
267
271
|
localHead: initialLocalHead,
|
268
272
|
} as SyncState.SyncState
|
269
273
|
|
@@ -410,7 +414,7 @@ const backgroundBackendPulling = ({
|
|
410
414
|
initialBlockingSyncContext,
|
411
415
|
}: {
|
412
416
|
dbReady: Deferred.Deferred<void>
|
413
|
-
initialBackendHead:
|
417
|
+
initialBackendHead: EventId.GlobalEventId
|
414
418
|
isLocalEvent: (mutationEventEncoded: MutationEvent.EncodedWithMeta) => boolean
|
415
419
|
restartBackendPushing: (
|
416
420
|
filteredRebasedPending: ReadonlyArray<MutationEvent.EncodedWithMeta>,
|
@@ -538,7 +542,7 @@ const backgroundBackendPulling = ({
|
|
538
542
|
yield* SubscriptionRef.waitUntil(syncBackend.isConnected, (isConnected) => isConnected === true)
|
539
543
|
|
540
544
|
yield* onNewPullChunk(
|
541
|
-
batch.map((_) =>
|
545
|
+
batch.map((_) => MutationEvent.EncodedWithMeta.fromGlobal(_.mutationEventEncoded)),
|
542
546
|
remaining,
|
543
547
|
)
|
544
548
|
|
@@ -588,7 +592,7 @@ const rollback = ({
|
|
588
592
|
}),
|
589
593
|
)
|
590
594
|
|
591
|
-
const getCursorInfo = (remoteHead:
|
595
|
+
const getCursorInfo = (remoteHead: EventId.GlobalEventId) =>
|
592
596
|
Effect.gen(function* () {
|
593
597
|
const { dbLog } = yield* LeaderThreadCtx
|
594
598
|
|
@@ -605,7 +609,7 @@ const getCursorInfo = (remoteHead: number) =>
|
|
605
609
|
).pipe(Effect.andThen(Schema.decode(MutationlogQuerySchema)), Effect.map(Option.flatten), Effect.orDie)
|
606
610
|
|
607
611
|
return Option.some({
|
608
|
-
cursor: { global: remoteHead, local:
|
612
|
+
cursor: { global: remoteHead, local: EventId.localDefault },
|
609
613
|
metadata: syncMetadataOption,
|
610
614
|
}) satisfies InitialSyncInfo
|
611
615
|
}).pipe(Effect.withSpan('@livestore/common:leader-thread:syncing:getCursorInfo', { attributes: { remoteHead } }))
|
@@ -616,7 +620,7 @@ const backgroundBackendPushing = ({
|
|
616
620
|
span,
|
617
621
|
}: {
|
618
622
|
dbReady: Deferred.Deferred<void>
|
619
|
-
syncBackendQueue: BucketQueue.BucketQueue<MutationEvent.
|
623
|
+
syncBackendQueue: BucketQueue.BucketQueue<MutationEvent.EncodedWithMeta>
|
620
624
|
span: otel.Span | undefined
|
621
625
|
}) =>
|
622
626
|
Effect.gen(function* () {
|
@@ -639,7 +643,7 @@ const backgroundBackendPushing = ({
|
|
639
643
|
})
|
640
644
|
|
641
645
|
// TODO handle push errors (should only happen during concurrent pull+push)
|
642
|
-
const pushResult = yield* syncBackend.push(queueItems).pipe(Effect.either)
|
646
|
+
const pushResult = yield* syncBackend.push(queueItems.map((_) => _.toGlobal())).pipe(Effect.either)
|
643
647
|
|
644
648
|
if (pushResult._tag === 'Left') {
|
645
649
|
span?.addEvent('backend-push-error', { error: pushResult.left.toString() })
|
@@ -1,18 +1,11 @@
|
|
1
|
-
import { Effect, FiberMap, Option,
|
1
|
+
import { Effect, FiberMap, Option, Stream, SubscriptionRef } from '@livestore/utils/effect'
|
2
2
|
|
3
3
|
import { Devtools, IntentionalShutdownCause, liveStoreVersion, UnexpectedError } from '../index.js'
|
4
4
|
import { MUTATION_LOG_META_TABLE, SCHEMA_META_TABLE, SCHEMA_MUTATIONS_META_TABLE } from '../schema/mod.js'
|
5
|
-
import type { ShutdownChannel } from './shutdown-channel.js'
|
6
5
|
import type { DevtoolsOptions, PersistenceInfoPair } from './types.js'
|
7
6
|
import { LeaderThreadCtx } from './types.js'
|
8
7
|
|
9
|
-
type SendMessageToDevtools = (
|
10
|
-
message: Devtools.MessageFromAppLeader,
|
11
|
-
options?: {
|
12
|
-
/** Send message even if not connected (e.g. for initial broadcast messages) */
|
13
|
-
force: boolean
|
14
|
-
},
|
15
|
-
) => Effect.Effect<void>
|
8
|
+
type SendMessageToDevtools = (message: Devtools.MessageFromAppLeader) => Effect.Effect<void>
|
16
9
|
|
17
10
|
// TODO bind scope to the webchannel lifetime
|
18
11
|
export const bootDevtools = (options: DevtoolsOptions) =>
|
@@ -21,44 +14,24 @@ export const bootDevtools = (options: DevtoolsOptions) =>
|
|
21
14
|
return
|
22
15
|
}
|
23
16
|
|
24
|
-
const {
|
17
|
+
const { connectedClientSessionPullQueues, syncProcessor, extraIncomingMessagesQueue } = yield* LeaderThreadCtx
|
25
18
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
)
|
19
|
+
yield* listenToDevtools({
|
20
|
+
incomingMessages: Stream.fromQueue(extraIncomingMessagesQueue),
|
21
|
+
sendMessage: () => Effect.void,
|
22
|
+
}).pipe(Effect.tapCauseLogPretty, Effect.forkScoped)
|
31
23
|
|
32
|
-
const
|
24
|
+
const { persistenceInfo, devtoolsWebChannel } = yield* options.makeContext
|
33
25
|
|
34
|
-
const
|
35
|
-
|
36
|
-
|
26
|
+
const sendMessage: SendMessageToDevtools = (message) =>
|
27
|
+
devtoolsWebChannel
|
28
|
+
.send(message)
|
29
|
+
.pipe(
|
30
|
+
Effect.withSpan('@livestore/common:leader-thread:devtools:sendToDevtools'),
|
31
|
+
Effect.interruptible,
|
32
|
+
Effect.ignoreLogged,
|
33
|
+
)
|
37
34
|
|
38
|
-
const devtoolsCoordinatorChannel = devtoolsWebChannel
|
39
|
-
// coordinatorMessagePortOrChannel instanceof MessagePort
|
40
|
-
// ? yield* WebChannel.messagePortChannel({
|
41
|
-
// port: coordinatorMessagePortOrChannel,
|
42
|
-
// schema: { send: Devtools.MessageFromAppLeader, listen: Devtools.MessageToAppLeader },
|
43
|
-
// })
|
44
|
-
// : coordinatorMessagePortOrChannel
|
45
|
-
|
46
|
-
const sendMessage: SendMessageToDevtools = (message, options) =>
|
47
|
-
Effect.gen(function* () {
|
48
|
-
if (options?.force === true || (yield* isConnected)) {
|
49
|
-
yield* devtoolsCoordinatorChannel.send(message)
|
50
|
-
} else {
|
51
|
-
yield* Queue.offer(outgoingMessagesQueue, message)
|
52
|
-
}
|
53
|
-
}).pipe(
|
54
|
-
Effect.withSpan('@livestore/common:leader-thread:devtools:sendToDevtools'),
|
55
|
-
Effect.interruptible,
|
56
|
-
Effect.ignoreLogged,
|
57
|
-
)
|
58
|
-
|
59
|
-
// broadcastCallbacks.add((message) => sendMessage(message))
|
60
|
-
|
61
|
-
const { connectedClientSessionPullQueues, syncProcessor } = yield* LeaderThreadCtx
|
62
35
|
const { localHead } = yield* syncProcessor.syncState
|
63
36
|
|
64
37
|
// TODO close queue when devtools disconnects
|
@@ -69,13 +42,8 @@ export const bootDevtools = (options: DevtoolsOptions) =>
|
|
69
42
|
Effect.gen(function* () {
|
70
43
|
if (msg.payload._tag === 'upstream-advance') {
|
71
44
|
for (const mutationEventEncoded of msg.payload.newEvents) {
|
72
|
-
|
73
|
-
|
74
|
-
mutationEventEncoded,
|
75
|
-
|
76
|
-
liveStoreVersion,
|
77
|
-
}),
|
78
|
-
)
|
45
|
+
// TODO refactor with push semantics
|
46
|
+
yield* sendMessage(Devtools.MutationBroadcast.make({ mutationEventEncoded, liveStoreVersion }))
|
79
47
|
}
|
80
48
|
} else {
|
81
49
|
yield* Effect.logWarning('TODO implement rebases in devtools')
|
@@ -86,68 +54,25 @@ export const bootDevtools = (options: DevtoolsOptions) =>
|
|
86
54
|
Effect.forkScoped,
|
87
55
|
)
|
88
56
|
|
89
|
-
yield* devtoolsCoordinatorChannel.listen.pipe(
|
90
|
-
Stream.flatten(),
|
91
|
-
// Stream.tapLogWithLabel('@livestore/common:leader-thread:devtools:onPortMessage'),
|
92
|
-
Stream.tap((msg) =>
|
93
|
-
Effect.gen(function* () {
|
94
|
-
// yield* Effect.logDebug(`[@livestore/common:leader-thread:devtools] message from port: ${msg._tag}`, msg)
|
95
|
-
// if (msg._tag === 'LSD.MessagePortForStoreRes') {
|
96
|
-
// yield* Deferred.succeed(storeMessagePortDeferred, msg.port)
|
97
|
-
// } else {
|
98
|
-
yield* PubSub.publish(incomingMessagesPubSub, msg)
|
99
|
-
// }
|
100
|
-
}),
|
101
|
-
),
|
102
|
-
Stream.runDrain,
|
103
|
-
Effect.withSpan(`@livestore/common:leader-thread:devtools:onPortMessage`),
|
104
|
-
Effect.ignoreLogged,
|
105
|
-
Effect.forkScoped,
|
106
|
-
)
|
107
|
-
|
108
|
-
// yield* sendMessage(Devtools.AppHostReady.make({ appHostId, liveStoreVersion, isLeader }), { force: true })
|
109
|
-
|
110
|
-
// yield* sendMessage(Devtools.MessagePortForStoreReq.make({ appHostId, liveStoreVersion, requestId: nanoid() }), {
|
111
|
-
// force: true,
|
112
|
-
// })
|
113
|
-
|
114
57
|
yield* listenToDevtools({
|
115
|
-
incomingMessages,
|
58
|
+
incomingMessages: devtoolsWebChannel.listen.pipe(Stream.flatten(), Stream.orDie),
|
116
59
|
sendMessage,
|
117
|
-
// isConnected,
|
118
|
-
// disconnect,
|
119
|
-
// storeId,
|
120
|
-
// appHostId,
|
121
|
-
// isLeader,
|
122
60
|
persistenceInfo,
|
123
|
-
|
124
|
-
})
|
61
|
+
}).pipe(Effect.tapCauseLogPretty, Effect.forkScoped)
|
125
62
|
}).pipe(Effect.withSpan('@livestore/common:leader-thread:devtools:boot'))
|
126
63
|
|
127
64
|
const listenToDevtools = ({
|
128
65
|
incomingMessages,
|
129
66
|
sendMessage,
|
130
|
-
// isConnected,
|
131
|
-
// disconnect,
|
132
|
-
// appHostId,
|
133
|
-
// storeId,
|
134
|
-
// isLeader,
|
135
67
|
persistenceInfo,
|
136
|
-
shutdownChannel,
|
137
68
|
}: {
|
138
69
|
incomingMessages: Stream.Stream<Devtools.MessageToAppLeader>
|
139
70
|
sendMessage: SendMessageToDevtools
|
140
|
-
|
141
|
-
// disconnect: Effect.Effect<void>
|
142
|
-
// appHostId: string
|
143
|
-
// storeId: string
|
144
|
-
// isLeader: boolean
|
145
|
-
persistenceInfo: PersistenceInfoPair
|
146
|
-
shutdownChannel: ShutdownChannel
|
71
|
+
persistenceInfo?: PersistenceInfoPair
|
147
72
|
}) =>
|
148
73
|
Effect.gen(function* () {
|
149
|
-
const
|
150
|
-
|
74
|
+
const { syncBackend, makeSyncDb, db, dbLog, shutdownStateSubRef, shutdownChannel, syncProcessor } =
|
75
|
+
yield* LeaderThreadCtx
|
151
76
|
|
152
77
|
type RequestId = string
|
153
78
|
const subscriptionFiberMap = yield* FiberMap.make<RequestId>()
|
@@ -158,15 +83,6 @@ const listenToDevtools = ({
|
|
158
83
|
// yield* Effect.logDebug('[@livestore/common:leader-thread:devtools] incomingMessage', decodedEvent)
|
159
84
|
|
160
85
|
if (decodedEvent._tag === 'LSD.Disconnect') {
|
161
|
-
// yield* SubscriptionRef.set(isConnected, false)
|
162
|
-
|
163
|
-
// yield* disconnect
|
164
|
-
|
165
|
-
// TODO is there a better place for this?
|
166
|
-
// yield* sendMessage(Devtools.AppHostReady.make({ appHostId, liveStoreVersion, isLeader }), {
|
167
|
-
// force: true,
|
168
|
-
// })
|
169
|
-
|
170
86
|
return
|
171
87
|
}
|
172
88
|
|
@@ -226,7 +142,7 @@ const listenToDevtools = ({
|
|
226
142
|
|
227
143
|
yield* sendMessage(Devtools.LoadDatabaseFileRes.make({ ...reqPayload, status: 'ok' }))
|
228
144
|
|
229
|
-
yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-import' }))
|
145
|
+
yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-import' })) ?? Effect.void
|
230
146
|
|
231
147
|
return
|
232
148
|
}
|
@@ -243,11 +159,16 @@ const listenToDevtools = ({
|
|
243
159
|
|
244
160
|
yield* sendMessage(Devtools.ResetAllDataRes.make({ ...reqPayload }))
|
245
161
|
|
246
|
-
yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-reset' }))
|
162
|
+
yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-reset' })) ?? Effect.void
|
247
163
|
|
248
164
|
return
|
249
165
|
}
|
250
166
|
case 'LSD.Leader.DatabaseFileInfoReq': {
|
167
|
+
if (persistenceInfo === undefined) {
|
168
|
+
console.log('[@livestore/common:leader-thread:devtools] persistenceInfo is required for this request')
|
169
|
+
return
|
170
|
+
}
|
171
|
+
|
251
172
|
const dbSizeQuery = `SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size();`
|
252
173
|
const dbFileSize = db.select<{ size: number }>(dbSizeQuery, undefined)[0]!.size
|
253
174
|
const mutationLogFileSize = dbLog.select<{ size: number }>(dbSizeQuery, undefined)[0]!.size
|
@@ -1,8 +1,9 @@
|
|
1
|
-
import type { HttpClient, Scope
|
2
|
-
import { Deferred, Effect,
|
1
|
+
import type { HttpClient, Scope } from '@livestore/utils/effect'
|
2
|
+
import { Deferred, Effect, Layer, Queue, SubscriptionRef } from '@livestore/utils/effect'
|
3
3
|
|
4
4
|
import type { BootStatus, MakeSynchronousDatabase, SqliteError, SynchronousDatabase } from '../adapter-types.js'
|
5
5
|
import { UnexpectedError } from '../adapter-types.js'
|
6
|
+
import type * as Devtools from '../devtools/index.js'
|
6
7
|
import type { LiveStoreSchema } from '../schema/mod.js'
|
7
8
|
import { EventId, MutationEvent, mutationLogMetaTable, SYNC_STATUS_TABLE, syncStatusTable } from '../schema/mod.js'
|
8
9
|
import { migrateTable } from '../schema-management/migrations.js'
|
@@ -13,22 +14,24 @@ import { makeLeaderSyncProcessor } from './leader-sync-processor.js'
|
|
13
14
|
import { bootDevtools } from './leader-worker-devtools.js'
|
14
15
|
import { makePullQueueSet } from './pull-queue-set.js'
|
15
16
|
import { recreateDb } from './recreate-db.js'
|
17
|
+
import type { ShutdownChannel } from './shutdown-channel.js'
|
16
18
|
import type { DevtoolsOptions, InitialBlockingSyncContext, InitialSyncOptions, ShutdownState } from './types.js'
|
17
19
|
import { LeaderThreadCtx } from './types.js'
|
18
20
|
|
19
21
|
export const makeLeaderThreadLayer = ({
|
20
22
|
schema,
|
21
23
|
storeId,
|
22
|
-
|
24
|
+
clientId,
|
23
25
|
makeSyncDb,
|
24
26
|
makeSyncBackend,
|
25
27
|
db,
|
26
28
|
dbLog,
|
27
29
|
devtoolsOptions,
|
28
30
|
initialSyncOptions = { _tag: 'Skip' },
|
31
|
+
shutdownChannel,
|
29
32
|
}: {
|
30
33
|
storeId: string
|
31
|
-
|
34
|
+
clientId: string
|
32
35
|
schema: LiveStoreSchema
|
33
36
|
makeSyncDb: MakeSynchronousDatabase
|
34
37
|
makeSyncBackend: Effect.Effect<SyncBackend, UnexpectedError, Scope.Scope> | undefined
|
@@ -36,6 +39,7 @@ export const makeLeaderThreadLayer = ({
|
|
36
39
|
dbLog: SynchronousDatabase
|
37
40
|
devtoolsOptions: DevtoolsOptions
|
38
41
|
initialSyncOptions: InitialSyncOptions | undefined
|
42
|
+
shutdownChannel: ShutdownChannel
|
39
43
|
}): Layer.Layer<LeaderThreadCtx, UnexpectedError, Scope.Scope | HttpClient.HttpClient> =>
|
40
44
|
Effect.gen(function* () {
|
41
45
|
const bootStatusQueue = yield* Queue.unbounded<BootStatus>().pipe(Effect.acquireRelease(Queue.shutdown))
|
@@ -50,19 +54,25 @@ export const makeLeaderThreadLayer = ({
|
|
50
54
|
|
51
55
|
const syncProcessor = yield* makeLeaderSyncProcessor({ schema, dbMissing, dbLog, initialBlockingSyncContext })
|
52
56
|
|
57
|
+
const extraIncomingMessagesQueue = yield* Queue.unbounded<Devtools.MessageToAppLeader>().pipe(
|
58
|
+
Effect.acquireRelease(Queue.shutdown),
|
59
|
+
)
|
60
|
+
|
53
61
|
const ctx = {
|
54
62
|
schema,
|
55
63
|
bootStatusQueue,
|
56
64
|
storeId,
|
57
|
-
|
65
|
+
clientId,
|
58
66
|
db,
|
59
67
|
dbLog,
|
60
68
|
makeSyncDb,
|
61
69
|
mutationEventSchema: MutationEvent.makeMutationEventSchema(schema),
|
62
70
|
shutdownStateSubRef: yield* SubscriptionRef.make<ShutdownState>('running'),
|
71
|
+
shutdownChannel,
|
63
72
|
syncBackend,
|
64
73
|
syncProcessor,
|
65
74
|
connectedClientSessionPullQueues: yield* makePullQueueSet,
|
75
|
+
extraIncomingMessagesQueue,
|
66
76
|
} satisfies typeof LeaderThreadCtx.Service
|
67
77
|
|
68
78
|
// @ts-expect-error For debugging purposes
|
@@ -2,11 +2,14 @@ import { Effect, Schema } from '@livestore/utils/effect'
|
|
2
2
|
|
3
3
|
import type { SynchronousDatabase } from '../adapter-types.js'
|
4
4
|
import * as EventId from '../schema/EventId.js'
|
5
|
+
import type * as MutationEvent from '../schema/MutationEvent.js'
|
5
6
|
import { MUTATION_LOG_META_TABLE, mutationLogMetaTable, SYNC_STATUS_TABLE } from '../schema/system-tables.js'
|
6
7
|
import { prepareBindValues, sql } from '../util.js'
|
7
8
|
import { LeaderThreadCtx } from './types.js'
|
8
9
|
|
9
|
-
export const getMutationEventsSince = (
|
10
|
+
export const getMutationEventsSince = (
|
11
|
+
since: EventId.EventId,
|
12
|
+
): Effect.Effect<ReadonlyArray<MutationEvent.AnyEncoded>, never, LeaderThreadCtx> =>
|
10
13
|
Effect.gen(function* () {
|
11
14
|
const { dbLog } = yield* LeaderThreadCtx
|
12
15
|
|
@@ -26,16 +29,17 @@ export const getMutationEventsSince = (since: EventId.EventId) =>
|
|
26
29
|
.filter((_) => EventId.compare(_.id, since) > 0)
|
27
30
|
})
|
28
31
|
|
29
|
-
export const getLocalHeadFromDb = (dbLog: SynchronousDatabase) => {
|
30
|
-
const res = dbLog.select<{ idGlobal:
|
32
|
+
export const getLocalHeadFromDb = (dbLog: SynchronousDatabase): EventId.EventId => {
|
33
|
+
const res = dbLog.select<{ idGlobal: EventId.GlobalEventId; idLocal: EventId.LocalEventId }>(
|
31
34
|
sql`select idGlobal, idLocal from ${MUTATION_LOG_META_TABLE} order by idGlobal DESC, idLocal DESC limit 1`,
|
32
35
|
)[0]
|
33
36
|
|
34
37
|
return res ? { global: res.idGlobal, local: res.idLocal } : EventId.ROOT
|
35
38
|
}
|
36
39
|
|
37
|
-
export const getBackendHeadFromDb = (dbLog: SynchronousDatabase) =>
|
38
|
-
dbLog.select<{ head:
|
40
|
+
export const getBackendHeadFromDb = (dbLog: SynchronousDatabase): EventId.GlobalEventId =>
|
41
|
+
dbLog.select<{ head: EventId.GlobalEventId }>(sql`select head from ${SYNC_STATUS_TABLE}`)[0]?.head ??
|
42
|
+
EventId.ROOT.global
|
39
43
|
|
40
44
|
// TODO use prepared statements
|
41
45
|
export const updateBackendHead = (dbLog: SynchronousDatabase, head: EventId.EventId) =>
|