@livestore/adapter-web 0.4.0-dev.9 → 0.4.0
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/README.md +5 -5
- package/dist/.tsbuildinfo +1 -1
- package/dist/in-memory/in-memory-adapter.d.ts +49 -5
- package/dist/in-memory/in-memory-adapter.d.ts.map +1 -1
- package/dist/in-memory/in-memory-adapter.js +77 -20
- package/dist/in-memory/in-memory-adapter.js.map +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/single-tab/mod.d.ts +15 -0
- package/dist/single-tab/mod.d.ts.map +1 -0
- package/dist/single-tab/mod.js +15 -0
- package/dist/single-tab/mod.js.map +1 -0
- package/dist/single-tab/single-tab-adapter.d.ts +108 -0
- package/dist/single-tab/single-tab-adapter.d.ts.map +1 -0
- package/dist/single-tab/single-tab-adapter.js +271 -0
- package/dist/single-tab/single-tab-adapter.js.map +1 -0
- package/dist/web-worker/client-session/client-session-devtools.d.ts +2 -2
- package/dist/web-worker/client-session/client-session-devtools.d.ts.map +1 -1
- package/dist/web-worker/client-session/client-session-devtools.js +20 -9
- package/dist/web-worker/client-session/client-session-devtools.js.map +1 -1
- package/dist/web-worker/client-session/persisted-adapter.d.ts +18 -0
- package/dist/web-worker/client-session/persisted-adapter.d.ts.map +1 -1
- package/dist/web-worker/client-session/persisted-adapter.js +141 -67
- package/dist/web-worker/client-session/persisted-adapter.js.map +1 -1
- package/dist/web-worker/client-session/sqlite-loader.d.ts +2 -0
- package/dist/web-worker/client-session/sqlite-loader.d.ts.map +1 -0
- package/dist/web-worker/client-session/sqlite-loader.js +16 -0
- package/dist/web-worker/client-session/sqlite-loader.js.map +1 -0
- package/dist/web-worker/common/persisted-sqlite.d.ts +11 -29
- package/dist/web-worker/common/persisted-sqlite.d.ts.map +1 -1
- package/dist/web-worker/common/persisted-sqlite.js +87 -188
- package/dist/web-worker/common/persisted-sqlite.js.map +1 -1
- package/dist/web-worker/common/shutdown-channel.d.ts +3 -2
- package/dist/web-worker/common/shutdown-channel.d.ts.map +1 -1
- package/dist/web-worker/common/shutdown-channel.js +2 -2
- package/dist/web-worker/common/shutdown-channel.js.map +1 -1
- package/dist/web-worker/common/worker-disconnect-channel.d.ts +2 -6
- package/dist/web-worker/common/worker-disconnect-channel.d.ts.map +1 -1
- package/dist/web-worker/common/worker-disconnect-channel.js +3 -2
- package/dist/web-worker/common/worker-disconnect-channel.js.map +1 -1
- package/dist/web-worker/common/worker-schema.d.ts +152 -58
- package/dist/web-worker/common/worker-schema.d.ts.map +1 -1
- package/dist/web-worker/common/worker-schema.js +55 -37
- package/dist/web-worker/common/worker-schema.js.map +1 -1
- package/dist/web-worker/leader-worker/make-leader-worker.d.ts +5 -3
- package/dist/web-worker/leader-worker/make-leader-worker.d.ts.map +1 -1
- package/dist/web-worker/leader-worker/make-leader-worker.js +98 -38
- package/dist/web-worker/leader-worker/make-leader-worker.js.map +1 -1
- package/dist/web-worker/shared-worker/make-shared-worker.d.ts +2 -1
- package/dist/web-worker/shared-worker/make-shared-worker.d.ts.map +1 -1
- package/dist/web-worker/shared-worker/make-shared-worker.js +62 -52
- package/dist/web-worker/shared-worker/make-shared-worker.js.map +1 -1
- package/package.json +56 -18
- package/src/in-memory/in-memory-adapter.ts +92 -26
- package/src/index.ts +15 -1
- package/src/single-tab/mod.ts +15 -0
- package/src/single-tab/single-tab-adapter.ts +499 -0
- package/src/web-worker/ambient.d.ts +7 -24
- package/src/web-worker/client-session/client-session-devtools.ts +32 -18
- package/src/web-worker/client-session/persisted-adapter.ts +199 -103
- package/src/web-worker/client-session/sqlite-loader.ts +19 -0
- package/src/web-worker/common/persisted-sqlite.ts +200 -298
- package/src/web-worker/common/shutdown-channel.ts +10 -3
- package/src/web-worker/common/worker-disconnect-channel.ts +10 -3
- package/src/web-worker/common/worker-schema.ts +78 -38
- package/src/web-worker/leader-worker/make-leader-worker.ts +148 -71
- package/src/web-worker/shared-worker/make-shared-worker.ts +78 -90
- package/dist/opfs-utils.d.ts +0 -7
- package/dist/opfs-utils.d.ts.map +0 -1
- package/dist/opfs-utils.js +0 -43
- package/dist/opfs-utils.js.map +0 -1
- package/src/opfs-utils.ts +0 -61
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BootStatus,
|
|
3
3
|
Devtools,
|
|
4
|
-
|
|
4
|
+
RejectedPushError,
|
|
5
5
|
liveStoreVersion,
|
|
6
6
|
MigrationsReport,
|
|
7
|
+
SyncBackend,
|
|
7
8
|
SyncState,
|
|
8
|
-
|
|
9
|
+
UnknownError,
|
|
9
10
|
} from '@livestore/common'
|
|
11
|
+
import { StreamEventsOptionsFields } from '@livestore/common/leader-thread'
|
|
10
12
|
import { EventSequenceNumber, LiveStoreEvent } from '@livestore/common/schema'
|
|
11
13
|
import * as WebmeshWorker from '@livestore/devtools-web-common/worker'
|
|
12
14
|
import { Schema, Transferable } from '@livestore/utils/effect'
|
|
@@ -48,7 +50,7 @@ export class LeaderWorkerOuterInitialMessage extends Schema.TaggedRequest<Leader
|
|
|
48
50
|
{
|
|
49
51
|
payload: { port: Transferable.MessagePort, storeId: Schema.String, clientId: Schema.String },
|
|
50
52
|
success: Schema.Void,
|
|
51
|
-
failure:
|
|
53
|
+
failure: Schema.Never,
|
|
52
54
|
},
|
|
53
55
|
) {}
|
|
54
56
|
|
|
@@ -64,10 +66,10 @@ export class LeaderWorkerInnerInitialMessage extends Schema.TaggedRequest<Leader
|
|
|
64
66
|
storeId: Schema.String,
|
|
65
67
|
clientId: Schema.String,
|
|
66
68
|
debugInstanceId: Schema.String,
|
|
67
|
-
|
|
69
|
+
syncPayloadEncoded: Schema.UndefinedOr(Schema.JsonValue),
|
|
68
70
|
},
|
|
69
71
|
success: Schema.Void,
|
|
70
|
-
failure:
|
|
72
|
+
failure: UnknownError,
|
|
71
73
|
},
|
|
72
74
|
) {}
|
|
73
75
|
|
|
@@ -76,7 +78,7 @@ export class LeaderWorkerInnerBootStatusStream extends Schema.TaggedRequest<Lead
|
|
|
76
78
|
{
|
|
77
79
|
payload: {},
|
|
78
80
|
success: BootStatus,
|
|
79
|
-
failure:
|
|
81
|
+
failure: Schema.Never,
|
|
80
82
|
},
|
|
81
83
|
) {}
|
|
82
84
|
|
|
@@ -84,27 +86,36 @@ export class LeaderWorkerInnerPushToLeader extends Schema.TaggedRequest<LeaderWo
|
|
|
84
86
|
'PushToLeader',
|
|
85
87
|
{
|
|
86
88
|
payload: {
|
|
87
|
-
batch: Schema.Array(LiveStoreEvent.
|
|
89
|
+
batch: Schema.Array(Schema.typeSchema(LiveStoreEvent.Client.Encoded)),
|
|
88
90
|
},
|
|
89
|
-
success: Schema.Void
|
|
90
|
-
failure:
|
|
91
|
+
success: Schema.Void as Schema.Schema<void>,
|
|
92
|
+
failure: RejectedPushError,
|
|
91
93
|
},
|
|
92
94
|
) {}
|
|
93
95
|
|
|
94
96
|
export class LeaderWorkerInnerPullStream extends Schema.TaggedRequest<LeaderWorkerInnerPullStream>()('PullStream', {
|
|
95
97
|
payload: {
|
|
96
|
-
cursor: EventSequenceNumber.
|
|
98
|
+
cursor: Schema.typeSchema(EventSequenceNumber.Client.Composite),
|
|
97
99
|
},
|
|
98
100
|
success: Schema.Struct({
|
|
99
101
|
payload: SyncState.PayloadUpstream,
|
|
100
102
|
}),
|
|
101
|
-
failure:
|
|
103
|
+
failure: Schema.Never,
|
|
102
104
|
}) {}
|
|
103
105
|
|
|
106
|
+
export class LeaderWorkerInnerStreamEvents extends Schema.TaggedRequest<LeaderWorkerInnerStreamEvents>()(
|
|
107
|
+
'StreamEvents',
|
|
108
|
+
{
|
|
109
|
+
payload: StreamEventsOptionsFields,
|
|
110
|
+
success: LiveStoreEvent.Client.Encoded,
|
|
111
|
+
failure: Schema.Never,
|
|
112
|
+
},
|
|
113
|
+
) {}
|
|
114
|
+
|
|
104
115
|
export class LeaderWorkerInnerExport extends Schema.TaggedRequest<LeaderWorkerInnerExport>()('Export', {
|
|
105
116
|
payload: {},
|
|
106
117
|
success: Transferable.Uint8Array as Schema.Schema<Uint8Array<ArrayBuffer>>,
|
|
107
|
-
failure:
|
|
118
|
+
failure: Schema.Never,
|
|
108
119
|
}) {}
|
|
109
120
|
|
|
110
121
|
export class LeaderWorkerInnerExportEventlog extends Schema.TaggedRequest<LeaderWorkerInnerExportEventlog>()(
|
|
@@ -112,7 +123,7 @@ export class LeaderWorkerInnerExportEventlog extends Schema.TaggedRequest<Leader
|
|
|
112
123
|
{
|
|
113
124
|
payload: {},
|
|
114
125
|
success: Transferable.Uint8Array as Schema.Schema<Uint8Array<ArrayBuffer>>,
|
|
115
|
-
failure:
|
|
126
|
+
failure: Schema.Never,
|
|
116
127
|
},
|
|
117
128
|
) {}
|
|
118
129
|
|
|
@@ -124,7 +135,7 @@ export class LeaderWorkerInnerGetRecreateSnapshot extends Schema.TaggedRequest<L
|
|
|
124
135
|
snapshot: Transferable.Uint8Array as Schema.Schema<Uint8Array<ArrayBuffer>>,
|
|
125
136
|
migrationsReport: MigrationsReport,
|
|
126
137
|
}),
|
|
127
|
-
failure:
|
|
138
|
+
failure: Schema.Never,
|
|
128
139
|
},
|
|
129
140
|
) {}
|
|
130
141
|
|
|
@@ -132,8 +143,8 @@ export class LeaderWorkerInnerGetLeaderHead extends Schema.TaggedRequest<LeaderW
|
|
|
132
143
|
'GetLeaderHead',
|
|
133
144
|
{
|
|
134
145
|
payload: {},
|
|
135
|
-
success: EventSequenceNumber.
|
|
136
|
-
failure:
|
|
146
|
+
success: Schema.typeSchema(EventSequenceNumber.Client.Composite),
|
|
147
|
+
failure: Schema.Never,
|
|
137
148
|
},
|
|
138
149
|
) {}
|
|
139
150
|
|
|
@@ -142,14 +153,41 @@ export class LeaderWorkerInnerGetLeaderSyncState extends Schema.TaggedRequest<Le
|
|
|
142
153
|
{
|
|
143
154
|
payload: {},
|
|
144
155
|
success: SyncState.SyncState,
|
|
145
|
-
failure:
|
|
156
|
+
failure: Schema.Never,
|
|
157
|
+
},
|
|
158
|
+
) {}
|
|
159
|
+
|
|
160
|
+
export class LeaderWorkerInnerSyncStateStream extends Schema.TaggedRequest<LeaderWorkerInnerSyncStateStream>()(
|
|
161
|
+
'SyncStateStream',
|
|
162
|
+
{
|
|
163
|
+
payload: {},
|
|
164
|
+
success: SyncState.SyncState,
|
|
165
|
+
failure: Schema.Never,
|
|
166
|
+
},
|
|
167
|
+
) {}
|
|
168
|
+
|
|
169
|
+
export class LeaderWorkerInnerGetNetworkStatus extends Schema.TaggedRequest<LeaderWorkerInnerGetNetworkStatus>()(
|
|
170
|
+
'GetNetworkStatus',
|
|
171
|
+
{
|
|
172
|
+
payload: {},
|
|
173
|
+
success: SyncBackend.NetworkStatus,
|
|
174
|
+
failure: Schema.Never,
|
|
175
|
+
},
|
|
176
|
+
) {}
|
|
177
|
+
|
|
178
|
+
export class LeaderWorkerInnerNetworkStatusStream extends Schema.TaggedRequest<LeaderWorkerInnerNetworkStatusStream>()(
|
|
179
|
+
'NetworkStatusStream',
|
|
180
|
+
{
|
|
181
|
+
payload: {},
|
|
182
|
+
success: SyncBackend.NetworkStatus,
|
|
183
|
+
failure: Schema.Never,
|
|
146
184
|
},
|
|
147
185
|
) {}
|
|
148
186
|
|
|
149
187
|
export class LeaderWorkerInnerShutdown extends Schema.TaggedRequest<LeaderWorkerInnerShutdown>()('Shutdown', {
|
|
150
188
|
payload: {},
|
|
151
189
|
success: Schema.Void,
|
|
152
|
-
failure:
|
|
190
|
+
failure: Schema.Never,
|
|
153
191
|
}) {}
|
|
154
192
|
|
|
155
193
|
export class LeaderWorkerInnerExtraDevtoolsMessage extends Schema.TaggedRequest<LeaderWorkerInnerExtraDevtoolsMessage>()(
|
|
@@ -159,7 +197,7 @@ export class LeaderWorkerInnerExtraDevtoolsMessage extends Schema.TaggedRequest<
|
|
|
159
197
|
message: Devtools.Leader.MessageToApp,
|
|
160
198
|
},
|
|
161
199
|
success: Schema.Void,
|
|
162
|
-
failure:
|
|
200
|
+
failure: Schema.Never,
|
|
163
201
|
},
|
|
164
202
|
) {}
|
|
165
203
|
|
|
@@ -168,58 +206,60 @@ export const LeaderWorkerInnerRequest = Schema.Union(
|
|
|
168
206
|
LeaderWorkerInnerBootStatusStream,
|
|
169
207
|
LeaderWorkerInnerPushToLeader,
|
|
170
208
|
LeaderWorkerInnerPullStream,
|
|
209
|
+
LeaderWorkerInnerStreamEvents,
|
|
171
210
|
LeaderWorkerInnerExport,
|
|
172
211
|
LeaderWorkerInnerExportEventlog,
|
|
173
212
|
LeaderWorkerInnerGetRecreateSnapshot,
|
|
174
213
|
LeaderWorkerInnerGetLeaderHead,
|
|
175
214
|
LeaderWorkerInnerGetLeaderSyncState,
|
|
215
|
+
LeaderWorkerInnerSyncStateStream,
|
|
216
|
+
LeaderWorkerInnerGetNetworkStatus,
|
|
217
|
+
LeaderWorkerInnerNetworkStatusStream,
|
|
176
218
|
LeaderWorkerInnerShutdown,
|
|
177
219
|
LeaderWorkerInnerExtraDevtoolsMessage,
|
|
178
220
|
WebmeshWorker.Schema.CreateConnection,
|
|
179
221
|
)
|
|
180
222
|
export type LeaderWorkerInnerRequest = typeof LeaderWorkerInnerRequest.Type
|
|
181
223
|
|
|
182
|
-
export class SharedWorkerInitialMessagePayloadFromClientSession extends Schema.TaggedStruct('FromClientSession', {
|
|
183
|
-
initialMessage: LeaderWorkerInnerInitialMessage,
|
|
184
|
-
}) {}
|
|
185
|
-
|
|
186
|
-
export class SharedWorkerInitialMessage extends Schema.TaggedRequest<SharedWorkerInitialMessage>()('InitialMessage', {
|
|
187
|
-
payload: {
|
|
188
|
-
payload: Schema.Union(SharedWorkerInitialMessagePayloadFromClientSession, Schema.TaggedStruct('FromWebBridge', {})),
|
|
189
|
-
// To guard against scenarios where a client session is already running a newer version of LiveStore
|
|
190
|
-
// We should probably find a better way to handle those cases once they become more common.
|
|
191
|
-
liveStoreVersion: Schema.Literal(liveStoreVersion),
|
|
192
|
-
},
|
|
193
|
-
success: Schema.Void,
|
|
194
|
-
failure: UnexpectedError,
|
|
195
|
-
}) {}
|
|
196
|
-
|
|
197
224
|
export class SharedWorkerUpdateMessagePort extends Schema.TaggedRequest<SharedWorkerUpdateMessagePort>()(
|
|
198
225
|
'UpdateMessagePort',
|
|
199
226
|
{
|
|
200
227
|
payload: {
|
|
201
228
|
port: Transferable.MessagePort,
|
|
229
|
+
// Version gate to prevent mixed LiveStore builds talking to the same SharedWorker
|
|
230
|
+
liveStoreVersion: Schema.Literal(liveStoreVersion),
|
|
231
|
+
/**
|
|
232
|
+
* Initial configuration for the leader worker. This replaces the previous
|
|
233
|
+
* two-phase SharedWorker handshake and is sent under the tab lock by the
|
|
234
|
+
* elected leader. Subsequent calls can omit changes and will simply rebind
|
|
235
|
+
* the port (join) without reinitializing the store.
|
|
236
|
+
*/
|
|
237
|
+
initial: LeaderWorkerInnerInitialMessage,
|
|
202
238
|
},
|
|
203
239
|
success: Schema.Void,
|
|
204
|
-
failure:
|
|
240
|
+
failure: UnknownError,
|
|
205
241
|
},
|
|
206
242
|
) {}
|
|
207
243
|
|
|
208
|
-
export
|
|
209
|
-
SharedWorkerInitialMessage,
|
|
244
|
+
export const SharedWorkerRequest = Schema.Union(
|
|
210
245
|
SharedWorkerUpdateMessagePort,
|
|
211
246
|
|
|
212
247
|
// Proxied requests
|
|
213
248
|
LeaderWorkerInnerBootStatusStream,
|
|
214
249
|
LeaderWorkerInnerPushToLeader,
|
|
215
250
|
LeaderWorkerInnerPullStream,
|
|
251
|
+
LeaderWorkerInnerStreamEvents,
|
|
216
252
|
LeaderWorkerInnerExport,
|
|
217
253
|
LeaderWorkerInnerGetRecreateSnapshot,
|
|
218
254
|
LeaderWorkerInnerExportEventlog,
|
|
219
255
|
LeaderWorkerInnerGetLeaderHead,
|
|
220
256
|
LeaderWorkerInnerGetLeaderSyncState,
|
|
257
|
+
LeaderWorkerInnerSyncStateStream,
|
|
258
|
+
LeaderWorkerInnerGetNetworkStatus,
|
|
259
|
+
LeaderWorkerInnerNetworkStatusStream,
|
|
221
260
|
LeaderWorkerInnerShutdown,
|
|
222
261
|
LeaderWorkerInnerExtraDevtoolsMessage,
|
|
223
262
|
|
|
224
263
|
WebmeshWorker.Schema.CreateConnection,
|
|
225
|
-
)
|
|
264
|
+
)
|
|
265
|
+
export type SharedWorkerRequest = typeof SharedWorkerRequest.Type
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import type
|
|
2
|
-
|
|
3
|
-
import type {
|
|
4
|
-
import {
|
|
1
|
+
import type * as otel from '@opentelemetry/api'
|
|
2
|
+
|
|
3
|
+
import type { BootStatus, BootWarningReason, SqliteDb, SyncOptions } from '@livestore/common'
|
|
4
|
+
import { Devtools, LogConfig, UnknownError } from '@livestore/common'
|
|
5
|
+
import type { DevtoolsOptions, StreamEventsOptions } from '@livestore/common/leader-thread'
|
|
6
|
+
import {
|
|
7
|
+
configureConnection,
|
|
8
|
+
Eventlog,
|
|
9
|
+
LeaderThreadCtx,
|
|
10
|
+
makeLeaderThreadLayer,
|
|
11
|
+
streamEventsWithSyncState,
|
|
12
|
+
} from '@livestore/common/leader-thread'
|
|
5
13
|
import type { LiveStoreSchema } from '@livestore/common/schema'
|
|
6
14
|
import { LiveStoreEvent } from '@livestore/common/schema'
|
|
7
15
|
import * as WebmeshWorker from '@livestore/devtools-web-common/worker'
|
|
@@ -10,22 +18,19 @@ import { loadSqlite3Wasm } from '@livestore/sqlite-wasm/load-wasm'
|
|
|
10
18
|
import { isDevEnv, LS_DEV } from '@livestore/utils'
|
|
11
19
|
import type { HttpClient, Scope, WorkerError } from '@livestore/utils/effect'
|
|
12
20
|
import {
|
|
13
|
-
BrowserWorkerRunner,
|
|
14
21
|
Effect,
|
|
15
22
|
FetchHttpClient,
|
|
16
23
|
identity,
|
|
17
24
|
Layer,
|
|
18
|
-
Logger,
|
|
19
|
-
LogLevel,
|
|
20
25
|
OtelTracer,
|
|
21
26
|
Scheduler,
|
|
27
|
+
Schema,
|
|
22
28
|
Stream,
|
|
23
29
|
TaskTracing,
|
|
24
30
|
WorkerRunner,
|
|
25
31
|
} from '@livestore/utils/effect'
|
|
26
|
-
import
|
|
32
|
+
import { BrowserWorkerRunner, Opfs, WebError } from '@livestore/utils/effect/browser'
|
|
27
33
|
|
|
28
|
-
import * as OpfsUtils from '../../opfs-utils.ts'
|
|
29
34
|
import { cleanupOldStateDbFiles, getStateDbFileName, sanitizeOpfsDir } from '../common/persisted-sqlite.ts'
|
|
30
35
|
import { makeShutdownChannel } from '../common/shutdown-channel.ts'
|
|
31
36
|
import * as WorkerSchema from '../common/worker-schema.ts'
|
|
@@ -33,18 +38,19 @@ import * as WorkerSchema from '../common/worker-schema.ts'
|
|
|
33
38
|
export type WorkerOptions = {
|
|
34
39
|
schema: LiveStoreSchema
|
|
35
40
|
sync?: SyncOptions
|
|
41
|
+
syncPayloadSchema?: Schema.Schema<any>
|
|
36
42
|
otelOptions?: {
|
|
37
43
|
tracer?: otel.Tracer
|
|
38
44
|
}
|
|
39
|
-
}
|
|
45
|
+
} & LogConfig.WithLoggerOptions
|
|
40
46
|
|
|
41
|
-
if (isDevEnv()) {
|
|
47
|
+
if (isDevEnv() === true) {
|
|
42
48
|
globalThis.__debugLiveStoreUtils = {
|
|
43
|
-
opfs:
|
|
49
|
+
opfs: Opfs.debugUtils,
|
|
44
50
|
blobUrl: (buffer: Uint8Array<ArrayBuffer>) =>
|
|
45
51
|
URL.createObjectURL(new Blob([buffer], { type: 'application/octet-stream' })),
|
|
46
|
-
runSync: (effect: Effect.Effect<
|
|
47
|
-
runFork: (effect: Effect.Effect<
|
|
52
|
+
runSync: <A, E>(effect: Effect.Effect<A, E>) => Effect.runSync(effect),
|
|
53
|
+
runFork: <A, E>(effect: Effect.Effect<A, E>) => Effect.runFork(effect),
|
|
48
54
|
}
|
|
49
55
|
}
|
|
50
56
|
|
|
@@ -53,13 +59,13 @@ export const makeWorker = (options: WorkerOptions) => {
|
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
export const makeWorkerEffect = (options: WorkerOptions) => {
|
|
56
|
-
const TracingLive = options.otelOptions?.tracer
|
|
62
|
+
const TracingLive = options.otelOptions?.tracer !== undefined
|
|
57
63
|
? Layer.unwrapEffect(Effect.map(OtelTracer.make, Layer.setTracer)).pipe(
|
|
58
64
|
Layer.provideMerge(Layer.succeed(OtelTracer.OtelTracer, options.otelOptions.tracer)),
|
|
59
65
|
)
|
|
60
66
|
: undefined
|
|
61
67
|
|
|
62
|
-
const
|
|
68
|
+
const runtimeLayer = Layer.mergeAll(FetchHttpClient.layer, TracingLive ?? Layer.empty)
|
|
63
69
|
|
|
64
70
|
return makeWorkerRunnerOuter(options).pipe(
|
|
65
71
|
Layer.provide(BrowserWorkerRunner.layer),
|
|
@@ -67,15 +73,15 @@ export const makeWorkerEffect = (options: WorkerOptions) => {
|
|
|
67
73
|
Effect.scoped,
|
|
68
74
|
Effect.tapCauseLogPretty,
|
|
69
75
|
Effect.annotateLogs({ thread: self.name }),
|
|
70
|
-
Effect.provide(
|
|
71
|
-
LS_DEV ? TaskTracing.withAsyncTaggingTracing((name) => (console as any).createTask(name)) : identity,
|
|
76
|
+
Effect.provide(runtimeLayer),
|
|
77
|
+
LS_DEV === true ? TaskTracing.withAsyncTaggingTracing((name) => (console as any).createTask(name)) : identity,
|
|
72
78
|
// We're using this custom scheduler to improve op batching behaviour and reduce the overhead
|
|
73
79
|
// of the Effect fiber runtime given we have different tradeoffs on a worker thread.
|
|
74
80
|
// Despite the "message channel" name, is has nothing to do with the `incomingRequestsPort` above.
|
|
75
81
|
Effect.withScheduler(Scheduler.messageChannel()),
|
|
76
82
|
// We're increasing the Effect ops limit here to allow for larger chunks of operations at a time
|
|
77
83
|
Effect.withMaxOpsBeforeYield(4096),
|
|
78
|
-
|
|
84
|
+
LogConfig.withLoggerConfig({ logger: options.logger, logLevel: options.logLevel }, { threadName: self.name }),
|
|
79
85
|
)
|
|
80
86
|
}
|
|
81
87
|
|
|
@@ -93,7 +99,12 @@ const makeWorkerRunnerOuter = (
|
|
|
93
99
|
Effect.withSpan('@livestore/adapter-web:worker:wrapper:InitialMessage:innerFiber'),
|
|
94
100
|
Effect.tapCauseLogPretty,
|
|
95
101
|
Effect.provide(
|
|
96
|
-
|
|
102
|
+
Layer.mergeAll(
|
|
103
|
+
Opfs.Opfs.Default,
|
|
104
|
+
WebmeshWorker.CacheService.layer({
|
|
105
|
+
nodeName: Devtools.makeNodeName.client.leader({ storeId, clientId }),
|
|
106
|
+
}),
|
|
107
|
+
),
|
|
97
108
|
),
|
|
98
109
|
Effect.forkScoped,
|
|
99
110
|
)
|
|
@@ -102,18 +113,34 @@ const makeWorkerRunnerOuter = (
|
|
|
102
113
|
}).pipe(Effect.withSpan('@livestore/adapter-web:worker:wrapper:InitialMessage'), Layer.unwrapScoped),
|
|
103
114
|
})
|
|
104
115
|
|
|
105
|
-
const makeWorkerRunnerInner = ({ schema, sync: syncOptions }: WorkerOptions) =>
|
|
116
|
+
const makeWorkerRunnerInner = ({ schema, sync: syncOptions, syncPayloadSchema }: WorkerOptions) =>
|
|
106
117
|
WorkerRunner.layerSerialized(WorkerSchema.LeaderWorkerInnerRequest, {
|
|
107
|
-
InitialMessage: ({ storageOptions, storeId, clientId, devtoolsEnabled, debugInstanceId,
|
|
118
|
+
InitialMessage: ({ storageOptions, storeId, clientId, devtoolsEnabled, debugInstanceId, syncPayloadEncoded }) =>
|
|
108
119
|
Effect.gen(function* () {
|
|
109
120
|
const sqlite3 = yield* Effect.promise(() => loadSqlite3Wasm())
|
|
110
121
|
const makeSqliteDb = sqliteDbFactory({ sqlite3 })
|
|
111
|
-
const runtime = yield* Effect.runtime
|
|
122
|
+
const runtime = yield* Effect.runtime()
|
|
123
|
+
|
|
124
|
+
// Check OPFS availability and determine storage mode
|
|
125
|
+
const opfsCheck = yield* checkOpfsAvailability
|
|
126
|
+
const useOpfs = opfsCheck === undefined
|
|
127
|
+
|
|
128
|
+
// Track boot warning to emit later
|
|
129
|
+
let bootWarning: BootStatus | undefined
|
|
130
|
+
if (useOpfs === false) {
|
|
131
|
+
yield* Effect.logWarning(
|
|
132
|
+
'[@livestore/adapter-web:worker] OPFS unavailable, using in-memory storage',
|
|
133
|
+
opfsCheck,
|
|
134
|
+
)
|
|
135
|
+
bootWarning = { stage: 'warning', ...opfsCheck }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const opfsDirectory = useOpfs === true ? yield* sanitizeOpfsDir(storageOptions.directory, storeId) : undefined
|
|
112
139
|
|
|
113
|
-
const
|
|
140
|
+
const makeOpfsDb = (kind: 'state' | 'eventlog') =>
|
|
114
141
|
makeSqliteDb({
|
|
115
142
|
_tag: 'opfs',
|
|
116
|
-
opfsDirectory:
|
|
143
|
+
opfsDirectory: opfsDirectory!,
|
|
117
144
|
fileName: kind === 'state' ? getStateDbFileName(schema) : 'eventlog.db',
|
|
118
145
|
configureDb: (db) =>
|
|
119
146
|
configureConnection(db, {
|
|
@@ -126,10 +153,17 @@ const makeWorkerRunnerInner = ({ schema, sync: syncOptions }: WorkerOptions) =>
|
|
|
126
153
|
}).pipe(Effect.provide(runtime), Effect.runSync),
|
|
127
154
|
}).pipe(Effect.acquireRelease((db) => Effect.try(() => db.close()).pipe(Effect.ignoreLogged)))
|
|
128
155
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
156
|
+
const makeInMemoryDb = () =>
|
|
157
|
+
makeSqliteDb({
|
|
158
|
+
_tag: 'in-memory',
|
|
159
|
+
configureDb: (db) =>
|
|
160
|
+
configureConnection(db, { foreignKeys: true }).pipe(Effect.provide(runtime), Effect.runSync),
|
|
161
|
+
}).pipe(Effect.acquireRelease((db) => Effect.try(() => db.close()).pipe(Effect.ignoreLogged)))
|
|
162
|
+
|
|
163
|
+
// Use OPFS if available, otherwise fall back to in-memory
|
|
164
|
+
const [dbState, dbEventlog] = useOpfs === true
|
|
165
|
+
? yield* Effect.all([makeOpfsDb('state'), makeOpfsDb('eventlog')], { concurrency: 2 })
|
|
166
|
+
: yield* Effect.all([makeInMemoryDb(), makeInMemoryDb()], { concurrency: 2 })
|
|
133
167
|
|
|
134
168
|
// Clean up old state database files after successful database creation
|
|
135
169
|
// This prevents OPFS file pool capacity exhaustion from accumulated state db files after schema changes/migrations
|
|
@@ -154,35 +188,33 @@ const makeWorkerRunnerInner = ({ schema, sync: syncOptions }: WorkerOptions) =>
|
|
|
154
188
|
dbEventlog,
|
|
155
189
|
devtoolsOptions,
|
|
156
190
|
shutdownChannel,
|
|
157
|
-
|
|
191
|
+
syncPayloadEncoded,
|
|
192
|
+
syncPayloadSchema,
|
|
193
|
+
...(bootWarning !== undefined ? { bootWarning } : {}),
|
|
158
194
|
})
|
|
159
195
|
}).pipe(
|
|
160
196
|
Effect.tapCauseLogPretty,
|
|
161
|
-
|
|
197
|
+
UnknownError.mapToUnknownError,
|
|
162
198
|
Effect.withPerformanceMeasure('@livestore/adapter-web:worker:InitialMessage'),
|
|
163
199
|
Effect.withSpan('@livestore/adapter-web:worker:InitialMessage'),
|
|
164
200
|
Effect.annotateSpans({ debugInstanceId }),
|
|
165
201
|
Layer.unwrapScoped,
|
|
166
202
|
),
|
|
167
|
-
GetRecreateSnapshot: ()
|
|
168
|
-
|
|
169
|
-
const workerCtx = yield* LeaderThreadCtx
|
|
203
|
+
GetRecreateSnapshot: Effect.fn('@livestore/adapter-web:worker:GetRecreateSnapshot')(function* () {
|
|
204
|
+
const workerCtx = yield* LeaderThreadCtx
|
|
170
205
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
206
|
+
// NOTE we can only return the cached snapshot once as it's transferred (i.e. disposed), so we need to set it to undefined
|
|
207
|
+
// const cachedSnapshot =
|
|
208
|
+
// result._tag === 'Recreate' ? yield* Ref.getAndSet(result.snapshotRef, undefined) : undefined
|
|
174
209
|
|
|
175
|
-
|
|
210
|
+
// return cachedSnapshot ?? workerCtx.db.export()
|
|
176
211
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
UnexpectedError.mapToUnexpectedError,
|
|
181
|
-
Effect.withSpan('@livestore/adapter-web:worker:GetRecreateSnapshot'),
|
|
182
|
-
),
|
|
212
|
+
const snapshot = workerCtx.dbState.export()
|
|
213
|
+
return { snapshot, migrationsReport: workerCtx.initialState.migrationsReport }
|
|
214
|
+
}),
|
|
183
215
|
PullStream: ({ cursor }) =>
|
|
184
216
|
Effect.gen(function* () {
|
|
185
|
-
const { syncProcessor } = yield* LeaderThreadCtx
|
|
217
|
+
const { syncProcessor } = yield* LeaderThreadCtx // <- syncState comes from here
|
|
186
218
|
return syncProcessor.pull({ cursor })
|
|
187
219
|
}).pipe(
|
|
188
220
|
Stream.unwrapScoped,
|
|
@@ -192,47 +224,66 @@ const makeWorkerRunnerInner = ({ schema, sync: syncOptions }: WorkerOptions) =>
|
|
|
192
224
|
PushToLeader: ({ batch }) =>
|
|
193
225
|
Effect.andThen(LeaderThreadCtx, ({ syncProcessor }) =>
|
|
194
226
|
syncProcessor.push(
|
|
195
|
-
batch.map((event) => new LiveStoreEvent.EncodedWithMeta(event)),
|
|
227
|
+
batch.map((event) => new LiveStoreEvent.Client.EncodedWithMeta(event)),
|
|
196
228
|
// We'll wait in order to keep back pressure on the client session
|
|
197
229
|
{ waitForProcessing: true },
|
|
198
230
|
),
|
|
199
231
|
).pipe(Effect.uninterruptible, Effect.withSpan('@livestore/adapter-web:worker:PushToLeader')),
|
|
232
|
+
StreamEvents: (options) =>
|
|
233
|
+
LeaderThreadCtx.pipe(
|
|
234
|
+
Effect.map(({ dbEventlog, syncProcessor }) => {
|
|
235
|
+
const { _tag: _ignored, ...payload } = options as any
|
|
236
|
+
const streamOptions = payload as StreamEventsOptions
|
|
237
|
+
return streamEventsWithSyncState({
|
|
238
|
+
dbEventlog,
|
|
239
|
+
syncState: syncProcessor.syncState,
|
|
240
|
+
options: streamOptions,
|
|
241
|
+
})
|
|
242
|
+
}),
|
|
243
|
+
Stream.unwrapScoped,
|
|
244
|
+
Stream.withSpan('@livestore/adapter-web:worker:StreamEvents'),
|
|
245
|
+
),
|
|
200
246
|
Export: () =>
|
|
201
247
|
Effect.andThen(LeaderThreadCtx, (_) => _.dbState.export()).pipe(
|
|
202
|
-
UnexpectedError.mapToUnexpectedError,
|
|
203
248
|
Effect.withSpan('@livestore/adapter-web:worker:Export'),
|
|
204
249
|
),
|
|
205
250
|
ExportEventlog: () =>
|
|
206
251
|
Effect.andThen(LeaderThreadCtx, (_) => _.dbEventlog.export()).pipe(
|
|
207
|
-
UnexpectedError.mapToUnexpectedError,
|
|
208
252
|
Effect.withSpan('@livestore/adapter-web:worker:ExportEventlog'),
|
|
209
253
|
),
|
|
210
254
|
BootStatusStream: () =>
|
|
211
255
|
Effect.andThen(LeaderThreadCtx, (_) => Stream.fromQueue(_.bootStatusQueue)).pipe(Stream.unwrap),
|
|
212
|
-
GetLeaderHead: ()
|
|
256
|
+
GetLeaderHead: Effect.fn('@livestore/adapter-web:worker:GetLeaderHead')(function* () {
|
|
257
|
+
const workerCtx = yield* LeaderThreadCtx
|
|
258
|
+
return Eventlog.getClientHeadFromDb(workerCtx.dbEventlog)
|
|
259
|
+
}),
|
|
260
|
+
GetLeaderSyncState: Effect.fn('@livestore/adapter-web:worker:GetLeaderSyncState')(function* () {
|
|
261
|
+
const workerCtx = yield* LeaderThreadCtx
|
|
262
|
+
return yield* workerCtx.syncProcessor.syncState
|
|
263
|
+
}),
|
|
264
|
+
SyncStateStream: () =>
|
|
213
265
|
Effect.gen(function* () {
|
|
214
266
|
const workerCtx = yield* LeaderThreadCtx
|
|
215
|
-
return
|
|
216
|
-
}).pipe(
|
|
217
|
-
|
|
267
|
+
return workerCtx.syncProcessor.syncState.changes
|
|
268
|
+
}).pipe(Stream.unwrapScoped),
|
|
269
|
+
GetNetworkStatus: Effect.fn('@livestore/adapter-web:worker:GetNetworkStatus')(function* () {
|
|
270
|
+
const workerCtx = yield* LeaderThreadCtx
|
|
271
|
+
return yield* workerCtx.networkStatus
|
|
272
|
+
}),
|
|
273
|
+
NetworkStatusStream: () =>
|
|
218
274
|
Effect.gen(function* () {
|
|
219
275
|
const workerCtx = yield* LeaderThreadCtx
|
|
220
|
-
return
|
|
221
|
-
}).pipe(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
),
|
|
225
|
-
Shutdown: () =>
|
|
226
|
-
Effect.gen(function* () {
|
|
227
|
-
yield* Effect.logDebug('[@livestore/adapter-web:worker] Shutdown')
|
|
276
|
+
return workerCtx.networkStatus.changes
|
|
277
|
+
}).pipe(Stream.unwrapScoped),
|
|
278
|
+
Shutdown: Effect.fn('@livestore/adapter-web:worker:Shutdown')(function* () {
|
|
279
|
+
yield* Effect.logDebug('[@livestore/adapter-web:worker] Shutdown')
|
|
228
280
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
281
|
+
// Buy some time for Otel to flush
|
|
282
|
+
// TODO find a cleaner way to do this
|
|
283
|
+
yield* Effect.sleep(300)
|
|
284
|
+
}),
|
|
233
285
|
ExtraDevtoolsMessage: ({ message }) =>
|
|
234
286
|
Effect.andThen(LeaderThreadCtx, (_) => _.extraIncomingMessagesQueue.offer(message)).pipe(
|
|
235
|
-
UnexpectedError.mapToUnexpectedError,
|
|
236
287
|
Effect.withSpan('@livestore/adapter-web:worker:ExtraDevtoolsMessage'),
|
|
237
288
|
),
|
|
238
289
|
'DevtoolsWebCommon.CreateConnection': WebmeshWorker.CreateConnection,
|
|
@@ -246,7 +297,7 @@ const makeDevtoolsOptions = ({
|
|
|
246
297
|
devtoolsEnabled: boolean
|
|
247
298
|
dbState: SqliteDb
|
|
248
299
|
dbEventlog: SqliteDb
|
|
249
|
-
}): Effect.Effect<DevtoolsOptions,
|
|
300
|
+
}): Effect.Effect<DevtoolsOptions, UnknownError, Scope.Scope | WebmeshWorker.CacheService> =>
|
|
250
301
|
Effect.gen(function* () {
|
|
251
302
|
if (devtoolsEnabled === false) {
|
|
252
303
|
return { enabled: false }
|
|
@@ -256,13 +307,39 @@ const makeDevtoolsOptions = ({
|
|
|
256
307
|
|
|
257
308
|
return {
|
|
258
309
|
enabled: true,
|
|
259
|
-
boot: Effect.
|
|
260
|
-
|
|
310
|
+
boot: Effect.succeed({
|
|
311
|
+
node,
|
|
312
|
+
persistenceInfo: {
|
|
261
313
|
state: dbState.metadata.persistenceInfo,
|
|
262
314
|
eventlog: dbEventlog.metadata.persistenceInfo,
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return { node, persistenceInfo, mode: 'direct' }
|
|
315
|
+
},
|
|
316
|
+
mode: 'direct' as const,
|
|
266
317
|
}),
|
|
267
318
|
}
|
|
268
319
|
})
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Attempts to access OPFS and returns a warning if unavailable.
|
|
323
|
+
*
|
|
324
|
+
* Common failure scenarios:
|
|
325
|
+
* - Safari/Firefox private browsing: SecurityError or NotAllowedError
|
|
326
|
+
* - Permission denied: NotAllowedError
|
|
327
|
+
* - Quota exceeded: QuotaExceededError
|
|
328
|
+
*/
|
|
329
|
+
const checkOpfsAvailability = Effect.gen(function* () {
|
|
330
|
+
const opfs = yield* Opfs.Opfs
|
|
331
|
+
return yield* opfs.getRootDirectoryHandle.pipe(
|
|
332
|
+
Effect.as(undefined),
|
|
333
|
+
Effect.catchAll((error) => {
|
|
334
|
+
const reason: BootWarningReason =
|
|
335
|
+
Schema.is(WebError.SecurityError)(error) === true || Schema.is(WebError.NotAllowedError)(error) === true
|
|
336
|
+
? 'private-browsing'
|
|
337
|
+
: 'storage-unavailable'
|
|
338
|
+
const message =
|
|
339
|
+
reason === 'private-browsing'
|
|
340
|
+
? 'Storage unavailable in private browsing mode. LiveStore will continue without persistence.'
|
|
341
|
+
: 'Storage access denied. LiveStore will continue without persistence.'
|
|
342
|
+
return Effect.succeed({ reason, message } as const)
|
|
343
|
+
}),
|
|
344
|
+
)
|
|
345
|
+
})
|