@livestore/adapter-web 0.4.0-dev.22 → 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 (41) hide show
  1. package/README.md +5 -5
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/in-memory/in-memory-adapter.js +3 -3
  4. package/dist/in-memory/in-memory-adapter.js.map +1 -1
  5. package/dist/single-tab/single-tab-adapter.d.ts.map +1 -1
  6. package/dist/single-tab/single-tab-adapter.js +17 -25
  7. package/dist/single-tab/single-tab-adapter.js.map +1 -1
  8. package/dist/web-worker/client-session/client-session-devtools.d.ts +1 -1
  9. package/dist/web-worker/client-session/client-session-devtools.d.ts.map +1 -1
  10. package/dist/web-worker/client-session/client-session-devtools.js +7 -7
  11. package/dist/web-worker/client-session/client-session-devtools.js.map +1 -1
  12. package/dist/web-worker/client-session/persisted-adapter.d.ts.map +1 -1
  13. package/dist/web-worker/client-session/persisted-adapter.js +25 -33
  14. package/dist/web-worker/client-session/persisted-adapter.js.map +1 -1
  15. package/dist/web-worker/client-session/sqlite-loader.d.ts.map +1 -1
  16. package/dist/web-worker/client-session/sqlite-loader.js +1 -1
  17. package/dist/web-worker/client-session/sqlite-loader.js.map +1 -1
  18. package/dist/web-worker/common/persisted-sqlite.d.ts.map +1 -1
  19. package/dist/web-worker/common/persisted-sqlite.js +12 -10
  20. package/dist/web-worker/common/persisted-sqlite.js.map +1 -1
  21. package/dist/web-worker/common/worker-schema.d.ts +26 -27
  22. package/dist/web-worker/common/worker-schema.d.ts.map +1 -1
  23. package/dist/web-worker/common/worker-schema.js +18 -19
  24. package/dist/web-worker/common/worker-schema.js.map +1 -1
  25. package/dist/web-worker/leader-worker/make-leader-worker.d.ts +1 -1
  26. package/dist/web-worker/leader-worker/make-leader-worker.d.ts.map +1 -1
  27. package/dist/web-worker/leader-worker/make-leader-worker.js +20 -20
  28. package/dist/web-worker/leader-worker/make-leader-worker.js.map +1 -1
  29. package/dist/web-worker/shared-worker/make-shared-worker.d.ts.map +1 -1
  30. package/dist/web-worker/shared-worker/make-shared-worker.js +15 -15
  31. package/dist/web-worker/shared-worker/make-shared-worker.js.map +1 -1
  32. package/package.json +56 -17
  33. package/src/in-memory/in-memory-adapter.ts +4 -4
  34. package/src/single-tab/single-tab-adapter.ts +34 -52
  35. package/src/web-worker/client-session/client-session-devtools.ts +26 -27
  36. package/src/web-worker/client-session/persisted-adapter.ts +41 -60
  37. package/src/web-worker/client-session/sqlite-loader.ts +1 -1
  38. package/src/web-worker/common/persisted-sqlite.ts +12 -9
  39. package/src/web-worker/common/worker-schema.ts +19 -18
  40. package/src/web-worker/leader-worker/make-leader-worker.ts +39 -46
  41. package/src/web-worker/shared-worker/make-shared-worker.ts +26 -48
@@ -16,6 +16,7 @@
16
16
  import type { Adapter, BootWarningReason, ClientSession, LockStatus } from '@livestore/common'
