@livestore/adapter-web 0.4.0-dev.8 → 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 +13 -20
- package/dist/web-worker/common/persisted-sqlite.d.ts.map +1 -1
- package/dist/web-worker/common/persisted-sqlite.js +95 -102
- 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 +99 -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 +215 -170
- 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 +149 -71
- package/src/web-worker/shared-worker/make-shared-worker.ts +78 -90
- package/dist/opfs-utils.d.ts +0 -5
- 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
|
|
@@ -137,6 +171,7 @@ const makeWorkerRunnerInner = ({ schema, sync: syncOptions }: WorkerOptions) =>
|
|
|
137
171
|
yield* cleanupOldStateDbFiles({
|
|
138
172
|
vfs: dbState.metadata.vfs,
|
|
139
173
|
currentSchema: schema,
|
|
174
|
+
opfsDirectory: dbState.metadata.persistenceInfo.opfsDirectory,
|
|
140
175
|
})
|
|
141
176
|
}
|
|
142
177
|
|
|
@@ -153,35 +188,33 @@ const makeWorkerRunnerInner = ({ schema, sync: syncOptions }: WorkerOptions) =>
|
|
|
153
188
|
dbEventlog,
|
|
154
189
|
devtoolsOptions,
|
|
155
190
|
shutdownChannel,
|
|
156
|
-
|
|
191
|
+
syncPayloadEncoded,
|
|
192
|
+
syncPayloadSchema,
|
|
193
|
+
...(bootWarning !== undefined ? { bootWarning } : {}),
|
|
157
194
|
})
|
|
158
195
|
}).pipe(
|
|
159
196
|
Effect.tapCauseLogPretty,
|
|
160
|
-
|
|
197
|
+
UnknownError.mapToUnknownError,
|
|
161
198
|
Effect.withPerformanceMeasure('@livestore/adapter-web:worker:InitialMessage'),
|
|
162
199
|
Effect.withSpan('@livestore/adapter-web:worker:InitialMessage'),
|
|
163
200
|
Effect.annotateSpans({ debugInstanceId }),
|
|
164
201
|
Layer.unwrapScoped,
|
|
165
202
|
),
|
|
166
|
-
GetRecreateSnapshot: ()
|
|
167
|
-
|
|
168
|
-
const workerCtx = yield* LeaderThreadCtx
|
|
203
|
+
GetRecreateSnapshot: Effect.fn('@livestore/adapter-web:worker:GetRecreateSnapshot')(function* () {
|
|
204
|
+
const workerCtx = yield* LeaderThreadCtx
|
|
169
205
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
|
173
209
|
|
|
174
|
-
|
|
210
|
+
// return cachedSnapshot ?? workerCtx.db.export()
|
|
175
211
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
UnexpectedError.mapToUnexpectedError,
|
|
180
|
-
Effect.withSpan('@livestore/adapter-web:worker:GetRecreateSnapshot'),
|
|
181
|
-
),
|
|
212
|
+
const snapshot = workerCtx.dbState.export()
|
|
213
|
+
return { snapshot, migrationsReport: workerCtx.initialState.migrationsReport }
|
|
214
|
+
}),
|
|
182
215
|
PullStream: ({ cursor }) =>
|
|
183
216
|
Effect.gen(function* () {
|
|
184
|
-
const { syncProcessor } = yield* LeaderThreadCtx
|
|
217
|
+
const { syncProcessor } = yield* LeaderThreadCtx // <- syncState comes from here
|
|
185
218
|
return syncProcessor.pull({ cursor })
|
|
186
219
|
}).pipe(
|
|
187
220
|
Stream.unwrapScoped,
|
|
@@ -191,47 +224,66 @@ const makeWorkerRunnerInner = ({ schema, sync: syncOptions }: WorkerOptions) =>
|
|
|
191
224
|
PushToLeader: ({ batch }) =>
|
|
192
225
|
Effect.andThen(LeaderThreadCtx, ({ syncProcessor }) =>
|
|
193
226
|
syncProcessor.push(
|
|
194
|
-
batch.map((event) => new LiveStoreEvent.EncodedWithMeta(event)),
|
|
227
|
+
batch.map((event) => new LiveStoreEvent.Client.EncodedWithMeta(event)),
|
|
195
228
|
// We'll wait in order to keep back pressure on the client session
|
|
196
229
|
{ waitForProcessing: true },
|
|
197
230
|
),
|
|
198
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
|
+
),
|
|
199
246
|
Export: () =>
|
|
200
247
|
Effect.andThen(LeaderThreadCtx, (_) => _.dbState.export()).pipe(
|
|
201
|
-
UnexpectedError.mapToUnexpectedError,
|
|
202
248
|
Effect.withSpan('@livestore/adapter-web:worker:Export'),
|
|
203
249
|
),
|
|
204
250
|
ExportEventlog: () =>
|
|
205
251
|
Effect.andThen(LeaderThreadCtx, (_) => _.dbEventlog.export()).pipe(
|
|
206
|
-
UnexpectedError.mapToUnexpectedError,
|
|
207
252
|
Effect.withSpan('@livestore/adapter-web:worker:ExportEventlog'),
|
|
208
253
|
),
|
|
209
254
|
BootStatusStream: () =>
|
|
210
255
|
Effect.andThen(LeaderThreadCtx, (_) => Stream.fromQueue(_.bootStatusQueue)).pipe(Stream.unwrap),
|
|
211
|
-
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: () =>
|
|
212
265
|
Effect.gen(function* () {
|
|
213
266
|
const workerCtx = yield* LeaderThreadCtx
|
|
214
|
-
return
|
|
215
|
-
}).pipe(
|
|
216
|
-
|
|
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: () =>
|
|
217
274
|
Effect.gen(function* () {
|
|
218
275
|
const workerCtx = yield* LeaderThreadCtx
|
|
219
|
-
return
|
|
220
|
-
}).pipe(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
),
|
|
224
|
-
Shutdown: () =>
|
|
225
|
-
Effect.gen(function* () {
|
|
226
|
-
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')
|
|
227
280
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
281
|
+
// Buy some time for Otel to flush
|
|
282
|
+
// TODO find a cleaner way to do this
|
|
283
|
+
yield* Effect.sleep(300)
|
|
284
|
+
}),
|
|
232
285
|
ExtraDevtoolsMessage: ({ message }) =>
|
|
233
286
|
Effect.andThen(LeaderThreadCtx, (_) => _.extraIncomingMessagesQueue.offer(message)).pipe(
|
|
234
|
-
UnexpectedError.mapToUnexpectedError,
|
|
235
287
|
Effect.withSpan('@livestore/adapter-web:worker:ExtraDevtoolsMessage'),
|
|
236
288
|
),
|
|
237
289
|
'DevtoolsWebCommon.CreateConnection': WebmeshWorker.CreateConnection,
|
|
@@ -245,7 +297,7 @@ const makeDevtoolsOptions = ({
|
|
|
245
297
|
devtoolsEnabled: boolean
|
|
246
298
|
dbState: SqliteDb
|
|
247
299
|
dbEventlog: SqliteDb
|
|
248
|
-
}): Effect.Effect<DevtoolsOptions,
|
|
300
|
+
}): Effect.Effect<DevtoolsOptions, UnknownError, Scope.Scope | WebmeshWorker.CacheService> =>
|
|
249
301
|
Effect.gen(function* () {
|
|
250
302
|
if (devtoolsEnabled === false) {
|
|
251
303
|
return { enabled: false }
|
|
@@ -255,13 +307,39 @@ const makeDevtoolsOptions = ({
|
|
|
255
307
|
|
|
256
308
|
return {
|
|
257
309
|
enabled: true,
|
|
258
|
-
boot: Effect.
|
|
259
|
-
|
|
310
|
+
boot: Effect.succeed({
|
|
311
|
+
node,
|
|
312
|
+
persistenceInfo: {
|
|
260
313
|
state: dbState.metadata.persistenceInfo,
|
|
261
314
|
eventlog: dbEventlog.metadata.persistenceInfo,
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
return { node, persistenceInfo, mode: 'direct' }
|
|
315
|
+
},
|
|
316
|
+
mode: 'direct' as const,
|
|
265
317
|
}),
|
|
266
318
|
}
|
|
267
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
|
+
})
|