@livestore/adapter-web 0.4.0-dev.21 → 0.4.0-dev.23
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.map +1 -1
- package/dist/in-memory/in-memory-adapter.js +8 -4
- 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 +1 -1
- package/dist/web-worker/client-session/client-session-devtools.d.ts.map +1 -1
- package/dist/web-worker/client-session/client-session-devtools.js +7 -7
- 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 +95 -36
- package/dist/web-worker/client-session/persisted-adapter.js.map +1 -1
- package/dist/web-worker/client-session/sqlite-loader.d.ts.map +1 -1
- package/dist/web-worker/client-session/sqlite-loader.js +1 -1
- package/dist/web-worker/client-session/sqlite-loader.js.map +1 -1
- package/dist/web-worker/common/persisted-sqlite.d.ts.map +1 -1
- package/dist/web-worker/common/persisted-sqlite.js +13 -11
- package/dist/web-worker/common/persisted-sqlite.js.map +1 -1
- package/dist/web-worker/common/worker-schema.d.ts +34 -31
- package/dist/web-worker/common/worker-schema.d.ts.map +1 -1
- package/dist/web-worker/common/worker-schema.js +18 -19
- package/dist/web-worker/common/worker-schema.js.map +1 -1
- package/dist/web-worker/leader-worker/make-leader-worker.d.ts +2 -2
- package/dist/web-worker/leader-worker/make-leader-worker.d.ts.map +1 -1
- package/dist/web-worker/leader-worker/make-leader-worker.js +59 -25
- package/dist/web-worker/leader-worker/make-leader-worker.js.map +1 -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 +15 -15
- package/dist/web-worker/shared-worker/make-shared-worker.js.map +1 -1
- package/package.json +56 -17
- package/src/in-memory/in-memory-adapter.ts +9 -5
- 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/client-session/client-session-devtools.ts +26 -27
- package/src/web-worker/client-session/persisted-adapter.ts +126 -64
- package/src/web-worker/client-session/sqlite-loader.ts +1 -1
- package/src/web-worker/common/persisted-sqlite.ts +13 -10
- package/src/web-worker/common/worker-schema.ts +19 -18
- package/src/web-worker/leader-worker/make-leader-worker.ts +94 -52
- package/src/web-worker/shared-worker/make-shared-worker.ts +26 -48
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import type * as otel from '@opentelemetry/api'
|
|
2
|
+
|
|
3
|
+
import type { BootStatus, BootWarningReason, SqliteDb, SyncOptions } from '@livestore/common'
|
|
2
4
|
import { Devtools, LogConfig, UnknownError } from '@livestore/common'
|
|
3
5
|
import type { DevtoolsOptions, StreamEventsOptions } from '@livestore/common/leader-thread'
|
|
4
6
|
import {
|
|
@@ -22,13 +24,12 @@ import {
|
|
|
22
24
|
Layer,
|
|
23
25
|
OtelTracer,
|
|
24
26
|
Scheduler,
|
|
25
|
-
|
|
27
|
+
Schema,
|
|
26
28
|
Stream,
|
|
27
29
|
TaskTracing,
|
|
28
30
|
WorkerRunner,
|
|
29
31
|
} from '@livestore/utils/effect'
|
|
30
|
-
import { BrowserWorkerRunner, Opfs } from '@livestore/utils/effect/browser'
|
|
31
|
-
import type * as otel from '@opentelemetry/api'
|
|
32
|
+
import { BrowserWorkerRunner, Opfs, WebError } from '@livestore/utils/effect/browser'
|
|
32
33
|
|
|
33
34
|
import { cleanupOldStateDbFiles, getStateDbFileName, sanitizeOpfsDir } from '../common/persisted-sqlite.ts'
|
|
34
35
|
import { makeShutdownChannel } from '../common/shutdown-channel.ts'
|
|
@@ -43,13 +44,13 @@ export type WorkerOptions = {
|
|
|
43
44
|
}
|
|
44
45
|
} & LogConfig.WithLoggerOptions
|
|
45
46
|
|
|
46
|
-
if (isDevEnv()) {
|
|
47
|
+
if (isDevEnv() === true) {
|
|
47
48
|
globalThis.__debugLiveStoreUtils = {
|
|
48
49
|
opfs: Opfs.debugUtils,
|
|
49
50
|
blobUrl: (buffer: Uint8Array<ArrayBuffer>) =>
|
|
50
51
|
URL.createObjectURL(new Blob([buffer], { type: 'application/octet-stream' })),
|
|
51
|
-
runSync: (effect: Effect.Effect<
|
|
52
|
-
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),
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -58,7 +59,7 @@ export const makeWorker = (options: WorkerOptions) => {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
export const makeWorkerEffect = (options: WorkerOptions) => {
|
|
61
|
-
const TracingLive = options.otelOptions?.tracer
|
|
62
|
+
const TracingLive = options.otelOptions?.tracer !== undefined
|
|
62
63
|
? Layer.unwrapEffect(Effect.map(OtelTracer.make, Layer.setTracer)).pipe(
|
|
63
64
|
Layer.provideMerge(Layer.succeed(OtelTracer.OtelTracer, options.otelOptions.tracer)),
|
|
64
65
|
)
|
|
@@ -73,7 +74,7 @@ export const makeWorkerEffect = (options: WorkerOptions) => {
|
|
|
73
74
|
Effect.tapCauseLogPretty,
|
|
74
75
|
Effect.annotateLogs({ thread: self.name }),
|
|
75
76
|
Effect.provide(runtimeLayer),
|
|
76
|
-
LS_DEV ? TaskTracing.withAsyncTaggingTracing((name) => (console as any).createTask(name)) : identity,
|
|
77
|
+
LS_DEV === true ? TaskTracing.withAsyncTaggingTracing((name) => (console as any).createTask(name)) : identity,
|
|
77
78
|
// We're using this custom scheduler to improve op batching behaviour and reduce the overhead
|
|
78
79
|
// of the Effect fiber runtime given we have different tradeoffs on a worker thread.
|
|
79
80
|
// Despite the "message channel" name, is has nothing to do with the `incomingRequestsPort` above.
|
|
@@ -118,13 +119,28 @@ const makeWorkerRunnerInner = ({ schema, sync: syncOptions, syncPayloadSchema }:
|
|
|
118
119
|
Effect.gen(function* () {
|
|
119
120
|
const sqlite3 = yield* Effect.promise(() => loadSqlite3Wasm())
|
|
120
121
|
const makeSqliteDb = sqliteDbFactory({ sqlite3 })
|
|
121
|
-
const
|
|
122
|
-
|
|
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
|
|
123
139
|
|
|
124
|
-
const
|
|
140
|
+
const makeOpfsDb = (kind: 'state' | 'eventlog') =>
|
|
125
141
|
makeSqliteDb({
|
|
126
142
|
_tag: 'opfs',
|
|
127
|
-
opfsDirectory
|
|
143
|
+
opfsDirectory: opfsDirectory!,
|
|
128
144
|
fileName: kind === 'state' ? getStateDbFileName(schema) : 'eventlog.db',
|
|
129
145
|
configureDb: (db) =>
|
|
130
146
|
configureConnection(db, {
|
|
@@ -137,10 +153,17 @@ const makeWorkerRunnerInner = ({ schema, sync: syncOptions, syncPayloadSchema }:
|
|
|
137
153
|
}).pipe(Effect.provide(runtime), Effect.runSync),
|
|
138
154
|
}).pipe(Effect.acquireRelease((db) => Effect.try(() => db.close()).pipe(Effect.ignoreLogged)))
|
|
139
155
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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 })
|
|
144
167
|
|
|
145
168
|
// Clean up old state database files after successful database creation
|
|
146
169
|
// This prevents OPFS file pool capacity exhaustion from accumulated state db files after schema changes/migrations
|
|
@@ -167,6 +190,7 @@ const makeWorkerRunnerInner = ({ schema, sync: syncOptions, syncPayloadSchema }:
|
|
|
167
190
|
shutdownChannel,
|
|
168
191
|
syncPayloadEncoded,
|
|
169
192
|
syncPayloadSchema,
|
|
193
|
+
...(bootWarning !== undefined ? { bootWarning } : {}),
|
|
170
194
|
})
|
|
171
195
|
}).pipe(
|
|
172
196
|
Effect.tapCauseLogPretty,
|
|
@@ -176,19 +200,18 @@ const makeWorkerRunnerInner = ({ schema, sync: syncOptions, syncPayloadSchema }:
|
|
|
176
200
|
Effect.annotateSpans({ debugInstanceId }),
|
|
177
201
|
Layer.unwrapScoped,
|
|
178
202
|
),
|
|
179
|
-
GetRecreateSnapshot: ()
|
|
180
|
-
|
|
181
|
-
const workerCtx = yield* LeaderThreadCtx
|
|
203
|
+
GetRecreateSnapshot: Effect.fn('@livestore/adapter-web:worker:GetRecreateSnapshot')(function* () {
|
|
204
|
+
const workerCtx = yield* LeaderThreadCtx
|
|
182
205
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
|
186
209
|
|
|
187
|
-
|
|
210
|
+
// return cachedSnapshot ?? workerCtx.db.export()
|
|
188
211
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
212
|
+
const snapshot = workerCtx.dbState.export()
|
|
213
|
+
return { snapshot, migrationsReport: workerCtx.initialState.migrationsReport }
|
|
214
|
+
}),
|
|
192
215
|
PullStream: ({ cursor }) =>
|
|
193
216
|
Effect.gen(function* () {
|
|
194
217
|
const { syncProcessor } = yield* LeaderThreadCtx // <- syncState comes from here
|
|
@@ -222,52 +245,45 @@ const makeWorkerRunnerInner = ({ schema, sync: syncOptions, syncPayloadSchema }:
|
|
|
222
245
|
),
|
|
223
246
|
Export: () =>
|
|
224
247
|
Effect.andThen(LeaderThreadCtx, (_) => _.dbState.export()).pipe(
|
|
225
|
-
UnknownError.mapToUnknownError,
|
|
226
248
|
Effect.withSpan('@livestore/adapter-web:worker:Export'),
|
|
227
249
|
),
|
|
228
250
|
ExportEventlog: () =>
|
|
229
251
|
Effect.andThen(LeaderThreadCtx, (_) => _.dbEventlog.export()).pipe(
|
|
230
|
-
UnknownError.mapToUnknownError,
|
|
231
252
|
Effect.withSpan('@livestore/adapter-web:worker:ExportEventlog'),
|
|
232
253
|
),
|
|
233
254
|
BootStatusStream: () =>
|
|
234
255
|
Effect.andThen(LeaderThreadCtx, (_) => Stream.fromQueue(_.bootStatusQueue)).pipe(Stream.unwrap),
|
|
235
|
-
GetLeaderHead: ()
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
return yield* workerCtx.syncProcessor.syncState
|
|
244
|
-
}).pipe(UnknownError.mapToUnknownError, Effect.withSpan('@livestore/adapter-web:worker:GetLeaderSyncState')),
|
|
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
|
+
}),
|
|
245
264
|
SyncStateStream: () =>
|
|
246
265
|
Effect.gen(function* () {
|
|
247
266
|
const workerCtx = yield* LeaderThreadCtx
|
|
248
267
|
return workerCtx.syncProcessor.syncState.changes
|
|
249
268
|
}).pipe(Stream.unwrapScoped),
|
|
250
|
-
GetNetworkStatus: ()
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}).pipe(UnknownError.mapToUnknownError, Effect.withSpan('@livestore/adapter-web:worker:GetNetworkStatus')),
|
|
269
|
+
GetNetworkStatus: Effect.fn('@livestore/adapter-web:worker:GetNetworkStatus')(function* () {
|
|
270
|
+
const workerCtx = yield* LeaderThreadCtx
|
|
271
|
+
return yield* workerCtx.networkStatus
|
|
272
|
+
}),
|
|
255
273
|
NetworkStatusStream: () =>
|
|
256
274
|
Effect.gen(function* () {
|
|
257
275
|
const workerCtx = yield* LeaderThreadCtx
|
|
258
276
|
return workerCtx.networkStatus.changes
|
|
259
277
|
}).pipe(Stream.unwrapScoped),
|
|
260
|
-
Shutdown: ()
|
|
261
|
-
Effect.
|
|
262
|
-
yield* Effect.logDebug('[@livestore/adapter-web:worker] Shutdown')
|
|
278
|
+
Shutdown: Effect.fn('@livestore/adapter-web:worker:Shutdown')(function* () {
|
|
279
|
+
yield* Effect.logDebug('[@livestore/adapter-web:worker] Shutdown')
|
|
263
280
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
281
|
+
// Buy some time for Otel to flush
|
|
282
|
+
// TODO find a cleaner way to do this
|
|
283
|
+
yield* Effect.sleep(300)
|
|
284
|
+
}),
|
|
268
285
|
ExtraDevtoolsMessage: ({ message }) =>
|
|
269
286
|
Effect.andThen(LeaderThreadCtx, (_) => _.extraIncomingMessagesQueue.offer(message)).pipe(
|
|
270
|
-
UnknownError.mapToUnknownError,
|
|
271
287
|
Effect.withSpan('@livestore/adapter-web:worker:ExtraDevtoolsMessage'),
|
|
272
288
|
),
|
|
273
289
|
'DevtoolsWebCommon.CreateConnection': WebmeshWorker.CreateConnection,
|
|
@@ -301,3 +317,29 @@ const makeDevtoolsOptions = ({
|
|
|
301
317
|
}),
|
|
302
318
|
}
|
|
303
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
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Devtools, LogConfig, liveStoreVersion, UnknownError } from '@livestore/common'
|
|
1
|
+
import { Devtools, isWorkerTransportError, LogConfig, liveStoreVersion, UnknownError } from '@livestore/common'
|
|
2
2
|
import * as DevtoolsWeb from '@livestore/devtools-web-common/web-channel'
|
|
3
3
|
import * as WebmeshWorker from '@livestore/devtools-web-common/worker'
|
|
4
4
|
import { isDevEnv, isNotUndefined, LS_DEV } from '@livestore/utils'
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
FetchHttpClient,
|
|
10
10
|
identity,
|
|
11
11
|
Layer,
|
|
12
|
-
|
|
12
|
+
Option,
|
|
13
13
|
Ref,
|
|
14
14
|
Schema,
|
|
15
15
|
Scope,
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
SubscriptionRef,
|
|
18
18
|
TaskTracing,
|
|
19
19
|
Worker,
|
|
20
|
-
WorkerError,
|
|
21
20
|
WorkerRunner,
|
|
22
21
|
} from '@livestore/utils/effect'
|
|
23
22
|
import { BrowserWorker, BrowserWorkerRunner } from '@livestore/utils/effect/browser'
|
|
@@ -39,15 +38,16 @@ navigator.locks.request(
|
|
|
39
38
|
async () => new Promise(() => {}),
|
|
40
39
|
)
|
|
41
40
|
|
|
42
|
-
if (isDevEnv()) {
|
|
41
|
+
if (isDevEnv() === true) {
|
|
43
42
|
globalThis.__debugLiveStoreUtils = {
|
|
44
43
|
blobUrl: (buffer: Uint8Array<ArrayBuffer>) =>
|
|
45
44
|
URL.createObjectURL(new Blob([buffer], { type: 'application/octet-stream' })),
|
|
46
|
-
runSync: (effect: Effect.Effect<
|
|
47
|
-
runFork: (effect: Effect.Effect<
|
|
45
|
+
runSync: <A, E>(effect: Effect.Effect<A, E>) => Effect.runSync(effect),
|
|
46
|
+
runFork: <A, E>(effect: Effect.Effect<A, E>) => Effect.runFork(effect),
|
|
48
47
|
}
|
|
49
48
|
}
|
|
50
49
|
|
|
50
|
+
// @effect-diagnostics-next-line anyUnknownInErrorContext:off -- `SerializedRunner.Handlers` uses `any` in the R channel, propagating as `unknown` in `HandlersContext`
|
|
51
51
|
const makeWorkerRunner = Effect.gen(function* () {
|
|
52
52
|
const leaderWorkerContextSubRef = yield* SubscriptionRef.make<
|
|
53
53
|
| {
|
|
@@ -61,17 +61,14 @@ const makeWorkerRunner = Effect.gen(function* () {
|
|
|
61
61
|
Effect.map((_) => _.worker),
|
|
62
62
|
)
|
|
63
63
|
|
|
64
|
-
const forwardRequest = <
|
|
65
|
-
req:
|
|
66
|
-
): Effect.Effect<
|
|
67
|
-
|
|
68
|
-
UnknownError | Schema.WithResult.Failure<TReq>,
|
|
69
|
-
Schema.WithResult.Context<TReq>
|
|
70
|
-
> =>
|
|
71
|
-
// Forward the request to the active worker and normalize platform errors into UnknownError.
|
|
64
|
+
const forwardRequest = <A, I, E, EI, R>(
|
|
65
|
+
req: WorkerSchema.LeaderWorkerInnerRequest & Schema.WithResult<A, I, E, EI, R>,
|
|
66
|
+
): Effect.Effect<A, E, R> =>
|
|
67
|
+
// Forward the request to the active worker and convert transport errors to defects.
|
|
72
68
|
waitForWorker.pipe(
|
|
73
69
|
// Effect.logBefore(`forwardRequest: ${req._tag}`),
|
|
74
|
-
Effect.andThen((worker) => worker.executeEffect(req)
|
|
70
|
+
Effect.andThen((worker) => worker.executeEffect(req)),
|
|
71
|
+
Effect.catchIf(isWorkerTransportError, (e) => Effect.die(e)),
|
|
75
72
|
// Effect.tap((_) => Effect.log(`forwardRequest: ${req._tag}`, _)),
|
|
76
73
|
// Effect.tapError((cause) => Effect.logError(`forwardRequest err: ${req._tag}`, cause)),
|
|
77
74
|
Effect.interruptible,
|
|
@@ -79,33 +76,18 @@ const makeWorkerRunner = Effect.gen(function* () {
|
|
|
79
76
|
label: `@livestore/adapter-web:shared-worker:forwardRequest:${req._tag}`,
|
|
80
77
|
duration: 500,
|
|
81
78
|
}),
|
|
82
|
-
Effect.mapError((cause) =>
|
|
83
|
-
Schema.is(UnknownError)(cause)
|
|
84
|
-
? cause
|
|
85
|
-
: ParseResult.isParseError(cause) || Schema.is(WorkerError.WorkerError)(cause)
|
|
86
|
-
? new UnknownError({ cause })
|
|
87
|
-
: cause,
|
|
88
|
-
),
|
|
89
|
-
Effect.catchAllDefect((cause) => new UnknownError({ cause })),
|
|
90
79
|
Effect.tapCauseLogPretty,
|
|
91
|
-
)
|
|
92
|
-
Schema.WithResult.Success<TReq>,
|
|
93
|
-
UnknownError | Schema.WithResult.Failure<TReq>,
|
|
94
|
-
Schema.WithResult.Context<TReq>
|
|
95
|
-
>
|
|
80
|
+
)
|
|
96
81
|
|
|
97
|
-
const forwardRequestStream = <
|
|
98
|
-
req:
|
|
99
|
-
): Stream.Stream<
|
|
100
|
-
Schema.WithResult.Success<TReq>,
|
|
101
|
-
UnknownError | Schema.WithResult.Failure<TReq>,
|
|
102
|
-
Schema.WithResult.Context<TReq>
|
|
103
|
-
> =>
|
|
82
|
+
const forwardRequestStream = <A, I, E, EI, R>(
|
|
83
|
+
req: WorkerSchema.LeaderWorkerInnerRequest & Schema.WithResult<A, I, E, EI, R>,
|
|
84
|
+
): Stream.Stream<A, E, R> =>
|
|
104
85
|
Effect.gen(function* () {
|
|
105
86
|
yield* Effect.logDebug(`forwardRequestStream: ${req._tag}`)
|
|
106
87
|
const { worker, scope } = yield* SubscriptionRef.waitUntil(leaderWorkerContextSubRef, isNotUndefined)
|
|
107
|
-
const stream = worker.execute(req)
|
|
108
|
-
|
|
88
|
+
const stream = worker.execute(req).pipe(
|
|
89
|
+
Stream.refineOrDie((e) => isWorkerTransportError(e) === true ? Option.none() : Option.some(e)),
|
|
90
|
+
)
|
|
109
91
|
// It seems the request stream is not automatically interrupted when the scope shuts down
|
|
110
92
|
// so we need to manually interrupt it when the scope shuts down
|
|
111
93
|
const shutdownDeferred = yield* Deferred.make<void>()
|
|
@@ -120,16 +102,10 @@ const makeWorkerRunner = Effect.gen(function* () {
|
|
|
120
102
|
return Stream.merge(stream, scopeShutdownStream, { haltStrategy: 'either' })
|
|
121
103
|
}).pipe(
|
|
122
104
|
Effect.interruptible,
|
|
123
|
-
UnknownError.mapToUnknownError,
|
|
124
105
|
Effect.tapCauseLogPretty,
|
|
125
106
|
Stream.unwrap,
|
|
126
107
|
Stream.ensuring(Effect.logDebug(`shutting down stream for ${req._tag}`)),
|
|
127
|
-
|
|
128
|
-
) as Stream.Stream<
|
|
129
|
-
Schema.WithResult.Success<TReq>,
|
|
130
|
-
UnknownError | Schema.WithResult.Failure<TReq>,
|
|
131
|
-
Schema.WithResult.Context<TReq>
|
|
132
|
-
>
|
|
108
|
+
)
|
|
133
109
|
|
|
134
110
|
const resetCurrentWorkerCtx = Effect.gen(function* () {
|
|
135
111
|
const prevWorker = yield* SubscriptionRef.get(leaderWorkerContextSubRef)
|
|
@@ -168,6 +144,7 @@ const makeWorkerRunner = Effect.gen(function* () {
|
|
|
168
144
|
const invariantsRef = yield* Ref.make<Invariants | undefined>(undefined)
|
|
169
145
|
const sameInvariants = Schema.equivalence(InvariantsSchema)
|
|
170
146
|
|
|
147
|
+
// @effect-diagnostics-next-line anyUnknownInErrorContext:off -- `SerializedRunner.Handlers` uses `any` in the R channel
|
|
171
148
|
return WorkerRunner.layerSerialized(WorkerSchema.SharedWorkerRequest, {
|
|
172
149
|
// Whenever the client session leader changes (and thus creates a new leader thread), the new client session leader
|
|
173
150
|
// sends a new MessagePort to the shared worker which proxies messages to the new leader thread.
|
|
@@ -183,7 +160,7 @@ const makeWorkerRunner = Effect.gen(function* () {
|
|
|
183
160
|
}
|
|
184
161
|
const prev = yield* Ref.get(invariantsRef)
|
|
185
162
|
// Early return on mismatch to keep happy path linear
|
|
186
|
-
if (prev !== undefined &&
|
|
163
|
+
if (prev !== undefined && sameInvariants(prev, invariants) === false) {
|
|
187
164
|
const diff = Schema.debugDiff(InvariantsSchema)(prev, invariants)
|
|
188
165
|
return yield* new UnknownError({
|
|
189
166
|
cause: 'Store invariants changed across leader transitions',
|
|
@@ -235,7 +212,6 @@ const makeWorkerRunner = Effect.gen(function* () {
|
|
|
235
212
|
}).pipe(Effect.tapCauseLogPretty, Scope.extend(scope), Effect.forkIn(scope))
|
|
236
213
|
}).pipe(
|
|
237
214
|
Effect.withSpan('@livestore/adapter-web:shared-worker:updateMessagePort'),
|
|
238
|
-
UnknownError.mapToUnknownError,
|
|
239
215
|
Effect.tapCauseLogPretty,
|
|
240
216
|
),
|
|
241
217
|
|
|
@@ -267,6 +243,7 @@ export const makeWorker = (options?: LogConfig.WithLoggerOptions): void => {
|
|
|
267
243
|
WebmeshWorker.CacheService.layer({ nodeName: DevtoolsWeb.makeNodeName.sharedWorker({ storeId }) }),
|
|
268
244
|
)
|
|
269
245
|
|
|
246
|
+
// @effect-diagnostics-next-line anyUnknownInErrorContext:off -- propagated from `makeWorkerRunner`
|
|
270
247
|
makeWorkerRunner.pipe(
|
|
271
248
|
Layer.provide(BrowserWorkerRunner.layer),
|
|
272
249
|
// WorkerRunner.launch,
|
|
@@ -275,9 +252,10 @@ export const makeWorker = (options?: LogConfig.WithLoggerOptions): void => {
|
|
|
275
252
|
Effect.tapCauseLogPretty,
|
|
276
253
|
Effect.annotateLogs({ thread: self.name }),
|
|
277
254
|
Effect.provide(runtimeLayer),
|
|
278
|
-
LS_DEV ? TaskTracing.withAsyncTaggingTracing((name) => (console as any).createTask(name)) : identity,
|
|
255
|
+
LS_DEV === true ? TaskTracing.withAsyncTaggingTracing((name) => (console as any).createTask(name)) : identity,
|
|
279
256
|
// TODO remove type-cast (currently needed to silence a tsc bug)
|
|
280
|
-
|
|
257
|
+
// @effect-diagnostics-next-line anyUnknownInErrorContext:off -- TSC bug workaround; the cast uses `any` as an intermediate
|
|
258
|
+
(_) => _ as any as Effect.Effect<void>,
|
|
281
259
|
LogConfig.withLoggerConfig(options, { threadName: self.name }),
|
|
282
260
|
Effect.runFork,
|
|
283
261
|
)
|