17
17
  import {
18
18
  IntentionalShutdownCause,
19
+ isWorkerTransportError,
19
20
  makeClientSession,
20
21
  StoreInterrupted,
21
22
  sessionChangesetMetaTable,
@@ -30,17 +31,17 @@ import {
30
31
  Exit,
31
32
  Fiber,
32
33
  Layer,
33
- ParseResult,
34
+ Option,
34
35
  Queue,
35
36
  Schema,
36
37
  Stream,
37
38
  Subscribable,
38
39
  SubscriptionRef,
39
40
  Worker,
40
- WorkerError,
41
41
  } from '@livestore/utils/effect'
42
42
  import { BrowserWorker, Opfs, WebError } from '@livestore/utils/effect/browser'
43
43
  import { nanoid } from '@livestore/utils/nanoid'
44
+
44
45
  import { loadSqlite3 } from '../web-worker/client-session/sqlite-loader.ts'
45
46
  import {
46
47
  readPersistedStateDbFromClientSession,
@@ -193,7 +194,7 @@ export const makeSingleTabAdapter =
193
194
  yield* shutdownChannel.listen.pipe(
194
195
  Stream.flatten(),
195
196
  Stream.tap((cause) =>
196
- shutdown(cause._tag === 'LiveStore.IntentionalShutdownCause' ? Exit.succeed(cause) : Exit.fail(cause)),
197
+ shutdown(cause._tag === 'IntentionalShutdownCause' ? Exit.succeed(cause) : Exit.fail(cause)),
197
198
  ),
198
199
  Stream.runDrain,
199
200
  Effect.interruptible,
@@ -241,60 +242,43 @@ export const makeSingleTabAdapter =
241
242
  }).pipe(
242
243
  Effect.provide(innerWorkerContext),
243
244
  Effect.tapCauseLogPretty,
244
- UnknownError.mapToUnknownError,
245
+ Effect.orDie,
245
246
  Effect.tapErrorCause((cause) => shutdown(Exit.failCause(cause))),
246
247
  Effect.withSpan('@livestore/adapter-web:single-tab:setupInnerWorker'),
247
248
  Effect.forkScoped,
248
249
  )
249
250
 
250
251
  // Helper to run requests against the worker
251
- const runInWorker = <TReq extends WorkerSchema.LeaderWorkerInnerRequest>(
252
- req: TReq,
253
- ): TReq extends Schema.WithResult<infer A, infer _I, infer E, infer _EI, infer R>
254
- ? Effect.Effect<A, UnknownError | E, R>
255
- : never =>
252
+ const runInWorker = <A, I, E, EI, R>(
253
+ req: WorkerSchema.LeaderWorkerInnerRequest & Schema.WithResult<A, I, E, EI, R>,
254
+ ): Effect.Effect<A, E, R> =>
256
255
  Fiber.join(innerWorkerFiber).pipe(
257
- Effect.flatMap((worker) => worker.executeEffect(req) as any),
256
+ Effect.flatMap((worker) => worker.executeEffect(req)),
257
+ Effect.catchIf(isWorkerTransportError, (e) => Effect.die(e)),
258
258
  Effect.logWarnIfTakesLongerThan({
259
259
  label: `@livestore/adapter-web:single-tab:runInWorker:${req._tag}`,
260
260
  duration: 2000,
261
261
  }),
262
262
  Effect.withSpan(`@livestore/adapter-web:single-tab:runInWorker:${req._tag}`),
263
- Effect.mapError((cause) =>
264
- Schema.is(UnknownError)(cause)
265
- ? cause
266
- : ParseResult.isParseError(cause) || Schema.is(WorkerError.WorkerError)(cause)
267
- ? new UnknownError({ cause })
268
- : cause,
269
- ),
270
- Effect.catchAllDefect((cause) => new UnknownError({ cause })),
271
- ) as any
272
-
273
- const runInWorkerStream = <TReq extends WorkerSchema.LeaderWorkerInnerRequest>(
274
- req: TReq,
275
- ): TReq extends Schema.WithResult<infer A, infer _I, infer _E, infer _EI, infer R>
276
- ? Stream.Stream<A, UnknownError, R>
277
- : never =>
263
+ )
264
+
265
+ const runInWorkerStream = <A, I, E, EI, R>(
266
+ req: WorkerSchema.LeaderWorkerInnerRequest & Schema.WithResult<A, I, E, EI, R>,
267
+ ): Stream.Stream<A, E, R> =>
278
268
  Effect.gen(function* () {
279
269
  const innerWorker = yield* Fiber.join(innerWorkerFiber)
280
- return innerWorker.execute(req as any).pipe(
281
- Stream.mapError((cause) =>
282
- Schema.is(UnknownError)(cause)
283
- ? cause
284
- : ParseResult.isParseError(cause) || Schema.is(WorkerError.WorkerError)(cause)
285
- ? new UnknownError({ cause })
286
- : cause,
287
- ),
270
+ return innerWorker.execute(req).pipe(
271
+ Stream.refineOrDie((e) => isWorkerTransportError(e) === true ? Option.none() : Option.some(e)),
288
272
  Stream.withSpan(`@livestore/adapter-web:single-tab:runInWorkerStream:${req._tag}`),
289
273
  )
290
- }).pipe(Stream.unwrap) as any
274
+ }).pipe(Stream.unwrap)
291
275
 
292
276
  // Forward boot status from worker
293
277
  const bootStatusFiber = yield* runInWorkerStream(new WorkerSchema.LeaderWorkerInnerBootStatusStream()).pipe(
294
278
  Stream.tap((_) => Queue.offer(bootStatusQueue, _)),
295
279
  Stream.runDrain,
296
280
  Effect.tapErrorCause((cause) =>
297
- Cause.isInterruptedOnly(cause) ? Effect.void : shutdown(Exit.failCause(cause)),
281
+ Cause.isInterruptedOnly(cause) === true ? Effect.void : shutdown(Exit.failCause(cause)),
298
282
  ),
299
283
  Effect.interruptible,
300
284
  Effect.tapCauseLogPretty,
@@ -347,18 +331,19 @@ export const makeSingleTabAdapter =
347
331
  .first(),
348
332
  )
349
333
 
350
- const initialLeaderHead = initialLeaderHeadRes
351
- ? EventSequenceNumber.Client.Composite.make({
352
- global: initialLeaderHeadRes.seqNumGlobal,
353
- client: initialLeaderHeadRes.seqNumClient,
354
- rebaseGeneration: initialLeaderHeadRes.seqNumRebaseGeneration,
355
- })
356
- : EventSequenceNumber.Client.ROOT
334
+ const initialLeaderHead =
335
+ initialLeaderHeadRes !== undefined
336
+ ? EventSequenceNumber.Client.Composite.make({
337
+ global: initialLeaderHeadRes.seqNumGlobal,
338
+ client: initialLeaderHeadRes.seqNumClient,
339
+ rebaseGeneration: initialLeaderHeadRes.seqNumRebaseGeneration,
340
+ })
341
+ : EventSequenceNumber.Client.ROOT
357
342
 
