@livestore/common 0.3.0-dev.19 → 0.3.0-dev.21
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 +4 -3
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js +1 -0
- package/dist/adapter-types.js.map +1 -1
- package/dist/debug-info.d.ts +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +41 -22
- package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-client-session.js +26 -7
- package/dist/devtools/devtools-messages-client-session.js.map +1 -1
- package/dist/devtools/devtools-messages-common.d.ts +16 -12
- package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-common.js +15 -5
- package/dist/devtools/devtools-messages-common.js.map +1 -1
- package/dist/devtools/devtools-messages-leader.d.ts +74 -106
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-leader.js +52 -37
- package/dist/devtools/devtools-messages-leader.js.map +1 -1
- package/dist/devtools/devtools-sessioninfo.d.ts +28 -0
- package/dist/devtools/devtools-sessioninfo.d.ts.map +1 -0
- package/dist/devtools/devtools-sessioninfo.js +34 -0
- package/dist/devtools/devtools-sessioninfo.js.map +1 -0
- package/dist/devtools/devtools-sessions-channel.d.ts +28 -0
- package/dist/devtools/devtools-sessions-channel.d.ts.map +1 -0
- package/dist/devtools/devtools-sessions-channel.js +34 -0
- package/dist/devtools/devtools-sessions-channel.js.map +1 -0
- package/dist/devtools/index.d.ts +27 -49
- package/dist/devtools/index.d.ts.map +1 -1
- package/dist/devtools/index.js +10 -55
- package/dist/devtools/index.js.map +1 -1
- package/dist/devtools/mod.d.ts +39 -0
- package/dist/devtools/mod.d.ts.map +1 -0
- package/dist/devtools/mod.js +27 -0
- package/dist/devtools/mod.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +73 -31
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/otel.d.ts +2 -0
- package/dist/otel.d.ts.map +1 -1
- package/dist/otel.js +5 -0
- package/dist/otel.js.map +1 -1
- package/dist/query-builder/impl.d.ts +3 -3
- package/dist/query-builder/impl.d.ts.map +1 -1
- package/dist/rehydrate-from-mutationlog.js.map +1 -1
- package/dist/schema/db-schema/dsl/field-defs.d.ts.map +1 -1
- package/dist/schema/db-schema/dsl/field-defs.js.map +1 -1
- package/dist/schema/db-schema/dsl/mod.js.map +1 -1
- package/dist/schema/system-tables.d.ts +2 -2
- package/dist/sync/ClientSessionSyncProcessor.d.ts +6 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +2 -2
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/util.d.ts +2 -2
- package/dist/util.d.ts.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 +2 -1
- package/src/devtools/devtools-messages-client-session.ts +26 -10
- package/src/devtools/devtools-messages-common.ts +38 -13
- package/src/devtools/devtools-messages-leader.ts +61 -46
- package/src/devtools/devtools-sessioninfo.ts +99 -0
- package/src/devtools/mod.ts +36 -0
- package/src/index.ts +1 -1
- package/src/leader-thread/leader-worker-devtools.ts +83 -36
- package/src/leader-thread/make-leader-thread-layer.ts +1 -1
- package/src/otel.ts +8 -0
- package/src/rehydrate-from-mutationlog.ts +1 -1
- package/src/schema/db-schema/dsl/field-defs.ts +2 -1
- package/src/schema/db-schema/dsl/mod.ts +1 -1
- package/src/sync/ClientSessionSyncProcessor.ts +7 -1
- package/src/version.ts +1 -1
- package/dist/devtools/devtools-bridge.d.ts +0 -16
- package/dist/devtools/devtools-bridge.d.ts.map +0 -1
- package/dist/devtools/devtools-bridge.js +0 -2
- package/dist/devtools/devtools-bridge.js.map +0 -1
- package/src/devtools/devtools-bridge.ts +0 -17
- package/src/devtools/index.ts +0 -76
@@ -1,16 +1,10 @@
|
|
1
1
|
import { Schema, Transferable } from '@livestore/utils/effect'
|
2
2
|
|
3
|
-
import { NetworkStatus
|
3
|
+
import { NetworkStatus } from '../adapter-types.js'
|
4
4
|
import { EventId } from '../schema/mod.js'
|
5
5
|
import * as MutationEvent from '../schema/MutationEvent.js'
|
6
6
|
import * as SyncState from '../sync/syncstate.js'
|
7
|
-
import {
|
8
|
-
LeaderReqResMessage,
|
9
|
-
liveStoreVersion,
|
10
|
-
LSDMessage,
|
11
|
-
LSDReqResMessage,
|
12
|
-
requestId,
|
13
|
-
} from './devtools-messages-common.js'
|
7
|
+
import { LeaderReqResMessage, LSDMessage, LSDReqResMessage } from './devtools-messages-common.js'
|
14
8
|
|
15
9
|
export class ResetAllDataReq extends LSDReqResMessage('LSD.Leader.ResetAllDataReq', {
|
16
10
|
mode: Schema.Literal('all-data', 'only-app-db'),
|
@@ -28,11 +22,16 @@ export class DatabaseFileInfoRes extends LSDReqResMessage('LSD.Leader.DatabaseFi
|
|
28
22
|
mutationLog: DatabaseFileInfo,
|
29
23
|
}) {}
|
30
24
|
|
31
|
-
export class NetworkStatusSubscribe extends LSDReqResMessage('LSD.Leader.NetworkStatusSubscribe', {
|
32
|
-
|
25
|
+
export class NetworkStatusSubscribe extends LSDReqResMessage('LSD.Leader.NetworkStatusSubscribe', {
|
26
|
+
subscriptionId: Schema.String,
|
27
|
+
}) {}
|
28
|
+
export class NetworkStatusUnsubscribe extends LSDReqResMessage('LSD.Leader.NetworkStatusUnsubscribe', {
|
29
|
+
subscriptionId: Schema.String,
|
30
|
+
}) {}
|
33
31
|
|
34
32
|
export class NetworkStatusRes extends LSDReqResMessage('LSD.Leader.NetworkStatusRes', {
|
35
33
|
networkStatus: NetworkStatus,
|
34
|
+
subscriptionId: Schema.String,
|
36
35
|
}) {}
|
37
36
|
|
38
37
|
export class SyncingInfoReq extends LSDReqResMessage('LSD.Leader.SyncingInfoReq', {}) {}
|
@@ -46,18 +45,28 @@ export class SyncingInfoRes extends LSDReqResMessage('LSD.Leader.SyncingInfoRes'
|
|
46
45
|
syncingInfo: SyncingInfo,
|
47
46
|
}) {}
|
48
47
|
|
49
|
-
export class SyncHistorySubscribe extends LSDReqResMessage('LSD.Leader.SyncHistorySubscribe', {
|
50
|
-
|
48
|
+
export class SyncHistorySubscribe extends LSDReqResMessage('LSD.Leader.SyncHistorySubscribe', {
|
49
|
+
subscriptionId: Schema.String,
|
50
|
+
}) {}
|
51
|
+
export class SyncHistoryUnsubscribe extends LSDReqResMessage('LSD.Leader.SyncHistoryUnsubscribe', {
|
52
|
+
subscriptionId: Schema.String,
|
53
|
+
}) {}
|
51
54
|
export class SyncHistoryRes extends LSDReqResMessage('LSD.Leader.SyncHistoryRes', {
|
52
55
|
mutationEventEncoded: MutationEvent.AnyEncodedGlobal,
|
53
56
|
metadata: Schema.Option(Schema.JsonValue),
|
57
|
+
subscriptionId: Schema.String,
|
54
58
|
}) {}
|
55
59
|
|
56
|
-
export class SyncHeadSubscribe extends LSDReqResMessage('LSD.Leader.SyncHeadSubscribe', {
|
57
|
-
|
60
|
+
export class SyncHeadSubscribe extends LSDReqResMessage('LSD.Leader.SyncHeadSubscribe', {
|
61
|
+
subscriptionId: Schema.String,
|
62
|
+
}) {}
|
63
|
+
export class SyncHeadUnsubscribe extends LSDReqResMessage('LSD.Leader.SyncHeadUnsubscribe', {
|
64
|
+
subscriptionId: Schema.String,
|
65
|
+
}) {}
|
58
66
|
export class SyncHeadRes extends LSDReqResMessage('LSD.Leader.SyncHeadRes', {
|
59
67
|
local: EventId.EventId,
|
60
68
|
upstream: EventId.EventId,
|
69
|
+
subscriptionId: Schema.String,
|
61
70
|
}) {}
|
62
71
|
|
63
72
|
export class SnapshotReq extends LSDReqResMessage('LSD.Leader.SnapshotReq', {}) {}
|
@@ -66,13 +75,19 @@ export class SnapshotRes extends LSDReqResMessage('LSD.Leader.SnapshotRes', {
|
|
66
75
|
snapshot: Transferable.Uint8Array,
|
67
76
|
}) {}
|
68
77
|
|
69
|
-
export
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
78
|
+
export const LoadDatabaseFile = LeaderReqResMessage('LSD.Leader.LoadDatabaseFile', {
|
79
|
+
payload: {
|
80
|
+
data: Transferable.Uint8Array,
|
81
|
+
},
|
82
|
+
success: {},
|
83
|
+
error: {
|
84
|
+
cause: Schema.Union(
|
85
|
+
Schema.TaggedStruct('unsupported-file', {}),
|
86
|
+
Schema.TaggedStruct('unsupported-database', {}),
|
87
|
+
Schema.TaggedStruct('unexpected-error', { cause: Schema.Defect }),
|
88
|
+
),
|
89
|
+
},
|
90
|
+
})
|
76
91
|
|
77
92
|
// TODO refactor this to use push/pull semantics
|
78
93
|
export class SyncPull extends LSDMessage('LSD.Leader.SyncPull', {
|
@@ -113,32 +128,32 @@ export const ResetAllData = LeaderReqResMessage('LSD.Leader.ResetAllData', {
|
|
113
128
|
})
|
114
129
|
|
115
130
|
// TODO move to `Schema.TaggedRequest` once new RPC is ready https://github.com/Effect-TS/effect/pull/4362
|
116
|
-
export class DatabaseFileInfo_ extends Schema.TaggedRequest<DatabaseFileInfo_>()('LSD.Leader.DatabaseFileInfo', {
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
}) {}
|
124
|
-
|
125
|
-
export class NetworkStatus_ extends Schema.TaggedRequest<NetworkStatus_>()('LSD.Leader.NetworkStatus', {
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
}) {}
|
133
|
-
|
134
|
-
export const MessageToApp_ = Schema.Union(DatabaseFileInfo_, NetworkStatus_)
|
135
|
-
|
136
|
-
export type MessageToApp_ = typeof MessageToApp_.Type
|
131
|
+
// export class DatabaseFileInfo_ extends Schema.TaggedRequest<DatabaseFileInfo_>()('LSD.Leader.DatabaseFileInfo', {
|
132
|
+
// payload: {
|
133
|
+
// requestId,
|
134
|
+
// liveStoreVersion,
|
135
|
+
// },
|
136
|
+
// success: DatabaseFileInfo,
|
137
|
+
// failure: UnexpectedError,
|
138
|
+
// }) {}
|
139
|
+
|
140
|
+
// export class NetworkStatus_ extends Schema.TaggedRequest<NetworkStatus_>()('LSD.Leader.NetworkStatus', {
|
141
|
+
// payload: {
|
142
|
+
// requestId,
|
143
|
+
// liveStoreVersion,
|
144
|
+
// },
|
145
|
+
// success: NetworkStatus,
|
146
|
+
// failure: UnexpectedError,
|
147
|
+
// }) {}
|
148
|
+
|
149
|
+
// export const MessageToApp_ = Schema.Union(DatabaseFileInfo_, NetworkStatus_)
|
150
|
+
|
151
|
+
// export type MessageToApp_ = typeof MessageToApp_.Type
|
137
152
|
//
|
138
153
|
|
139
154
|
export const MessageToApp = Schema.Union(
|
140
155
|
SnapshotReq,
|
141
|
-
|
156
|
+
LoadDatabaseFile.Request,
|
142
157
|
MutationLogReq,
|
143
158
|
ResetAllData.Request,
|
144
159
|
NetworkStatusSubscribe,
|
@@ -159,7 +174,7 @@ export type MessageToApp = typeof MessageToApp.Type
|
|
159
174
|
|
160
175
|
export const MessageFromApp = Schema.Union(
|
161
176
|
SnapshotRes,
|
162
|
-
|
177
|
+
LoadDatabaseFile.Response,
|
163
178
|
MutationLogRes,
|
164
179
|
Disconnect,
|
165
180
|
SyncPull,
|
@@ -170,8 +185,8 @@ export const MessageFromApp = Schema.Union(
|
|
170
185
|
SyncHistoryRes,
|
171
186
|
SyncingInfoRes,
|
172
187
|
SyncHeadRes,
|
173
|
-
ResetAllData.
|
174
|
-
SetSyncLatch.
|
188
|
+
ResetAllData.Success,
|
189
|
+
SetSyncLatch.Success,
|
175
190
|
).annotations({ identifier: 'LSD.Leader.MessageFromApp' })
|
176
191
|
|
177
192
|
export type MessageFromApp = typeof MessageFromApp.Type
|
@@ -0,0 +1,99 @@
|
|
1
|
+
import type { ParseResult, Scope, WebChannel } from '@livestore/utils/effect'
|
2
|
+
import {
|
3
|
+
Data,
|
4
|
+
Duration,
|
5
|
+
Effect,
|
6
|
+
FiberMap,
|
7
|
+
HashSet,
|
8
|
+
Schedule,
|
9
|
+
Schema,
|
10
|
+
Stream,
|
11
|
+
Subscribable,
|
12
|
+
SubscriptionRef,
|
13
|
+
} from '@livestore/utils/effect'
|
14
|
+
|
15
|
+
export const RequestSessions = Schema.TaggedStruct('RequestSessions', {})
|
16
|
+
export type RequestSessions = typeof RequestSessions.Type
|
17
|
+
|
18
|
+
export const SessionInfo = Schema.TaggedStruct('SessionInfo', {
|
19
|
+
storeId: Schema.String,
|
20
|
+
clientId: Schema.String,
|
21
|
+
sessionId: Schema.String,
|
22
|
+
})
|
23
|
+
export type SessionInfo = typeof SessionInfo.Type
|
24
|
+
|
25
|
+
export const Message = Schema.Union(RequestSessions, SessionInfo)
|
26
|
+
export type Message = typeof Message.Type
|
27
|
+
|
28
|
+
/** Usually called in client session */
|
29
|
+
export const provideSessionInfo = ({
|
30
|
+
webChannel,
|
31
|
+
sessionInfo,
|
32
|
+
}: {
|
33
|
+
webChannel: WebChannel.WebChannel<Message, Message>
|
34
|
+
sessionInfo: SessionInfo
|
35
|
+
}): Effect.Effect<void, ParseResult.ParseError> =>
|
36
|
+
Effect.gen(function* () {
|
37
|
+
yield* webChannel.send(sessionInfo)
|
38
|
+
|
39
|
+
yield* webChannel.listen.pipe(
|
40
|
+
Stream.flatten(),
|
41
|
+
Stream.filter(Schema.is(RequestSessions)),
|
42
|
+
Stream.tap(() => webChannel.send(sessionInfo)),
|
43
|
+
Stream.runDrain,
|
44
|
+
)
|
45
|
+
})
|
46
|
+
|
47
|
+
/** Usually called in devtools */
|
48
|
+
export const requestSessionInfoSubscription = ({
|
49
|
+
webChannel,
|
50
|
+
pollInterval = Duration.seconds(1),
|
51
|
+
staleTimeout = Duration.seconds(5),
|
52
|
+
}: {
|
53
|
+
webChannel: WebChannel.WebChannel<Message, Message>
|
54
|
+
pollInterval?: Duration.DurationInput
|
55
|
+
staleTimeout?: Duration.DurationInput
|
56
|
+
}): Effect.Effect<Subscribable.Subscribable<Set<SessionInfo>>, ParseResult.ParseError, Scope.Scope> =>
|
57
|
+
Effect.gen(function* () {
|
58
|
+
yield* webChannel
|
59
|
+
.send(RequestSessions.make({}))
|
60
|
+
.pipe(
|
61
|
+
Effect.repeat(Schedule.spaced(pollInterval)),
|
62
|
+
Effect.interruptible,
|
63
|
+
Effect.tapCauseLogPretty,
|
64
|
+
Effect.forkScoped,
|
65
|
+
)
|
66
|
+
|
67
|
+
const timeoutFiberMap = yield* FiberMap.make<SessionInfo>()
|
68
|
+
|
69
|
+
const sessionInfoSubRef = yield* SubscriptionRef.make<HashSet.HashSet<SessionInfo>>(HashSet.empty())
|
70
|
+
|
71
|
+
yield* webChannel.listen.pipe(
|
72
|
+
Stream.flatten(),
|
73
|
+
Stream.filter(Schema.is(SessionInfo)),
|
74
|
+
Stream.map(Data.struct),
|
75
|
+
Stream.tap(
|
76
|
+
Effect.fn(function* (sessionInfo) {
|
77
|
+
yield* SubscriptionRef.getAndUpdate(sessionInfoSubRef, HashSet.add(sessionInfo))
|
78
|
+
|
79
|
+
// Remove sessionInfo from cache after staleTimeout (unless a new identical item resets the timeout)
|
80
|
+
yield* FiberMap.run(
|
81
|
+
timeoutFiberMap,
|
82
|
+
sessionInfo,
|
83
|
+
Effect.gen(function* () {
|
84
|
+
yield* Effect.sleep(staleTimeout)
|
85
|
+
yield* SubscriptionRef.getAndUpdate(sessionInfoSubRef, HashSet.remove(sessionInfo))
|
86
|
+
}),
|
87
|
+
)
|
88
|
+
}),
|
89
|
+
),
|
90
|
+
Stream.runDrain,
|
91
|
+
Effect.tapCauseLogPretty,
|
92
|
+
Effect.forkScoped,
|
93
|
+
)
|
94
|
+
|
95
|
+
return Subscribable.make({
|
96
|
+
get: sessionInfoSubRef.get.pipe(Effect.map((sessionInfos) => new Set(sessionInfos))),
|
97
|
+
changes: sessionInfoSubRef.changes.pipe(Stream.map((sessionInfos) => new Set(sessionInfos))),
|
98
|
+
})
|
99
|
+
})
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import { Schema } from '@livestore/utils/effect'
|
2
|
+
|
3
|
+
export * from './devtools-messages.js'
|
4
|
+
export * from './devtools-window-message.js'
|
5
|
+
export * as SessionInfo from './devtools-sessioninfo.js'
|
6
|
+
export const ClientSessionInfo = Schema.Struct({
|
7
|
+
storeId: Schema.String,
|
8
|
+
clientId: Schema.String,
|
9
|
+
sessionId: Schema.String,
|
10
|
+
})
|
11
|
+
|
12
|
+
export const DevtoolsMode = Schema.Union(
|
13
|
+
Schema.TaggedStruct('expo', {
|
14
|
+
// TODO get rid of embedded `clientSessionInfo`
|
15
|
+
clientSessionInfo: Schema.optional(ClientSessionInfo),
|
16
|
+
}),
|
17
|
+
// TODO add storeId, clientId and sessionId for Node
|
18
|
+
Schema.TaggedStruct('node', {
|
19
|
+
// TODO get rid of embedded `clientSessionInfo`
|
20
|
+
clientSessionInfo: Schema.UndefinedOr(ClientSessionInfo),
|
21
|
+
url: Schema.String,
|
22
|
+
}),
|
23
|
+
Schema.TaggedStruct('web', {
|
24
|
+
// TODO get rid of embedded `clientSessionInfo`
|
25
|
+
clientSessionInfo: Schema.UndefinedOr(ClientSessionInfo),
|
26
|
+
}),
|
27
|
+
Schema.TaggedStruct('browser-extension', {
|
28
|
+
// TODO get rid of embedded `clientSessionInfo`
|
29
|
+
clientSessionInfo: Schema.UndefinedOr(ClientSessionInfo),
|
30
|
+
}),
|
31
|
+
)
|
32
|
+
|
33
|
+
export type DevtoolsMode = typeof DevtoolsMode.Type
|
34
|
+
|
35
|
+
export const DevtoolsModeTag = DevtoolsMode.pipe(Schema.pluck('_tag'), Schema.typeSchema)
|
36
|
+
export type DevtoolsModeTag = typeof DevtoolsModeTag.Type
|
package/src/index.ts
CHANGED
@@ -8,7 +8,7 @@ export * from './rehydrate-from-mutationlog.js'
|
|
8
8
|
export * from './query-info.js'
|
9
9
|
export * from './derived-mutations.js'
|
10
10
|
export * from './sync/index.js'
|
11
|
-
export * as Devtools from './devtools/
|
11
|
+
export * as Devtools from './devtools/mod.js'
|
12
12
|
export * from './debug-info.js'
|
13
13
|
export * from './bounded-collections.js'
|
14
14
|
export * from './version.js'
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { Effect, FiberMap, Option, Stream, SubscriptionRef } from '@livestore/utils/effect'
|
2
|
+
import { nanoid } from '@livestore/utils/nanoid'
|
2
3
|
|
3
4
|
import { Devtools, IntentionalShutdownCause, liveStoreVersion, UnexpectedError } from '../index.js'
|
4
5
|
import { MUTATION_LOG_META_TABLE, SCHEMA_META_TABLE, SCHEMA_MUTATIONS_META_TABLE } from '../schema/mod.js'
|
@@ -72,20 +73,37 @@ const listenToDevtools = ({
|
|
72
73
|
devtools,
|
73
74
|
} = yield* LeaderThreadCtx
|
74
75
|
|
76
|
+
type SubscriptionId = string
|
77
|
+
const subscriptionFiberMap = yield* FiberMap.make<SubscriptionId>()
|
78
|
+
|
75
79
|
type RequestId = string
|
76
|
-
const
|
80
|
+
const handledRequestIds = new Set<RequestId>()
|
77
81
|
|
78
82
|
yield* incomingMessages.pipe(
|
79
83
|
Stream.tap((decodedEvent) =>
|
80
84
|
Effect.gen(function* () {
|
81
|
-
|
85
|
+
const { requestId } = decodedEvent
|
86
|
+
const reqPayload = { requestId, liveStoreVersion, clientId }
|
87
|
+
|
88
|
+
// yield* Effect.logDebug(
|
89
|
+
// `[@livestore/common:leader-thread:devtools] incomingMessage: ${decodedEvent._tag} (${requestId})`,
|
90
|
+
// decodedEvent,
|
91
|
+
// )
|
82
92
|
|
83
93
|
if (decodedEvent._tag === 'LSD.Leader.Disconnect') {
|
84
94
|
return
|
85
95
|
}
|
86
96
|
|
87
|
-
|
88
|
-
|
97
|
+
// TODO we should try to move the duplicate message handling on the webmesh layer
|
98
|
+
// So far I could only observe this problem with webmesh proxy channels (e.g. for Expo)
|
99
|
+
// Proof: https://share.cleanshot.com/V9G87B0B
|
100
|
+
// Also see `store/devtools.ts` for same problem
|
101
|
+
if (handledRequestIds.has(requestId)) {
|
102
|
+
// yield* Effect.logWarning(`Duplicate message`, decodedEvent)
|
103
|
+
return
|
104
|
+
}
|
105
|
+
|
106
|
+
handledRequestIds.add(requestId)
|
89
107
|
|
90
108
|
switch (decodedEvent._tag) {
|
91
109
|
case 'LSD.Leader.Ping': {
|
@@ -99,7 +117,7 @@ const listenToDevtools = ({
|
|
99
117
|
|
100
118
|
return
|
101
119
|
}
|
102
|
-
case 'LSD.Leader.
|
120
|
+
case 'LSD.Leader.LoadDatabaseFile.Request': {
|
103
121
|
const { data } = decodedEvent
|
104
122
|
|
105
123
|
let tableNames: Set<string>
|
@@ -114,39 +132,57 @@ const listenToDevtools = ({
|
|
114
132
|
tableNames = new Set(tableNameResults.map((_) => _.name))
|
115
133
|
|
116
134
|
tmpDb.close()
|
117
|
-
} catch (
|
118
|
-
yield* Effect.logError(`Error importing database file`,
|
135
|
+
} catch (cause) {
|
136
|
+
yield* Effect.logError(`Error importing database file`, cause)
|
119
137
|
yield* sendMessage(
|
120
|
-
Devtools.Leader.
|
138
|
+
Devtools.Leader.LoadDatabaseFile.Error.make({
|
139
|
+
...reqPayload,
|
140
|
+
cause: { _tag: 'unexpected-error', cause },
|
141
|
+
}),
|
121
142
|
)
|
122
143
|
|
123
144
|
return
|
124
145
|
}
|
125
146
|
|
126
|
-
|
127
|
-
|
147
|
+
try {
|
148
|
+
if (tableNames.has(MUTATION_LOG_META_TABLE)) {
|
149
|
+
// Is mutation log
|
150
|
+
yield* SubscriptionRef.set(shutdownStateSubRef, 'shutting-down')
|
128
151
|
|
129
|
-
|
152
|
+
dbMutationLog.import(data)
|
130
153
|
|
131
|
-
|
132
|
-
|
133
|
-
|
154
|
+
dbReadModel.destroy()
|
155
|
+
} else if (tableNames.has(SCHEMA_META_TABLE) && tableNames.has(SCHEMA_MUTATIONS_META_TABLE)) {
|
156
|
+
// Is read model
|
157
|
+
yield* SubscriptionRef.set(shutdownStateSubRef, 'shutting-down')
|
134
158
|
|
135
|
-
|
159
|
+
dbReadModel.import(data)
|
136
160
|
|
137
|
-
|
138
|
-
|
161
|
+
dbMutationLog.destroy()
|
162
|
+
} else {
|
163
|
+
yield* sendMessage(
|
164
|
+
Devtools.Leader.LoadDatabaseFile.Error.make({
|
165
|
+
...reqPayload,
|
166
|
+
cause: { _tag: 'unsupported-database' },
|
167
|
+
}),
|
168
|
+
)
|
169
|
+
return
|
170
|
+
}
|
171
|
+
|
172
|
+
yield* sendMessage(Devtools.Leader.LoadDatabaseFile.Success.make({ ...reqPayload }))
|
173
|
+
yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-import' })) ?? Effect.void
|
174
|
+
|
175
|
+
return
|
176
|
+
} catch (cause) {
|
177
|
+
yield* Effect.logError(`Error importing database file`, cause)
|
139
178
|
yield* sendMessage(
|
140
|
-
Devtools.Leader.
|
179
|
+
Devtools.Leader.LoadDatabaseFile.Error.make({
|
180
|
+
...reqPayload,
|
181
|
+
cause: { _tag: 'unexpected-error', cause },
|
182
|
+
}),
|
141
183
|
)
|
142
184
|
return
|
143
185
|
}
|
144
|
-
|
145
|
-
yield* sendMessage(Devtools.Leader.LoadDatabaseFileRes.make({ ...reqPayload, status: 'ok' }))
|
146
|
-
|
147
|
-
yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-import' })) ?? Effect.void
|
148
|
-
|
149
|
-
return
|
150
186
|
}
|
151
187
|
case 'LSD.Leader.ResetAllData.Request': {
|
152
188
|
const { mode } = decodedEvent
|
@@ -159,7 +195,7 @@ const listenToDevtools = ({
|
|
159
195
|
dbMutationLog.destroy()
|
160
196
|
}
|
161
197
|
|
162
|
-
yield* sendMessage(Devtools.Leader.ResetAllData.
|
198
|
+
yield* sendMessage(Devtools.Leader.ResetAllData.Success.make({ ...reqPayload }))
|
163
199
|
|
164
200
|
yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-reset' })) ?? Effect.void
|
165
201
|
|
@@ -204,7 +240,7 @@ const listenToDevtools = ({
|
|
204
240
|
return
|
205
241
|
}
|
206
242
|
case 'LSD.Leader.SyncHistorySubscribe': {
|
207
|
-
const {
|
243
|
+
const { subscriptionId } = decodedEvent
|
208
244
|
|
209
245
|
if (syncBackend !== undefined) {
|
210
246
|
// TODO consider piggybacking on the existing leader-thread sync-pulling
|
@@ -212,13 +248,20 @@ const listenToDevtools = ({
|
|
212
248
|
Stream.map((_) => _.batch),
|
213
249
|
Stream.flattenIterables,
|
214
250
|
Stream.tap(({ mutationEventEncoded, metadata }) =>
|
215
|
-
sendMessage(
|
251
|
+
sendMessage(
|
252
|
+
Devtools.Leader.SyncHistoryRes.make({
|
253
|
+
mutationEventEncoded,
|
254
|
+
metadata,
|
255
|
+
subscriptionId,
|
256
|
+
...reqPayload,
|
257
|
+
requestId: nanoid(10),
|
258
|
+
}),
|
259
|
+
),
|
216
260
|
),
|
217
261
|
Stream.runDrain,
|
218
|
-
Effect.acquireRelease(() => Effect.log('syncHistorySubscribe done')),
|
219
262
|
Effect.interruptible,
|
220
263
|
Effect.tapCauseLogPretty,
|
221
|
-
FiberMap.run(subscriptionFiberMap,
|
264
|
+
FiberMap.run(subscriptionFiberMap, subscriptionId),
|
222
265
|
)
|
223
266
|
}
|
224
267
|
|
@@ -244,7 +287,7 @@ const listenToDevtools = ({
|
|
244
287
|
}
|
245
288
|
case 'LSD.Leader.NetworkStatusSubscribe': {
|
246
289
|
if (syncBackend !== undefined) {
|
247
|
-
const {
|
290
|
+
const { subscriptionId } = decodedEvent
|
248
291
|
|
249
292
|
// TODO investigate and fix bug. seems that when sending messages right after
|
250
293
|
// the devtools have connected get sometimes lost
|
@@ -259,14 +302,16 @@ const listenToDevtools = ({
|
|
259
302
|
sendMessage(
|
260
303
|
Devtools.Leader.NetworkStatusRes.make({
|
261
304
|
networkStatus: { isConnected, timestampMs: Date.now(), latchClosed },
|
305
|
+
subscriptionId,
|
262
306
|
...reqPayload,
|
307
|
+
requestId: nanoid(10),
|
263
308
|
}),
|
264
309
|
),
|
265
310
|
),
|
266
311
|
Stream.runDrain,
|
267
312
|
Effect.interruptible,
|
268
313
|
Effect.tapCauseLogPretty,
|
269
|
-
FiberMap.run(subscriptionFiberMap,
|
314
|
+
FiberMap.run(subscriptionFiberMap, subscriptionId),
|
270
315
|
)
|
271
316
|
}
|
272
317
|
|
@@ -280,7 +325,7 @@ const listenToDevtools = ({
|
|
280
325
|
return
|
281
326
|
}
|
282
327
|
case 'LSD.Leader.SyncHeadSubscribe': {
|
283
|
-
const {
|
328
|
+
const { subscriptionId } = decodedEvent
|
284
329
|
|
285
330
|
yield* syncProcessor.syncState.changes.pipe(
|
286
331
|
Stream.tap((syncState) =>
|
@@ -288,22 +333,24 @@ const listenToDevtools = ({
|
|
288
333
|
Devtools.Leader.SyncHeadRes.make({
|
289
334
|
local: syncState.localHead,
|
290
335
|
upstream: syncState.upstreamHead,
|
336
|
+
subscriptionId,
|
291
337
|
...reqPayload,
|
338
|
+
requestId: nanoid(10),
|
292
339
|
}),
|
293
340
|
),
|
294
341
|
),
|
295
342
|
Stream.runDrain,
|
296
343
|
Effect.interruptible,
|
297
344
|
Effect.tapCauseLogPretty,
|
298
|
-
FiberMap.run(subscriptionFiberMap,
|
345
|
+
FiberMap.run(subscriptionFiberMap, subscriptionId),
|
299
346
|
)
|
300
347
|
|
301
348
|
return
|
302
349
|
}
|
303
350
|
case 'LSD.Leader.SyncHeadUnsubscribe': {
|
304
|
-
const {
|
351
|
+
const { subscriptionId } = decodedEvent
|
305
352
|
|
306
|
-
yield* FiberMap.remove(subscriptionFiberMap,
|
353
|
+
yield* FiberMap.remove(subscriptionFiberMap, subscriptionId)
|
307
354
|
|
308
355
|
return
|
309
356
|
}
|
@@ -320,7 +367,7 @@ const listenToDevtools = ({
|
|
320
367
|
|
321
368
|
yield* SubscriptionRef.set(devtools.syncBackendLatchState, { latchClosed: closeLatch })
|
322
369
|
|
323
|
-
yield* sendMessage(Devtools.Leader.SetSyncLatch.
|
370
|
+
yield* sendMessage(Devtools.Leader.SetSyncLatch.Success.make({ ...reqPayload }))
|
324
371
|
|
325
372
|
return
|
326
373
|
}
|
@@ -3,7 +3,7 @@ import { Deferred, Effect, Layer, Queue, SubscriptionRef } from '@livestore/util
|
|
3
3
|
|
4
4
|
import type { BootStatus, MakeSqliteDb, MigrationsReport, SqliteError } from '../adapter-types.js'
|
5
5
|
import { UnexpectedError } from '../adapter-types.js'
|
6
|
-
import type * as Devtools from '../devtools/
|
6
|
+
import type * as Devtools from '../devtools/mod.js'
|
7
7
|
import type { LiveStoreSchema } from '../schema/mod.js'
|
8
8
|
import { EventId, MutationEvent, mutationLogMetaTable, SYNC_STATUS_TABLE, syncStatusTable } from '../schema/mod.js'
|
9
9
|
import { migrateTable } from '../schema-management/migrations.js'
|
package/src/otel.ts
CHANGED
@@ -18,3 +18,11 @@ export const provideOtel =
|
|
18
18
|
Effect.provide(TracingLive),
|
19
19
|
)
|
20
20
|
}
|
21
|
+
|
22
|
+
export const getDurationMsFromSpan = (span: otel.Span): number => {
|
23
|
+
const durationHr: [seconds: number, nanos: number] = (span as any)._duration
|
24
|
+
return durationHr[0] * 1000 + durationHr[1] / 1_000_000
|
25
|
+
}
|
26
|
+
|
27
|
+
export const getStartTimeHighResFromSpan = (span: otel.Span): DOMHighResTimeStamp =>
|
28
|
+
(span as any)._performanceStartTime as DOMHighResTimeStamp
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { memoizeByRef
|
1
|
+
import { memoizeByRef } from '@livestore/utils'
|
2
2
|
import { Chunk, Effect, Option, Schema, Stream } from '@livestore/utils/effect'
|
3
3
|
|
4
4
|
import { type MigrationOptionsFromMutationLog, type SqliteDb, UnexpectedError } from './adapter-types.js'
|
@@ -53,7 +53,8 @@ export type ColDefFn<TColumnType extends FieldColumnType> = {
|
|
53
53
|
const TNullable extends boolean = false,
|
54
54
|
const TDefault extends TDecoded | SqlDefaultValue | NoDefault | (TNullable extends true ? null : never) = NoDefault,
|
55
55
|
const TPrimaryKey extends boolean = false,
|
56
|
-
>(args: {
|
56
|
+
>(args: {
|
57
|
+
schema?: Schema.Schema<TDecoded, TEncoded>
|
57
58
|
default?: TDefault
|
58
59
|
nullable?: TNullable
|
59
60
|
primaryKey?: TPrimaryKey
|
@@ -30,7 +30,7 @@ export type DbSchemaFromInputSchema<TSchemaInput extends DbSchemaInput> =
|
|
30
30
|
export const makeDbSchema = <TDbSchemaInput extends DbSchemaInput>(
|
31
31
|
schema: TDbSchemaInput,
|
32
32
|
): DbSchemaFromInputSchema<TDbSchemaInput> => {
|
33
|
-
return Array.isArray(schema) ? Object.fromEntries(schema.map((_) => [_.name, _])) : schema as any
|
33
|
+
return Array.isArray(schema) ? Object.fromEntries(schema.map((_) => [_.name, _])) : (schema as any)
|
34
34
|
}
|
35
35
|
|
36
36
|
export const table = <TTableName extends string, TColumns extends Columns, TIndexes extends Index[]>(
|
@@ -27,6 +27,7 @@ export const makeClientSessionSyncProcessor = ({
|
|
27
27
|
refreshTables,
|
28
28
|
span,
|
29
29
|
params,
|
30
|
+
confirmUnsavedChanges,
|
30
31
|
}: {
|
31
32
|
schema: LiveStoreSchema
|
32
33
|
clientSession: ClientSession
|
@@ -44,6 +45,11 @@ export const makeClientSessionSyncProcessor = ({
|
|
44
45
|
params: {
|
45
46
|
leaderPushBatchSize: number
|
46
47
|
}
|
48
|
+
/**
|
49
|
+
* Currently only used in the web adapter:
|
50
|
+
* If true, registers a beforeunload event listener to confirm unsaved changes.
|
51
|
+
*/
|
52
|
+
confirmUnsavedChanges: boolean
|
47
53
|
}): ClientSessionSyncProcessor => {
|
48
54
|
const mutationEventSchema = MutationEvent.makeMutationEventSchemaMemo(schema)
|
49
55
|
|
@@ -132,7 +138,7 @@ export const makeClientSessionSyncProcessor = ({
|
|
132
138
|
|
133
139
|
const boot: ClientSessionSyncProcessor['boot'] = Effect.gen(function* () {
|
134
140
|
// eslint-disable-next-line unicorn/prefer-global-this
|
135
|
-
if (typeof window !== 'undefined' && typeof window.addEventListener === 'function') {
|
141
|
+
if (confirmUnsavedChanges && typeof window !== 'undefined' && typeof window.addEventListener === 'function') {
|
136
142
|
const onBeforeUnload = (event: BeforeUnloadEvent) => {
|
137
143
|
if (syncStateRef.current.pending.length > 0) {
|
138
144
|
// Trigger the default browser dialog
|
package/src/version.ts
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
// import packageJson from '../package.json' with { type: 'json' }
|
3
3
|
// export const liveStoreVersion = packageJson.version
|
4
4
|
|
5
|
-
export const liveStoreVersion = '0.3.0-dev.
|
5
|
+
export const liveStoreVersion = '0.3.0-dev.21' as const
|
6
6
|
|
7
7
|
/**
|
8
8
|
* This version number is incremented whenever the internal storage format changes in a breaking way.
|
@@ -1,16 +0,0 @@
|
|
1
|
-
import type { Effect, WebChannel } from '@livestore/utils/effect';
|
2
|
-
import type * as Devtools from './devtools-messages.js';
|
3
|
-
export type PrepareDevtoolsBridge = {
|
4
|
-
webchannels: {
|
5
|
-
leader: WebChannel.WebChannel<Devtools.Leader.MessageFromApp, Devtools.Leader.MessageToApp>;
|
6
|
-
clientSession: WebChannel.WebChannel<Devtools.ClientSession.MessageFromApp, Devtools.ClientSession.MessageToApp>;
|
7
|
-
};
|
8
|
-
clientInfo: {
|
9
|
-
clientId: string;
|
10
|
-
sessionId: string;
|
11
|
-
isLeader: boolean;
|
12
|
-
};
|
13
|
-
copyToClipboard: (text: string) => Effect.Effect<void>;
|
14
|
-
sendEscapeKey?: Effect.Effect<void>;
|
15
|
-
};
|
16
|
-
//# sourceMappingURL=devtools-bridge.d.ts.map
|