@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.
Files changed (82) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/adapter-types.d.ts +4 -3
  3. package/dist/adapter-types.d.ts.map +1 -1
  4. package/dist/adapter-types.js +1 -0
  5. package/dist/adapter-types.js.map +1 -1
  6. package/dist/debug-info.d.ts +1 -1
  7. package/dist/devtools/devtools-messages-client-session.d.ts +41 -22
  8. package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -1
  9. package/dist/devtools/devtools-messages-client-session.js +26 -7
  10. package/dist/devtools/devtools-messages-client-session.js.map +1 -1
  11. package/dist/devtools/devtools-messages-common.d.ts +16 -12
  12. package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
  13. package/dist/devtools/devtools-messages-common.js +15 -5
  14. package/dist/devtools/devtools-messages-common.js.map +1 -1
  15. package/dist/devtools/devtools-messages-leader.d.ts +74 -106
  16. package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
  17. package/dist/devtools/devtools-messages-leader.js +52 -37
  18. package/dist/devtools/devtools-messages-leader.js.map +1 -1
  19. package/dist/devtools/devtools-sessioninfo.d.ts +28 -0
  20. package/dist/devtools/devtools-sessioninfo.d.ts.map +1 -0
  21. package/dist/devtools/devtools-sessioninfo.js +34 -0
  22. package/dist/devtools/devtools-sessioninfo.js.map +1 -0
  23. package/dist/devtools/devtools-sessions-channel.d.ts +28 -0
  24. package/dist/devtools/devtools-sessions-channel.d.ts.map +1 -0
  25. package/dist/devtools/devtools-sessions-channel.js +34 -0
  26. package/dist/devtools/devtools-sessions-channel.js.map +1 -0
  27. package/dist/devtools/index.d.ts +27 -49
  28. package/dist/devtools/index.d.ts.map +1 -1
  29. package/dist/devtools/index.js +10 -55
  30. package/dist/devtools/index.js.map +1 -1
  31. package/dist/devtools/mod.d.ts +39 -0
  32. package/dist/devtools/mod.d.ts.map +1 -0
  33. package/dist/devtools/mod.js +27 -0
  34. package/dist/devtools/mod.js.map +1 -0
  35. package/dist/index.d.ts +1 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +1 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
  40. package/dist/leader-thread/leader-worker-devtools.js +73 -31
  41. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  42. package/dist/otel.d.ts +2 -0
  43. package/dist/otel.d.ts.map +1 -1
  44. package/dist/otel.js +5 -0
  45. package/dist/otel.js.map +1 -1
  46. package/dist/query-builder/impl.d.ts +3 -3
  47. package/dist/query-builder/impl.d.ts.map +1 -1
  48. package/dist/rehydrate-from-mutationlog.js.map +1 -1
  49. package/dist/schema/db-schema/dsl/field-defs.d.ts.map +1 -1
  50. package/dist/schema/db-schema/dsl/field-defs.js.map +1 -1
  51. package/dist/schema/db-schema/dsl/mod.js.map +1 -1
  52. package/dist/schema/system-tables.d.ts +2 -2
  53. package/dist/sync/ClientSessionSyncProcessor.d.ts +6 -1
  54. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  55. package/dist/sync/ClientSessionSyncProcessor.js +2 -2
  56. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  57. package/dist/util.d.ts +2 -2
  58. package/dist/util.d.ts.map +1 -1
  59. package/dist/version.d.ts +1 -1
  60. package/dist/version.js +1 -1
  61. package/package.json +3 -3
  62. package/src/adapter-types.ts +2 -1
  63. package/src/devtools/devtools-messages-client-session.ts +26 -10
  64. package/src/devtools/devtools-messages-common.ts +38 -13
  65. package/src/devtools/devtools-messages-leader.ts +61 -46
  66. package/src/devtools/devtools-sessioninfo.ts +99 -0
  67. package/src/devtools/mod.ts +36 -0
  68. package/src/index.ts +1 -1
  69. package/src/leader-thread/leader-worker-devtools.ts +83 -36
  70. package/src/leader-thread/make-leader-thread-layer.ts +1 -1
  71. package/src/otel.ts +8 -0
  72. package/src/rehydrate-from-mutationlog.ts +1 -1
  73. package/src/schema/db-schema/dsl/field-defs.ts +2 -1
  74. package/src/schema/db-schema/dsl/mod.ts +1 -1
  75. package/src/sync/ClientSessionSyncProcessor.ts +7 -1
  76. package/src/version.ts +1 -1
  77. package/dist/devtools/devtools-bridge.d.ts +0 -16
  78. package/dist/devtools/devtools-bridge.d.ts.map +0 -1
  79. package/dist/devtools/devtools-bridge.js +0 -2
  80. package/dist/devtools/devtools-bridge.js.map +0 -1
  81. package/src/devtools/devtools-bridge.ts +0 -17
  82. 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, UnexpectedError } from '../adapter-types.js'
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
- export class NetworkStatusUnsubscribe extends LSDReqResMessage('LSD.Leader.NetworkStatusUnsubscribe', {}) {}
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
- export class SyncHistoryUnsubscribe extends LSDReqResMessage('LSD.Leader.SyncHistoryUnsubscribe', {}) {}
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
- export class SyncHeadUnsubscribe extends LSDReqResMessage('LSD.Leader.SyncHeadUnsubscribe', {}) {}
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 class LoadDatabaseFileReq extends LSDReqResMessage('LSD.Leader.LoadDatabaseFileReq', {
70
- data: Transferable.Uint8Array,
71
- }) {}
72
-
73
- export class LoadDatabaseFileRes extends LSDReqResMessage('LSD.Leader.LoadDatabaseFileRes', {
74
- status: Schema.Literal('ok', 'unsupported-file', 'unsupported-database'),
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
- payload: {
118
- requestId,
119
- liveStoreVersion,
120
- },
121
- success: DatabaseFileInfo,
122
- failure: UnexpectedError,
123
- }) {}
124
-
125
- export class NetworkStatus_ extends Schema.TaggedRequest<NetworkStatus_>()('LSD.Leader.NetworkStatus', {
126
- payload: {
127
- requestId,
128
- liveStoreVersion,
129
- },
130
- success: NetworkStatus,
131
- failure: UnexpectedError,
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
- LoadDatabaseFileReq,
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
- LoadDatabaseFileRes,
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.Response,
174
- SetSyncLatch.Response,
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/index.js'
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 subscriptionFiberMap = yield* FiberMap.make<RequestId>()
80
+ const handledRequestIds = new Set<RequestId>()
77
81
 
78
82
  yield* incomingMessages.pipe(
79
83
  Stream.tap((decodedEvent) =>
80
84
  Effect.gen(function* () {
81
- // yield* Effect.logDebug('[@livestore/common:leader-thread:devtools] incomingMessage', decodedEvent)
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
- const { requestId } = decodedEvent
88
- const reqPayload = { requestId, liveStoreVersion, clientId }
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.LoadDatabaseFileReq': {
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 (e) {
118
- yield* Effect.logError(`Error importing database file`, e)
135
+ } catch (cause) {
136
+ yield* Effect.logError(`Error importing database file`, cause)
119
137
  yield* sendMessage(
120
- Devtools.Leader.LoadDatabaseFileRes.make({ ...reqPayload, status: 'unsupported-file' }),
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
- if (tableNames.has(MUTATION_LOG_META_TABLE)) {
127
- yield* SubscriptionRef.set(shutdownStateSubRef, 'shutting-down')
147
+ try {
148
+ if (tableNames.has(MUTATION_LOG_META_TABLE)) {
149
+ // Is mutation log
150
+ yield* SubscriptionRef.set(shutdownStateSubRef, 'shutting-down')
128
151
 
129
- dbMutationLog.import(data)
152
+ dbMutationLog.import(data)
130
153
 
131
- dbReadModel.destroy()
132
- } else if (tableNames.has(SCHEMA_META_TABLE) && tableNames.has(SCHEMA_MUTATIONS_META_TABLE)) {
133
- yield* SubscriptionRef.set(shutdownStateSubRef, 'shutting-down')
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
- dbReadModel.import(data)
159
+ dbReadModel.import(data)
136
160
 
137
- dbMutationLog.destroy()
138
- } else {
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.LoadDatabaseFileRes.make({ ...reqPayload, status: 'unsupported-database' }),
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.Response.make({ ...reqPayload }))
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 { requestId } = decodedEvent
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(Devtools.Leader.SyncHistoryRes.make({ mutationEventEncoded, metadata, ...reqPayload })),
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, requestId),
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 { requestId } = decodedEvent
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, requestId),
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 { requestId } = decodedEvent
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, requestId),
345
+ FiberMap.run(subscriptionFiberMap, subscriptionId),
299
346
  )
300
347
 
301
348
  return
302
349
  }
303
350
  case 'LSD.Leader.SyncHeadUnsubscribe': {
304
- const { requestId } = decodedEvent
351
+ const { subscriptionId } = decodedEvent
305
352
 
306
- yield* FiberMap.remove(subscriptionFiberMap, requestId)
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.Response.make({ ...reqPayload }))
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/index.js'
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, shouldNeverHappen } from '@livestore/utils'
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: { schema?: Schema.Schema<TDecoded, TEncoded>
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.19' as const
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