358
343
  yield* Effect.addFinalizer((ex) =>
359
344
  Effect.gen(function* () {
360
345
  if (
361
- Exit.isFailure(ex) &&
346
+ Exit.isFailure(ex) === true &&
362
347
  Exit.isInterrupted(ex) === false &&
363
348
  Schema.is(IntentionalShutdownCause)(Cause.squash(ex.cause)) === false &&
364
349
  Schema.is(StoreInterrupted)(Cause.squash(ex.cause)) === false
@@ -372,8 +357,7 @@ export const makeSingleTabAdapter =
372
357
 
373
358
  const leaderThread: ClientSession['leaderThread'] = {
374
359
  export: runInWorker(new WorkerSchema.LeaderWorkerInnerExport()).pipe(
375
- Effect.timeout(10_000),
376
- UnknownError.mapToUnknownError,
360
+ Effect.timeoutOrDie(10_000),
377
361
  Effect.withSpan('@livestore/adapter-web:single-tab:export'),
378
362
  ),
379
363
 
@@ -400,14 +384,12 @@ export const makeSingleTabAdapter =
400
384
  },
401
385
 
402
386
  getEventlogData: runInWorker(new WorkerSchema.LeaderWorkerInnerExportEventlog()).pipe(
403
- Effect.timeout(10_000),
404
- UnknownError.mapToUnknownError,
387
+ Effect.timeoutOrDie(10_000),
405
388
  Effect.withSpan('@livestore/adapter-web:single-tab:getEventlogData'),
406
389
  ),
407
390
 
408
391
  syncState: Subscribable.make({
409
392
  get: runInWorker(new WorkerSchema.LeaderWorkerInnerGetLeaderSyncState()).pipe(
410
- UnknownError.mapToUnknownError,
411
393
  Effect.withSpan('@livestore/adapter-web:single-tab:getLeaderSyncState'),
412
394
  ),
413
395
  changes: runInWorkerStream(new WorkerSchema.LeaderWorkerInnerSyncStateStream()).pipe(Stream.orDie),
@@ -458,7 +440,7 @@ const getPersistedId = (key: string, storageType: 'session' | 'local') => {
458
440
  ? sessionStorage
459
441
  : storageType === 'local'
460
442
  ? localStorage
461
- : shouldNeverHappen(`[@livestore/adapter-web] Invalid storage type: ${storageType}`)
443
+ : shouldNeverHappen(`[@livestore/adapter-web] Invalid storage type: ${String(storageType)}`)
462
444
 
463
445
  if (storage === undefined) {
464
446
  return makeId()
@@ -467,7 +449,7 @@ const getPersistedId = (key: string, storageType: 'session' | 'local') => {
467
449
  const fullKey = `livestore:${key}`
468
450
  const storedKey = storage.getItem(fullKey)
469
451
 
470
- if (storedKey) return storedKey
452
+ if (storedKey !== null) return storedKey
471
453
 
472
454
  const newKey = makeId()
473
455
  storage.setItem(fullKey, newKey)
@@ -478,7 +460,7 @@ const getPersistedId = (key: string, storageType: 'session' | 'local') => {
478
460
  const ensureBrowserRequirements = Effect.gen(function* () {
479
461
  const validate = (condition: boolean, label: string) =>
480
462
  Effect.gen(function* () {
481
- if (condition) {
463
+ if (condition === true) {
482
464
  return yield* UnknownError.make({
483
465
  cause: `[@livestore/adapter-web] Browser not supported. The LiveStore web adapter needs '${label}' to work properly`,
484
466
  })
@@ -504,7 +486,7 @@ const checkOpfsAvailability = Effect.gen(function* () {
504
486
  Effect.as(undefined),
505
487
  Effect.catchAll((error) => {
506
488
  const reason: BootWarningReason =
507
- Schema.is(WebError.SecurityError)(error) || Schema.is(WebError.NotAllowedError)(error)
489
+ Schema.is(WebError.SecurityError)(error) === true || Schema.is(WebError.NotAllowedError)(error) === true
508
490
  ? 'private-browsing'
509
491
  : 'storage-unavailable'
510
492
  const message =
@@ -7,7 +7,7 @@ import { Effect, Stream } from '@livestore/utils/effect'
7
7
  import { WebChannelBrowser } from '@livestore/utils/effect/browser'
8
8
  import * as Webmesh from '@livestore/webmesh'
9
9
 
10
- export const logDevtoolsUrl = ({
10
+ export const logDevtoolsUrl = Effect.fn('@livestore/adapter-web:client-session:devtools:logDevtoolsUrl')(function* ({
11
11
  schema,
12
12
  storeId,
13
13
  clientId,
@@ -17,37 +17,36 @@ export const logDevtoolsUrl = ({
17
17
  storeId: string
18
18
  clientId: string
19
19
  sessionId: string
20
- }) =>
21
- Effect.gen(function* () {
22
- if (isDevEnv()) {
23
- const devtoolsPath = globalThis.LIVESTORE_DEVTOOLS_PATH ?? `/_livestore`
24
- const devtoolsBaseUrl = `${location.origin}${devtoolsPath}`
20
+ }) {
21
+ if (isDevEnv() === true) {
22
+ const devtoolsPath = (globalThis as unknown as { LIVESTORE_DEVTOOLS_PATH?: string }).LIVESTORE_DEVTOOLS_PATH ?? `/_livestore`
23
+ const devtoolsBaseUrl = `${location.origin}${devtoolsPath}`
25
24
 
26
- // Check whether devtools are available and then log the URL
27
- const response = yield* Effect.promise(() => fetch(devtoolsBaseUrl))
28
- if (response.ok) {
29
- const text = yield* Effect.promise(() => response.text())
30
- if (text.includes('<meta name="livestore-devtools" content="true" />')) {
31
- const url = `${devtoolsBaseUrl}/web/${storeId}/${clientId}/${sessionId}/${schema.devtools.alias}`
32
- yield* Effect.log(`[@livestore/adapter-web] Devtools ready on ${url}`)
33
- }
25
+ // Check whether devtools are available and then log the URL
26
+ const response = yield* Effect.promise(() => fetch(devtoolsBaseUrl))
27
+ if (response.ok === true) {
28
+ const text = yield* Effect.promise(() => response.text())
29
+ if (text.includes('<meta name="livestore-devtools" content="true" />') === true) {
30
+ const url = `${devtoolsBaseUrl}/web/${storeId}/${clientId}/${sessionId}/${schema.devtools.alias}`
31
+ yield* Effect.log(`[@livestore/adapter-web] Devtools ready on ${url}`)
32
+ }
34
33
 
35
- // Check for DevTools Chrome extension presence via iframe container the extension injects
36
- const hasExt = document.querySelector('[id^="livestore-devtools-iframe-"]') !== null
37
- if (!hasExt) {
38
- const g = globalThis as { __livestoreDevtoolsChromeNoticeShown?: boolean }
39
- if (g.__livestoreDevtoolsChromeNoticeShown !== true) {
40
- g.__livestoreDevtoolsChromeNoticeShown = true
34
+ // Check for DevTools Chrome extension presence via iframe container the extension injects
35
+ const hasExt = document.querySelector('[id^="livestore-devtools-iframe-"]') !== null
36
+ if (hasExt === false) {
37
+ const g = globalThis as { __livestoreDevtoolsChromeNoticeShown?: boolean }
38
+ if (g.__livestoreDevtoolsChromeNoticeShown !== true) {
39
+ g.__livestoreDevtoolsChromeNoticeShown = true
41
40
 
42
- const urlToLog = `https://github.com/livestorejs/livestore/releases/download/v${liveStoreVersion}/livestore-devtools-chrome-${liveStoreVersion}.zip`
43
- yield* Effect.log(
44
- `[@livestore/adapter-web] LiveStore DevTools Chrome extension not detected. Install v${liveStoreVersion}: ${urlToLog}`,
45
- )
46
- }
41
+ const urlToLog = `https://github.com/livestorejs/livestore/releases/download/v${liveStoreVersion}/livestore-devtools-chrome-${liveStoreVersion}.zip`
42
+ yield* Effect.log(
43
+ `[@livestore/adapter-web] LiveStore DevTools Chrome extension not detected. Install v${liveStoreVersion}: ${urlToLog}`,
44
+ )
47
45
  }
48
46
  }
49
47
  }
50
- }).pipe(Effect.withSpan('@livestore/adapter-web:client-session:devtools:logDevtoolsUrl'))
48
+ }
49
+ })
51
50
 
52
51
  export const connectWebmeshNodeClientSession = Effect.fn(function* ({
53
52
  webmeshNode,
@@ -62,7 +61,7 @@ export const connectWebmeshNodeClientSession = Effect.fn(function* ({
62
61
  devtoolsEnabled: boolean
63
62
  schema: LiveStoreSchema
64
63
  }) {
65
- if (devtoolsEnabled) {
64
+ if (devtoolsEnabled === true) {
66
65
  const { clientId, sessionId, storeId } = sessionInfo
67
66
  yield* logDevtoolsUrl({ clientId, sessionId, schema, storeId })
68
67
 
@@ -1,6 +1,7 @@
1
1
  import type { Adapter, BootWarningReason, ClientSession, LockStatus } from '@livestore/common'
2
2
  import {
3
3
  IntentionalShutdownCause,
4
+ isWorkerTransportError,
4
5
  liveStoreVersion,
5
6
  makeClientSession,
6
7
  StoreInterrupted,
@@ -20,17 +21,17 @@ import {
20
21
  Exit,
21
22
  Fiber,
22
23
  Layer,
23
- ParseResult,
24
+ Option,
24
25
  Queue,
25
26
  Schema,
26
27
  Stream,
27
28
  Subscribable,
28
29
  SubscriptionRef,
29
30
  Worker,
30
- WorkerError,
31
31
  } from '@livestore/utils/effect'
32
32
  import { BrowserWorker, Opfs, WebError, WebLock } from '@livestore/utils/effect/browser'
33
33
  import { nanoid } from '@livestore/utils/nanoid'
34
+
34
35
  import { makeSingleTabAdapter } from '../../single-tab/single-tab-adapter.ts'
35
36
  import {
36
37
  readPersistedStateDbFromClientSession,
@@ -52,7 +53,7 @@ import { loadSqlite3 } from './sqlite-loader.ts'
52
53
  */
53
54
  export const canUseSharedWorker = (): boolean => typeof SharedWorker !== 'undefined'
54
55
 
55
- if (isDevEnv()) {
56
+ if (isDevEnv() === true) {
56
57
  globalThis.__debugLiveStoreUtils = {
57
58
  ...globalThis.__debugLiveStoreUtils,
58
59
  opfs: Opfs.debugUtils,
@@ -158,7 +159,7 @@ export const makePersistedAdapter =
158
159
  (adapterArgs) =>
159
160
  Effect.gen(function* () {
160
161
  // Check SharedWorker availability first and fall back to single-tab mode if unavailable
161
- if (!canUseSharedWorker()) {
162
+ if (canUseSharedWorker() === false) {
162
163
  yield* Effect.logWarning(
163
164
  '[@livestore/adapter-web] SharedWorker unavailable (e.g. Android Chrome). ' +
164
165
  'Falling back to single-tab mode. Multi-tab synchronization and devtools are disabled. ' +
@@ -254,7 +255,7 @@ export const makePersistedAdapter =
254
255
  yield* shutdownChannel.listen.pipe(
255
256
  Stream.flatten(),
256
257
  Stream.tap((cause) =>
257
- shutdown(cause._tag === 'LiveStore.IntentionalShutdownCause' ? Exit.succeed(cause) : Exit.fail(cause)),
258
+ shutdown(cause._tag === 'IntentionalShutdownCause' ? Exit.succeed(cause) : Exit.fail(cause)),
258
259
  ),
259
260
  Stream.runDrain,
260
261
  Effect.interruptible,
@@ -264,7 +265,7 @@ export const makePersistedAdapter =
264
265
 
265
266
  const sharedWebWorker = tryAsFunctionAndNew(options.sharedWorker, { name: `livestore-shared-worker-${storeId}` })
266
267
 
267
- if (options.experimental?.awaitSharedWorkerTermination) {
268
+ if (options.experimental?.awaitSharedWorkerTermination === true) {
268
269
  // Relying on the lock being available is currently the only mechanism we're aware of
269
270
  // to know whether the shared worker has terminated.
270
271
  yield* Effect.addFinalizer(() => WebLock.waitForLock(LIVESTORE_SHARED_WORKER_TERMINATION_LOCK))
@@ -277,7 +278,7 @@ export const makePersistedAdapter =
277
278
  }).pipe(
278
279
  Effect.provide(sharedWorkerContext),
279
280
  Effect.tapCauseLogPretty,
280
- UnknownError.mapToUnknownError,
281
+ Effect.orDie,
281
282
  Effect.tapErrorCause((cause) => shutdown(Exit.failCause(cause))),
282
283
  Effect.withSpan('@livestore/adapter-web:client-session:setupSharedWorker'),
283
284
  Effect.forkScoped,
@@ -289,7 +290,7 @@ export const makePersistedAdapter =
289
290
  //
290
291
  // Sorry for this pun ...
291
292
  let gotLocky = yield* WebLock.tryGetDeferredLock(lockDeferred, LIVESTORE_TAB_LOCK)
292
- const lockStatus = yield* SubscriptionRef.make<LockStatus>(gotLocky ? 'has-lock' : 'no-lock')
293
+ const lockStatus = yield* SubscriptionRef.make<LockStatus>(gotLocky === true ? 'has-lock' : 'no-lock')
293
294
 
294
295
  // Ideally we can come up with a simpler implementation that doesn't require this
295
296
  const waitForSharedWorkerInitialized = yield* Deferred.make<void>()
@@ -374,15 +375,14 @@ export const makePersistedAdapter =
374
375
  yield* runLocked.pipe(Effect.interruptible, Effect.tapCauseLogPretty, Effect.forkScoped)
375
376
  }
376
377
 
377
- const runInWorker = <TReq extends typeof WorkerSchema.SharedWorkerRequest.Type>(
378
- req: TReq,
379
- ): TReq extends Schema.WithResult<infer A, infer _I, infer E, infer _EI, infer R>
380
- ? Effect.Effect<A, UnknownError | E, R>
381
- : never =>
378
+ const runInWorker = <A, I, E, EI, R>(
379
+ req: WorkerSchema.SharedWorkerRequest & Schema.WithResult<A, I, E, EI, R>,
380
+ ): Effect.Effect<A, E, R> =>
382
381
  Fiber.join(sharedWorkerFiber).pipe(
383
382
  // NOTE we need to wait for the shared worker to be initialized before we can send requests to it
384
383
  Effect.tap(() => waitForSharedWorkerInitialized),
385
- Effect.flatMap((worker) => worker.executeEffect(req) as any),
384
+ Effect.flatMap((worker) => worker.executeEffect(req)),
385
+ Effect.catchIf(isWorkerTransportError, (e) => Effect.die(e)),
386
386
  // NOTE we want to treat worker requests as atomic and therefore not allow them to be interrupted
387
387
  // Interruption usually only happens during leader re-election or store shutdown
388
388
  // Effect.uninterruptible,
@@ -391,40 +391,24 @@ export const makePersistedAdapter =
391
391
  duration: 2000,
392
392
  }),
393
393
  Effect.withSpan(`@livestore/adapter-web:client-session:runInWorker:${req._tag}`),
394
- Effect.mapError((cause) =>
395
- Schema.is(UnknownError)(cause)
396
- ? cause
397
- : ParseResult.isParseError(cause) || Schema.is(WorkerError.WorkerError)(cause)
398
- ? new UnknownError({ cause })
399
- : cause,
400
- ),
401
- Effect.catchAllDefect((cause) => new UnknownError({ cause })),
402
- ) as any
403
-
404
- const runInWorkerStream = <TReq extends typeof WorkerSchema.SharedWorkerRequest.Type>(
405
- req: TReq,
406
- ): TReq extends Schema.WithResult<infer A, infer _I, infer _E, infer _EI, infer R>
407
- ? Stream.Stream<A, UnknownError, R>
408
- : never =>
394
+ )
395
+
396
+ const runInWorkerStream = <A, I, E, EI, R>(
397
+ req: WorkerSchema.SharedWorkerRequest & Schema.WithResult<A, I, E, EI, R>,
398
+ ): Stream.Stream<A, E, R> =>
409
399
  Effect.gen(function* () {
410
400
  const sharedWorker = yield* Fiber.join(sharedWorkerFiber)
411
- return sharedWorker.execute(req as any).pipe(
412
- Stream.mapError((cause) =>
413
- Schema.is(UnknownError)(cause)
414
- ? cause
415
- : ParseResult.isParseError(cause) || Schema.is(WorkerError.WorkerError)(cause)
416
- ? new UnknownError({ cause })
417
- : cause,
418
- ),
401
+ return sharedWorker.execute(req).pipe(
402
+ Stream.refineOrDie((e) => isWorkerTransportError(e) === true ? Option.none() : Option.some(e)),
419
403
  Stream.withSpan(`@livestore/adapter-web:client-session:runInWorkerStream:${req._tag}`),
420
404
  )
421
- }).pipe(Stream.unwrap) as any
405
+ }).pipe(Stream.unwrap)
422
406
 
423
407
  const bootStatusFiber = yield* runInWorkerStream(new WorkerSchema.LeaderWorkerInnerBootStatusStream()).pipe(
424
408
  Stream.tap((_) => Queue.offer(bootStatusQueue, _)),
425
409
  Stream.runDrain,
426
410
  Effect.tapErrorCause((cause) =>
427
- Cause.isInterruptedOnly(cause) ? Effect.void : shutdown(Exit.failCause(cause)),
411
+ Cause.isInterruptedOnly(cause) === true ? Effect.void : shutdown(Exit.failCause(cause)),
428
412
  ),
429
413
  Effect.interruptible,
430
414
  Effect.tapCauseLogPretty,
@@ -479,20 +463,21 @@ export const makePersistedAdapter =
479
463
  .first(),
480
464
  )
481
465
 
482
- const initialLeaderHead = initialLeaderHeadRes
483
- ? EventSequenceNumber.Client.Composite.make({
484
- global: initialLeaderHeadRes.seqNumGlobal,
485
- client: initialLeaderHeadRes.seqNumClient,
486
- rebaseGeneration: initialLeaderHeadRes.seqNumRebaseGeneration,
487
- })
488
- : EventSequenceNumber.Client.ROOT
466
+ const initialLeaderHead =
467
+ initialLeaderHeadRes !== undefined
468
+ ? EventSequenceNumber.Client.Composite.make({
469
+ global: initialLeaderHeadRes.seqNumGlobal,
470
+ client: initialLeaderHeadRes.seqNumClient,
471
+ rebaseGeneration: initialLeaderHeadRes.seqNumRebaseGeneration,
472
+ })
473
+ : EventSequenceNumber.Client.ROOT
489
474
 
490
475
  // console.debug('[@livestore/adapter-web:client-session] initialLeaderHead', initialLeaderHead)
491
476
 
492
477
  yield* Effect.addFinalizer((ex) =>
493
478
  Effect.gen(function* () {
494
479
  if (
495
- Exit.isFailure(ex) &&
480
+ Exit.isFailure(ex) === true &&
496
481
  Exit.isInterrupted(ex) === false &&
497
482
  Schema.is(IntentionalShutdownCause)(Cause.squash(ex.cause)) === false &&
498
483
  Schema.is(StoreInterrupted)(Cause.squash(ex.cause)) === false
@@ -502,7 +487,7 @@ export const makePersistedAdapter =
502
487
  yield* Effect.logDebug('[@livestore/adapter-web:client-session] client-session shutdown', gotLocky, ex)
503
488
  }
504
489
 
505
- if (gotLocky) {
490
+ if (gotLocky === true) {
506
491
  yield* Deferred.succeed(lockDeferred, undefined)
507
492
  }
508
493
  }).pipe(Effect.tapCauseLogPretty, Effect.orDie),
@@ -510,8 +495,7 @@ export const makePersistedAdapter =
510
495
 
511
496
  const leaderThread: ClientSession['leaderThread'] = {
512
497
  export: runInWorker(new WorkerSchema.LeaderWorkerInnerExport()).pipe(
513
- Effect.timeout(10_000),
514
- UnknownError.mapToUnknownError,
498
+ Effect.timeoutOrDie(10_000),
515
499
  Effect.withSpan('@livestore/adapter-web:client-session:export'),
516
500
  ),
517
501
 
@@ -538,14 +522,12 @@ export const makePersistedAdapter =
538
522
  },
539
523
 
540
524
  getEventlogData: runInWorker(new WorkerSchema.LeaderWorkerInnerExportEventlog()).pipe(
541
- Effect.timeout(10_000),
542
- UnknownError.mapToUnknownError,
525
+ Effect.timeoutOrDie(10_000),
543
526
  Effect.withSpan('@livestore/adapter-web:client-session:getEventlogData'),
544
527
  ),
545
528
 
546
529
  syncState: Subscribable.make({
547
530
  get: runInWorker(new WorkerSchema.LeaderWorkerInnerGetLeaderSyncState()).pipe(
548
- UnknownError.mapToUnknownError,
549
531
  Effect.withSpan('@livestore/adapter-web:client-session:getLeaderSyncState'),
550
532
  ),
551
533
  changes: runInWorkerStream(new WorkerSchema.LeaderWorkerInnerSyncStateStream()).pipe(Stream.orDie),
@@ -553,12 +535,11 @@ export const makePersistedAdapter =
553
535
 
554
536
  sendDevtoolsMessage: (message) =>
555
537
  runInWorker(new WorkerSchema.LeaderWorkerInnerExtraDevtoolsMessage({ message })).pipe(
556
- UnknownError.mapToUnknownError,
557
538
  Effect.withSpan('@livestore/adapter-web:client-session:devtoolsMessageForLeader'),
558
539
  ),
559
540
  networkStatus: Subscribable.make({
560
- get: runInWorker(new WorkerSchema.LeaderWorkerInnerGetNetworkStatus()).pipe(Effect.orDie),
561
- changes: runInWorkerStream(new WorkerSchema.LeaderWorkerInnerNetworkStatusStream()).pipe(Stream.orDie),
541
+ get: runInWorker(new WorkerSchema.LeaderWorkerInnerGetNetworkStatus()),
542
+ changes: runInWorkerStream(new WorkerSchema.LeaderWorkerInnerNetworkStatusStream()),
562
543
  }),
563
544
  }
564
545
 
@@ -601,7 +582,7 @@ const getPersistedId = (key: string, storageType: 'session' | 'local') => {
601
582
  ? sessionStorage
602
583
  : storageType === 'local'
603
584
  ? localStorage
604
- : shouldNeverHappen(`[@livestore/adapter-web] Invalid storage type: ${storageType}`)
585
+ : shouldNeverHappen(`[@livestore/adapter-web] Invalid storage type: ${String(storageType)}`)
605
586
 
606
587
  // in case of a worker, we need the id of the parent window, to keep the id consistent
607
588
  // we also need to handle the case where there are multiple workers being spawned by the same window
@@ -612,7 +593,7 @@ const getPersistedId = (key: string, storageType: 'session' | 'local') => {
612
593
  const fullKey = `livestore:${key}`
613
594
  const storedKey = storage.getItem(fullKey)
614
595
 
615
- if (storedKey) return storedKey
596
+ if (storedKey !== null) return storedKey
616
597
 
617
598
  const newKey = makeId()
618
599
  storage.setItem(fullKey, newKey)
@@ -623,7 +604,7 @@ const getPersistedId = (key: string, storageType: 'session' | 'local') => {
623
604
  const ensureBrowserRequirements = Effect.gen(function* () {
624
605
  const validate = (condition: boolean, label: string) =>
625
606
  Effect.gen(function* () {
626
- if (condition) {
607
+ if (condition === true) {
627
608
  return yield* UnknownError.make({
628
609
  cause: `[@livestore/adapter-web] Browser not supported. The LiveStore web adapter needs '${label}' to work properly`,
629
610
  })
@@ -654,7 +635,7 @@ const checkOpfsAvailability = Effect.gen(function* () {
654
635
  Effect.as(undefined),
655
636
  Effect.catchAll((error) => {
656
637
  const reason: BootWarningReason =
657
- Schema.is(WebError.SecurityError)(error) || Schema.is(WebError.NotAllowedError)(error)
638
+ Schema.is(WebError.SecurityError)(error) === true || Schema.is(WebError.NotAllowedError)(error) === true
658
639
  ? 'private-browsing'
659
640
  : 'storage-unavailable'
660
641
  const message =
@@ -16,4 +16,4 @@ if (isServerRuntime === false) {
16
16
  sqlite3Promise = loadSqlite3Wasm()
17
17
  }
18
18
 
19
- export const loadSqlite3 = () => (isServerRuntime ? loadSqlite3Wasm() : (sqlite3Promise ?? loadSqlite3Wasm()))
19
+ export const loadSqlite3 = () => (isServerRuntime === true ? loadSqlite3Wasm() : (sqlite3Promise ?? loadSqlite3Wasm()))
@@ -8,9 +8,10 @@ import {
8
8
  import { isDevEnv } from '@livestore/utils'
9
9
  import { Chunk, Effect, Option, Order, Schedule, Schema, Stream } from '@livestore/utils/effect'
10
10
  import { Opfs, type WebError } from '@livestore/utils/effect/browser'
11
+
11
12
  import type * as WorkerSchema from './worker-schema.ts'
12
13
 
13
- export class PersistedSqliteError extends Schema.TaggedError<PersistedSqliteError>()('PersistedSqliteError', {
14
+ export class PersistedSqliteError extends Schema.TaggedError<PersistedSqliteError>('~@livestore/adapter-web/PersistedSqliteError')('PersistedSqliteError', {
14
15
  message: Schema.String,
15
16
  cause: Schema.optional(Schema.Defect),
16
17
  }) {}
@@ -56,7 +57,7 @@ export const readPersistedStateDbFromClientSession: (args: {
56
57
  Stream.runHead,
57
58
  )
58
59
 
59
- if (Option.isNone(stateDbFileOption)) {
60
+ if (Option.isNone(stateDbFileOption) === true) {
60
61
  return yield* new PersistedSqliteError({
61
62
  message: `State database file not found in client session (expected '${stateDbFileName}' in '${accessHandlePoolDirString}')`,
62
63
  })
@@ -90,7 +91,7 @@ export const resetPersistedDataFromClientSession = Effect.fn(
90
91
  const directory = yield* sanitizeOpfsDir(storageOptions.directory, storeId)
91
92
  yield* Opfs.remove(directory, { recursive: true }).pipe(
92
93
  // We ignore NotFoundError here as it may not exist or have already been deleted
93
- Effect.catchTag('@livestore/utils/Web/NotFoundError', () => Effect.void),
94
+ Effect.catchTag('NotFoundError', () => Effect.void),
94
95
  )
95
96
  },
96
97
  Effect.retry({
@@ -106,7 +107,7 @@ export const sanitizeOpfsDir = Effect.fn('@livestore/adapter-web:sanitizeOpfsDir
106
107
  return `livestore-${storeId}@${liveStoreStorageFormatVersion}`
107
108
  }
108
109
 
109
- if (directory.includes('/')) {
110
+ if (directory.includes('/') === true) {
110
111
  return yield* new PersistedSqliteError({
111
112
  message: `Nested directories are not yet supported ('${directory}')`,
112
113
  })
@@ -183,12 +184,13 @@ export const cleanupOldStateDbFiles: (options: {
183
184
  }
184
185
 
185
186
  const absoluteArchiveDirName = `${opfsDirectory}/${ARCHIVE_DIR_NAME}`
186
- if (isDev && !(yield* Opfs.exists(absoluteArchiveDirName))) yield* Opfs.makeDirectory(absoluteArchiveDirName)
187
+ if (isDev === true && (yield* Opfs.exists(absoluteArchiveDirName)) === false)
188
+ yield* Opfs.makeDirectory(absoluteArchiveDirName)
187
189
 
188
190
  for (const path of oldStateDbPaths) {
189
- const fileName = path.startsWith('/') ? path.slice(1) : path
191
+ const fileName = path.startsWith('/') === true ? path.slice(1) : path
190
192
 
191
- if (isDev) {
193
+ if (isDev === true) {
192
194
  const archiveFileData = yield* vfs.readFilePayload(fileName)
193
195
 
194
196
  const archiveFileName = `${Date.now()}-${fileName}`
@@ -201,7 +203,7 @@ export const cleanupOldStateDbFiles: (options: {
201
203
  const supportsCreateWritable =
202
204
  typeof FileSystemFileHandle !== 'undefined' && 'createWritable' in FileSystemFileHandle.prototype
203
205
 
204
- if (supportsCreateWritable) {
206
+ if (supportsCreateWritable === true) {
205
207
  yield* Opfs.writeFile(archivePath, archiveData)
206
208
  } else {
207
209
  yield* Opfs.syncWriteFile(archivePath, archiveData)
@@ -225,7 +227,7 @@ export const cleanupOldStateDbFiles: (options: {
225
227
  yield* Effect.logDebug(`Deleted old state database file: ${fileName}`)
226
228
  }
227
229
 
228
- if (isDev) {
230
+ if (isDev === true) {
229
231
  yield* pruneArchiveDirectory({
230
232
  archiveDirectory: absoluteArchiveDirName,
231
233
  keep: MAX_ARCHIVED_STATE_DBS_IN_DEV,
@@ -248,6 +250,7 @@ const pruneArchiveDirectory = Effect.fn('@livestore/adapter-web:pruneArchiveDire
248
250
  Stream.runCollect,
249
251
  )
250
252
  const filesToDelete = filesWithMetadata.pipe(
253
+ // oxlint-disable-next-line unicorn/no-array-sort -- false positive: Effect Chunk.sort is immutable, not Array#sort (https://github.com/oxc-project/oxc/issues/19110)
251
254
  Chunk.sort(Order.mapInput(Order.number, (entry: { lastModified: number }) => entry.lastModified)),
252
255
  Chunk.drop(keep),
253
256
  Chunk.toReadonlyArray,