@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.
Files changed (54) hide show
  1. package/README.md +5 -5
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/in-memory/in-memory-adapter.d.ts.map +1 -1
  4. package/dist/in-memory/in-memory-adapter.js +8 -4
  5. package/dist/in-memory/in-memory-adapter.js.map +1 -1
  6. package/dist/index.d.ts +11 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +11 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/single-tab/mod.d.ts +15 -0
  11. package/dist/single-tab/mod.d.ts.map +1 -0
  12. package/dist/single-tab/mod.js +15 -0
  13. package/dist/single-tab/mod.js.map +1 -0
  14. package/dist/single-tab/single-tab-adapter.d.ts +108 -0
  15. package/dist/single-tab/single-tab-adapter.d.ts.map +1 -0
  16. package/dist/single-tab/single-tab-adapter.js +271 -0
  17. package/dist/single-tab/single-tab-adapter.js.map +1 -0
  18. package/dist/web-worker/client-session/client-session-devtools.d.ts +1 -1
  19. package/dist/web-worker/client-session/client-session-devtools.d.ts.map +1 -1
  20. package/dist/web-worker/client-session/client-session-devtools.js +7 -7
  21. package/dist/web-worker/client-session/client-session-devtools.js.map +1 -1
  22. package/dist/web-worker/client-session/persisted-adapter.d.ts +18 -0
  23. package/dist/web-worker/client-session/persisted-adapter.d.ts.map +1 -1
  24. package/dist/web-worker/client-session/persisted-adapter.js +95 -36
  25. package/dist/web-worker/client-session/persisted-adapter.js.map +1 -1
  26. package/dist/web-worker/client-session/sqlite-loader.d.ts.map +1 -1
  27. package/dist/web-worker/client-session/sqlite-loader.js +1 -1
  28. package/dist/web-worker/client-session/sqlite-loader.js.map +1 -1
  29. package/dist/web-worker/common/persisted-sqlite.d.ts.map +1 -1
  30. package/dist/web-worker/common/persisted-sqlite.js +13 -11
  31. package/dist/web-worker/common/persisted-sqlite.js.map +1 -1
  32. package/dist/web-worker/common/worker-schema.d.ts +34 -31
  33. package/dist/web-worker/common/worker-schema.d.ts.map +1 -1
  34. package/dist/web-worker/common/worker-schema.js +18 -19
  35. package/dist/web-worker/common/worker-schema.js.map +1 -1
  36. package/dist/web-worker/leader-worker/make-leader-worker.d.ts +2 -2
  37. package/dist/web-worker/leader-worker/make-leader-worker.d.ts.map +1 -1
  38. package/dist/web-worker/leader-worker/make-leader-worker.js +59 -25
  39. package/dist/web-worker/leader-worker/make-leader-worker.js.map +1 -1
  40. package/dist/web-worker/shared-worker/make-shared-worker.d.ts.map +1 -1
  41. package/dist/web-worker/shared-worker/make-shared-worker.js +15 -15
  42. package/dist/web-worker/shared-worker/make-shared-worker.js.map +1 -1
  43. package/package.json +56 -17
  44. package/src/in-memory/in-memory-adapter.ts +9 -5
  45. package/src/index.ts +15 -1
  46. package/src/single-tab/mod.ts +15 -0
  47. package/src/single-tab/single-tab-adapter.ts +499 -0
  48. package/src/web-worker/client-session/client-session-devtools.ts +26 -27
  49. package/src/web-worker/client-session/persisted-adapter.ts +126 -64
  50. package/src/web-worker/client-session/sqlite-loader.ts +1 -1
  51. package/src/web-worker/common/persisted-sqlite.ts +13 -10
  52. package/src/web-worker/common/worker-schema.ts +19 -18
  53. package/src/web-worker/leader-worker/make-leader-worker.ts +94 -52
  54. package/src/web-worker/shared-worker/make-shared-worker.ts +26 -48
@@ -1,4 +1,6 @@
1
- import type { SqliteDb, SyncOptions } from '@livestore/common'
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
- type Schema,
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<any, any, never>) => Effect.runSync(effect),
52
- runFork: (effect: Effect.Effect<any, any, never>) => Effect.runFork(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 opfsDirectory = yield* sanitizeOpfsDir(storageOptions.directory, storeId)
122
- const runtime = yield* Effect.runtime<never>()
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 makeDb = (kind: 'state' | 'eventlog') =>
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
- // Might involve some async work, so we're running them concurrently
141
- const [dbState, dbEventlog] = yield* Effect.all([makeDb('state'), makeDb('eventlog')], {
142
- concurrency: 2,
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
- Effect.gen(function* () {
181
- const workerCtx = yield* LeaderThreadCtx
203
+ GetRecreateSnapshot: Effect.fn('@livestore/adapter-web:worker:GetRecreateSnapshot')(function* () {
204
+ const workerCtx = yield* LeaderThreadCtx
182
205
 
183
- // NOTE we can only return the cached snapshot once as it's transferred (i.e. disposed), so we need to set it to undefined
184
- // const cachedSnapshot =
185
- // result._tag === 'Recreate' ? yield* Ref.getAndSet(result.snapshotRef, undefined) : undefined
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
- // return cachedSnapshot ?? workerCtx.db.export()
210
+ // return cachedSnapshot ?? workerCtx.db.export()
188
211
 
189
- const snapshot = workerCtx.dbState.export()
190
- return { snapshot, migrationsReport: workerCtx.initialState.migrationsReport }
191
- }).pipe(UnknownError.mapToUnknownError, Effect.withSpan('@livestore/adapter-web:worker:GetRecreateSnapshot')),
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
- Effect.gen(function* () {
237
- const workerCtx = yield* LeaderThreadCtx
238
- return Eventlog.getClientHeadFromDb(workerCtx.dbEventlog)
239
- }).pipe(UnknownError.mapToUnknownError, Effect.withSpan('@livestore/adapter-web:worker:GetLeaderHead')),
240
- GetLeaderSyncState: () =>
241
- Effect.gen(function* () {
242
- const workerCtx = yield* LeaderThreadCtx
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
- Effect.gen(function* () {
252
- const workerCtx = yield* LeaderThreadCtx
253
- return yield* workerCtx.networkStatus
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.gen(function* () {
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
- // Buy some time for Otel to flush
265
- // TODO find a cleaner way to do this
266
- yield* Effect.sleep(300)
267
- }).pipe(UnknownError.mapToUnknownError, Effect.withSpan('@livestore/adapter-web:worker:Shutdown')),
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
- ParseResult,
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<any, any, never>) => Effect.runSync(effect),
47
- runFork: (effect: Effect.Effect<any, any, never>) => Effect.runFork(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 = <TReq extends WorkerSchema.LeaderWorkerInnerRequest>(
65
- req: TReq,
66
- ): Effect.Effect<
67
- Schema.WithResult.Success<TReq>,
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) as Effect.Effect<unknown, unknown, unknown>),
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
- ) as Effect.Effect<
92
- Schema.WithResult.Success<TReq>,
93
- UnknownError | Schema.WithResult.Failure<TReq>,
94
- Schema.WithResult.Context<TReq>
95
- >
80
+ )
96
81
 
97
- const forwardRequestStream = <TReq extends WorkerSchema.LeaderWorkerInnerRequest>(
98
- req: TReq,
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) as Stream.Stream<unknown, unknown, unknown>
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
- UnknownError.mapToUnknownErrorStream,
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 && !sameInvariants(prev, invariants)) {
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
- (_) => _ as any as Effect.Effect<void, any>,
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
  )