@livestore/adapter-node 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 (37) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/client-session/adapter.d.ts +1 -0
  3. package/dist/client-session/adapter.d.ts.map +1 -1
  4. package/dist/client-session/adapter.js +21 -24
  5. package/dist/client-session/adapter.js.map +1 -1
  6. package/dist/devtools/devtools-server.d.ts.map +1 -1
  7. package/dist/devtools/devtools-server.js +14 -6
  8. package/dist/devtools/devtools-server.js.map +1 -1
  9. package/dist/devtools/mod.d.ts +1 -1
  10. package/dist/devtools/mod.d.ts.map +1 -1
  11. package/dist/devtools/mod.js +1 -1
  12. package/dist/devtools/mod.js.map +1 -1
  13. package/dist/devtools/vite-dev-server.d.ts +25 -3
  14. package/dist/devtools/vite-dev-server.d.ts.map +1 -1
  15. package/dist/devtools/vite-dev-server.js +40 -5
  16. package/dist/devtools/vite-dev-server.js.map +1 -1
  17. package/dist/leader-thread-shared.js +2 -2
  18. package/dist/leader-thread-shared.js.map +1 -1
  19. package/dist/make-leader-worker.d.ts +1 -1
  20. package/dist/make-leader-worker.d.ts.map +1 -1
  21. package/dist/make-leader-worker.js +15 -17
  22. package/dist/make-leader-worker.js.map +1 -1
  23. package/dist/shutdown-channel.d.ts.map +1 -1
  24. package/dist/shutdown-channel.js.map +1 -1
  25. package/dist/worker-schema.d.ts +31 -27
  26. package/dist/worker-schema.d.ts.map +1 -1
  27. package/dist/worker-schema.js +16 -16
  28. package/dist/worker-schema.js.map +1 -1
  29. package/package.json +59 -19
  30. package/src/client-session/adapter.ts +32 -47
  31. package/src/devtools/devtools-server.ts +22 -10
  32. package/src/devtools/mod.ts +1 -1
  33. package/src/devtools/vite-dev-server.ts +60 -6
  34. package/src/leader-thread-shared.ts +4 -4
  35. package/src/make-leader-worker.ts +38 -47
  36. package/src/shutdown-channel.ts +1 -0
  37. package/src/worker-schema.ts +16 -16
@@ -1,15 +1,17 @@
1
1
  import { hostname } from 'node:os'
2
2
  import path from 'node:path'
3
+ import type { URL } from 'node:url'
3
4
  import * as WT from 'node:worker_threads'
5
+
4
6
  import {
5
7
  type Adapter,
6
8
  type BootStatus,
7
9
  ClientSessionLeaderThreadProxy,
8
10
  IntentionalShutdownCause,
11
+ isWorkerTransportError,
9
12
  type LockStatus,
10
13
  type MakeSqliteDb,
11
14
  makeClientSession,
12
- type SyncError,
13
15
  type SyncOptions,
14
16
  UnknownError,
15
17
  } from '@livestore/common'
@@ -27,7 +29,7 @@ import {
27
29
  Fiber,
28
30
  FileSystem,
29
31
  Layer,
30
- ParseResult,
32
+ Option,
31
33
  Queue,
32
34
  Schedule,
33
35
  Schema,
@@ -35,7 +37,6 @@ import {
35
37
  Subscribable,
36
38
  SubscriptionRef,
37
39
  Worker,
38
- WorkerError,
39
40
  } from '@livestore/utils/effect'
40
41
  import { PlatformNode } from '@livestore/utils/node'
41
42
  import * as Webmesh from '@livestore/webmesh'
@@ -232,7 +233,7 @@ const makeAdapterImpl = ({
232
233
  yield* shutdownChannel.listen.pipe(
233
234
  Stream.flatten(),
234
235
  Stream.tap((cause) =>
235
- shutdown(cause._tag === 'LiveStore.IntentionalShutdownCause' ? Exit.succeed(cause) : Exit.fail(cause)),
236
+ shutdown(cause._tag === 'IntentionalShutdownCause' ? Exit.succeed(cause) : Exit.fail(cause)),
236
237
  ),
237
238
  Stream.runDrain,
238
239
  Effect.interruptible,
@@ -246,7 +247,7 @@ const makeAdapterImpl = ({
246
247
  const lockStatus = yield* SubscriptionRef.make<LockStatus>('has-lock')
247
248
 
248
249
  const devtoolsOptions: WorkerSchema.LeaderWorkerInnerInitialMessage['devtools'] =
249
- devtoolsEnabled && devtoolsOptionsInput !== undefined
250
+ devtoolsEnabled === true && devtoolsOptionsInput !== undefined
250
251
  ? {
251
252
  enabled: true,
252
253
  schemaPath:
@@ -297,7 +298,7 @@ const makeAdapterImpl = ({
297
298
  sqliteDb: syncInMemoryDb,
298
299
  webmeshMode: 'proxy',
299
300
  connectWebmeshNode: Effect.fnUntraced(function* ({ webmeshNode }) {
300
- if (devtoolsOptions.enabled) {
301
+ if (devtoolsOptions.enabled === true) {
301
302
  yield* Webmesh.connectViaWebSocket({
302
303
  node: webmeshNode,
303
304
  url: `ws://${devtoolsOptions.host}:${devtoolsOptions.port}`,
@@ -413,7 +414,11 @@ const makeLocalLeaderThread = ({
413
414
  options,
414
415
  }),
415
416
  },
416
- initialState: { leaderHead: initialLeaderHead, migrationsReport: initialState.migrationsReport },
417
+ initialState: {
418
+ leaderHead: initialLeaderHead,
419
+ migrationsReport: initialState.migrationsReport,
420
+ storageMode: 'persisted',
421
+ },
417
422
  export: Effect.sync(() => dbState.export()),
418
423
  getEventlogData: Effect.sync(() => dbEventlog.export()),
419
424
  syncState: syncProcessor.syncState,
@@ -442,7 +447,7 @@ const makeWorkerLeaderThread = ({
442
447
  syncPayloadEncoded,
443
448
  testing,
444
449
  }: {
445
- shutdown: (cause: Exit.Exit<IntentionalShutdownCause, UnknownError | SyncError>) => Effect.Effect<void>
450
+ shutdown: (cause: Exit.Exit<IntentionalShutdownCause, UnknownError>) => Effect.Effect<void>
446
451
  storeId: string
447
452
  clientId: string
448
453
  sessionId: string
@@ -458,8 +463,8 @@ const makeWorkerLeaderThread = ({
458
463
  }) =>
459
464
  Effect.gen(function* () {
460
465
  const nodeWorker = new WT.Worker(workerUrl, {
461
- execArgv: process.env.DEBUG_WORKER ? ['--inspect --enable-source-maps'] : ['--enable-source-maps'],
462
- argv: [Schema.encodeSync(WorkerSchema.WorkerArgv)({ storeId, clientId, sessionId, extraArgs: workerExtraArgs })],
466
+ execArgv: process.env.DEBUG_WORKER !== undefined ? ['--inspect --enable-source-maps'] : ['--enable-source-maps'],
467
+ argv: [yield* Schema.encode(WorkerSchema.WorkerArgv)({ storeId, clientId, sessionId, extraArgs: workerExtraArgs }).pipe(Effect.orDie)],
463
468
  })
464
469
  const nodeWorkerLayer = yield* Layer.build(PlatformNode.NodeWorker.layer(() => nodeWorker))
465
470
 
@@ -481,47 +486,30 @@ const makeWorkerLeaderThread = ({
481
486
  Effect.withSpan('@livestore/adapter-node:adapter:setupLeaderThread'),
482
487
  )
483
488
 
484
- const runInWorker = <TReq extends typeof WorkerSchema.LeaderWorkerInnerRequest.Type>(
485
- req: TReq,
486
- ): TReq extends Schema.WithResult<infer A, infer _I, infer _E, infer _EI, infer R>
487
- ? Effect.Effect<A, UnknownError, R>
488
- : never =>
489
- (worker.executeEffect(req) as any).pipe(
489
+ const runInWorker = <A, I, E, EI, R>(
490
+ req: WorkerSchema.LeaderWorkerInnerRequest & Schema.WithResult<A, I, E, EI, R>,
491
+ ): Effect.Effect<A, E, R> =>
492
+ worker.executeEffect(req).pipe(
493
+ Effect.catchIf(isWorkerTransportError, (e) => Effect.die(e)),
490
494
  Effect.logWarnIfTakesLongerThan({
491
495
  label: `@livestore/adapter-node:client-session:runInWorker:${req._tag}`,
492
496
  duration: 2000,
493
497
  }),
494
498
  Effect.withSpan(`@livestore/adapter-node:client-session:runInWorker:${req._tag}`),
495
- Effect.mapError((cause) =>
496
- Schema.is(UnknownError)(cause)
497
- ? cause
498
- : ParseResult.isParseError(cause) || Schema.is(WorkerError.WorkerError)(cause)
499
- ? new UnknownError({ cause })
500
- : cause,
501
- ),
502
- Effect.catchAllDefect((cause) => new UnknownError({ cause })),
503
- ) as any
504
-
505
- const runInWorkerStream = <TReq extends typeof WorkerSchema.LeaderWorkerInnerRequest.Type>(
506
- req: TReq,
507
- ): TReq extends Schema.WithResult<infer A, infer _I, infer _E, infer _EI, infer R>
508
- ? Stream.Stream<A, UnknownError, R>
509
- : never =>
510
- worker.execute(req as any).pipe(
511
- Stream.mapError((cause) =>
512
- Schema.is(UnknownError)(cause)
513
- ? cause
514
- : ParseResult.isParseError(cause) || Schema.is(WorkerError.WorkerError)(cause)
515
- ? new UnknownError({ cause })
516
- : cause,
517
- ),
499
+ )
500
+
501
+ const runInWorkerStream = <A, I, E, EI, R>(
502
+ req: WorkerSchema.LeaderWorkerInnerRequest & Schema.WithResult<A, I, E, EI, R>,
503
+ ): Stream.Stream<A, E, R> =>
504
+ worker.execute(req).pipe(
505
+ Stream.refineOrDie((e) => isWorkerTransportError(e) === true ? Option.none() : Option.some(e)),
518
506
  Stream.withSpan(`@livestore/adapter-node:client-session:runInWorkerStream:${req._tag}`),
519
- ) as any
507
+ )
520
508
 
521
509
  const bootStatusFiber = yield* runInWorkerStream(new WorkerSchema.LeaderWorkerInnerBootStatusStream()).pipe(
522
510
  Stream.tap((bootStatus) => Queue.offer(bootStatusQueue, bootStatus)),
523
511
  Stream.runDrain,
524
- Effect.tapErrorCause((cause) => (Cause.isInterruptedOnly(cause) ? Effect.void : shutdown(Exit.failCause(cause)))),
512
+ Effect.tapErrorCause((cause) => (Cause.isInterruptedOnly(cause) === true ? Effect.void : shutdown(Exit.failCause(cause)))),
525
513
  Effect.interruptible,
526
514
  Effect.tapCauseLogPretty,
527
515
  Effect.forkScoped,
@@ -536,8 +524,7 @@ const makeWorkerLeaderThread = ({
536
524
  const initialLeaderHead = yield* runInWorker(new WorkerSchema.LeaderWorkerInnerGetLeaderHead())
537
525
 
538
526
  const bootResult = yield* runInWorker(new WorkerSchema.LeaderWorkerInnerGetRecreateSnapshot()).pipe(
539
- Effect.timeout(10_000),
540
- UnknownError.mapToUnknownError,
527
+ Effect.timeoutOrDie(10_000),
541
528
  Effect.withSpan('@livestore/adapter-node:client-session:export'),
542
529
  )
543
530
 
@@ -561,23 +548,21 @@ const makeWorkerLeaderThread = ({
561
548
  initialState: {
562
549
  leaderHead: initialLeaderHead,
563
550
  migrationsReport: bootResult.migrationsReport,
551
+ storageMode: 'persisted',
564
552
  },
565
553
  export: runInWorker(new WorkerSchema.LeaderWorkerInnerExport()).pipe(
566
- Effect.timeout(10_000),
567
- UnknownError.mapToUnknownError,
554
+ Effect.timeoutOrDie(10_000),
568
555
  Effect.withSpan('@livestore/adapter-node:client-session:export'),
569
556
  ),
570
557
  getEventlogData: Effect.dieMessage('Not implemented'),
571
558
  syncState: Subscribable.make({
572
559
  get: runInWorker(new WorkerSchema.LeaderWorkerInnerGetLeaderSyncState()).pipe(
573
- UnknownError.mapToUnknownError,
574
560
  Effect.withSpan('@livestore/adapter-node:client-session:getLeaderSyncState'),
575
561
  ),
576
562
  changes: runInWorkerStream(new WorkerSchema.LeaderWorkerInnerSyncStateStream()).pipe(Stream.orDie),
577
563
  }),
578
564
  sendDevtoolsMessage: (message) =>
579
565
  runInWorker(new WorkerSchema.LeaderWorkerInnerExtraDevtoolsMessage({ message })).pipe(
580
- UnknownError.mapToUnknownError,
581
566
  Effect.withSpan('@livestore/adapter-node:client-session:devtoolsMessageForLeader'),
582
567
  ),
583
568
  networkStatus: Subscribable.make({
@@ -20,6 +20,16 @@ import { makeMeshNode, makeWebSocketEdge } from '@livestore/webmesh'
20
20
 
21
21
  import { makeViteMiddleware } from './vite-dev-server.ts'
22
22
 
23
+ /**
24
+ * Determines if a request URL should be routed to the Vite middleware.
25
+ * Includes LiveStore devtools paths and Vite internal paths like `/@fs/`, `/@vite/`, etc.
26
+ */
27
+ const shouldRouteToVite = (url: string): boolean =>
28
+ url.startsWith('/_livestore') ||
29
+ url.startsWith('/@fs') ||
30
+ url.startsWith('/@vite') ||
31
+ url.startsWith('/@react-refresh')
32
+
23
33
  /**
24
34
  * Starts a devtools HTTP/WS server which serves ...
25
35
  * - the Devtools UI via Vite
@@ -39,11 +49,12 @@ export const startDevtoolsServer = ({
39
49
  Effect.gen(function* () {
40
50
  const viteMiddleware = yield* makeViteMiddleware({
41
51
  mode: { _tag: 'node', url: `http://${host}:${port}` },
42
- schemaPath: isReadonlyArray(schemaPath)
43
- ? schemaPath.map((schemaPath) => path.resolve(process.cwd(), schemaPath))
44
- : path.resolve(process.cwd(), schemaPath),
52
+ schemaPath:
53
+ isReadonlyArray(schemaPath) === true
54
+ ? schemaPath.map((schemaPath) => path.resolve(process.cwd(), schemaPath))
55
+ : path.resolve(process.cwd(), schemaPath),
45
56
  viteConfig: (viteConfig) => {
46
- if (LS_DEV) {
57
+ if (LS_DEV === true) {
47
58
  viteConfig.server ??= {}
48
59
  viteConfig.server.fs ??= {}
49
60
  viteConfig.server.fs.strict = false
@@ -63,7 +74,7 @@ export const startDevtoolsServer = ({
63
74
  const handler = Effect.gen(function* () {
64
75
  const req = yield* HttpServerRequest.HttpServerRequest
65
76
 
66
- if (Headers.has(req.headers, 'upgrade')) {
77
+ if (Headers.has(req.headers, 'upgrade') === true) {
67
78
  // yield* Effect.logDebug(`WS Relay ${relayNodeName}: WS upgrade request ${req.url}`)
68
79
 
69
80
  const socket = yield* req.upgrade
@@ -81,7 +92,7 @@ export const startDevtoolsServer = ({
81
92
  .addEdge({ target: from, edgeChannel: webChannel, replaceIfExists: true })
82
93
  .pipe(Effect.acquireRelease(() => node.removeEdge(from).pipe(Effect.orDie)))
83
94
 
84
- if (LS_DEV) {
95
+ if (LS_DEV === true) {
85
96
  yield* Effect.log(`WS Relay ${relayNodeName}: added edge from '${from}'`)
86
97
  yield* Effect.addFinalizerLog(`WS Relay ${relayNodeName}: removed edge from '${from}'`)
87
98
  }
@@ -96,7 +107,7 @@ export const startDevtoolsServer = ({
96
107
  } else {
97
108
  if (req.url === '/' || req.url === '') {
98
109
  return HttpServerResponse.redirect('/_livestore/node')
99
- } else if (req.url.startsWith('/_livestore')) {
110
+ } else if (shouldRouteToVite(req.url) === true) {
100
111
  // Here we're delegating to the Vite middleware
101
112
 
102
113
  // TODO replace this once @effect/platform-node supports Node HTTP middlewares
@@ -114,9 +125,10 @@ export const startDevtoolsServer = ({
114
125
  return HttpServerResponse.text('Not found')
115
126
  }).pipe(Effect.tapCauseLogPretty, Effect.interruptible)
116
127
 
117
- const sessionSuffix = clientSessionInfo
118
- ? `/${clientSessionInfo.storeId}/${clientSessionInfo.clientId}/${clientSessionInfo.sessionId}/${clientSessionInfo.schemaAlias}`
119
- : '?autoconnect'
128
+ const sessionSuffix =
129
+ clientSessionInfo !== undefined
130
+ ? `/${clientSessionInfo.storeId}/${clientSessionInfo.clientId}/${clientSessionInfo.sessionId}/${clientSessionInfo.schemaAlias}`
131
+ : '?autoconnect'
120
132
 
121
133
  // Use `localhost` instead of `0.0.0.0` as it doesn't have the `navigator.locks` web adapter limitation (https://share.cleanshot.com/nHBnmk6S)
122
134
  const maybeLocalhost = host === '0.0.0.0' ? 'localhost' : host
@@ -1,3 +1,3 @@
1
1
  export { startDevtoolsServer } from './devtools-server.ts'
2
2
  export type { ViteDevtoolsOptions } from './vite-dev-server.ts'
3
- export { makeViteMiddleware } from './vite-dev-server.ts'
3
+ export { DevtoolsViteNotInstalledError, makeViteMiddleware } from './vite-dev-server.ts'
@@ -1,12 +1,44 @@
1
1
  import path from 'node:path'
2
2
 
3
+ import type * as Vite from 'vite'
4
+
3
5
  import type { Devtools } from '@livestore/common'
4
6
  import { UnknownError } from '@livestore/common'
5
- import { livestoreDevtoolsPlugin } from '@livestore/devtools-vite'
6
7
  import { isReadonlyArray } from '@livestore/utils'
7
- import { Effect } from '@livestore/utils/effect'
8
+ import { Effect, Schema } from '@livestore/utils/effect'
8
9
  import { getFreePort } from '@livestore/utils/node'
9
- import * as Vite from 'vite'
10
+
11
+ /**
12
+ * Error thrown when @livestore/devtools-vite is not installed.
13
+ * This is a peer dependency that must be installed separately.
14
+ */
15
+ export class DevtoolsViteNotInstalledError extends Schema.TaggedError<DevtoolsViteNotInstalledError>(
16
+ '~@livestore/adapter-node/DevtoolsViteNotInstalledError',
17
+ )('DevtoolsViteNotInstalledError', {
18
+ cause: Schema.Defect,
19
+ }) {
20
+ override get message(): string {
21
+ return (
22
+ `@livestore/devtools-vite is required for devtools but not installed. ` +
23
+ `Install it with: pnpm add @livestore/devtools-vite@<version>. ` +
24
+ `Make sure to use the same version as @livestore/adapter-node.`
25
+ )
26
+ }
27
+ }
28
+
29
+ /** Error thrown when vite is not installed for the devtools server path. */
30
+ export class ViteNotInstalledError extends Schema.TaggedError<ViteNotInstalledError>(
31
+ '~@livestore/adapter-node/ViteNotInstalledError',
32
+ )('ViteNotInstalledError', {
33
+ cause: Schema.Defect,
34
+ }) {
35
+ override get message(): string {
36
+ return (
37
+ `vite is required for @livestore/adapter-node/devtools but not installed. ` +
38
+ `Install it with: pnpm add -D vite@<version>.`
39
+ )
40
+ }
41
+ }
10
42
 
11
43
  export type ViteDevtoolsOptions = {
12
44
  viteConfig?: (config: Vite.UserConfig) => Vite.UserConfig
@@ -26,8 +58,13 @@ export type ViteDevtoolsOptions = {
26
58
  }
27
59
 
28
60
  // NOTE this is currently also used in @livestore/devtools-expo
29
- export const makeViteMiddleware = (options: ViteDevtoolsOptions): Effect.Effect<Vite.ViteDevServer, UnknownError> =>
61
+ export const makeViteMiddleware = (
62
+ options: ViteDevtoolsOptions,
63
+ ): Effect.Effect<Vite.ViteDevServer, DevtoolsViteNotInstalledError | ViteNotInstalledError | UnknownError> =>
30
64
  Effect.gen(function* () {
65
+ const { livestoreDevtoolsPlugin } = yield* importDevtoolsVite()
66
+ const Vite = yield* importVite()
67
+
31
68
  const cwd = process.cwd()
32
69
 
33
70
  const hmrPort = yield* getFreePort.pipe(UnknownError.mapToUnknownError)
@@ -39,13 +76,13 @@ export const makeViteMiddleware = (options: ViteDevtoolsOptions): Effect.Effect<
39
76
  port: hmrPort,
40
77
  },
41
78
  // Relaxing fs access for monorepo setup
42
- fs: { strict: process.env.LS_DEV ? false : true },
79
+ fs: { strict: process.env.LS_DEV !== undefined ? false : true },
43
80
  },
44
81
  appType: 'spa',
45
82
  base: '/_livestore/',
46
83
  plugins: [
47
84
  livestoreDevtoolsPlugin({
48
- schemaPath: isReadonlyArray(options.schemaPath)
85
+ schemaPath: isReadonlyArray(options.schemaPath) === true
49
86
  ? options.schemaPath.map((schemaPath) => path.resolve(cwd, schemaPath))
50
87
  : path.resolve(cwd, options.schemaPath),
51
88
  mode: options.mode,
@@ -62,3 +99,20 @@ export const makeViteMiddleware = (options: ViteDevtoolsOptions): Effect.Effect<
62
99
 
63
100
  return viteServer
64
101
  }).pipe(Effect.withSpan('@livestore/adapter-node:devtools:makeViteServer'))
102
+
103
+ /**
104
+ * Dynamically imports @livestore/devtools-vite.
105
+ * This package is a peer dependency and may not be installed.
106
+ */
107
+ const importDevtoolsVite = () =>
108
+ Effect.tryPromise({
109
+ try: () => import('@livestore/devtools-vite'),
110
+ catch: (cause) => new DevtoolsViteNotInstalledError({ cause }),
111
+ })
112
+
113
+ /** Dynamically imports vite for the devtools-only server path. */
114
+ const importVite = () =>
115
+ Effect.tryPromise({
116
+ try: () => import('vite'),
117
+ catch: (cause) => new ViteNotInstalledError({ cause }),
118
+ })
@@ -1,7 +1,7 @@
1
1
  import inspector from 'node:inspector'
2
2
  import path from 'node:path'
3
3
 
4
- if (process.execArgv.includes('--inspect')) {
4
+ if (process.execArgv.includes('--inspect') === true) {
5
5
  inspector.open()
6
6
  inspector.waitForDebugger()
7
7
  }
@@ -64,13 +64,13 @@ export const makeLeaderThread = ({
64
64
  Scope.Scope
65
65
  > =>
66
66
  Effect.gen(function* () {
67
- const runtime = yield* Effect.runtime<never>()
67
+ const runtime = yield* Effect.runtime()
68
68
 
69
69
  const schemaHashSuffix =
70
70
  schema.state.sqlite.migrations.strategy === 'manual' ? 'fixed' : schema.state.sqlite.hash.toString()
71
71
 
72
72
  const makeDb = (kind: 'state' | 'eventlog') => {
73
- if (testing?.makeLeaderThread) {
73
+ if (testing?.makeLeaderThread !== undefined) {
74
74
  return testing
75
75
  .makeLeaderThread(makeSqliteDb)
76
76
  .pipe(Effect.map(({ dbEventlog, dbState }) => (kind === 'state' ? dbState : dbEventlog)))
@@ -184,7 +184,7 @@ const makeDevtoolsOptions = ({
184
184
  eventlog: dbEventlog.metadata.persistenceInfo,
185
185
  }
186
186
 
187
- return { node, persistenceInfo, mode: 'proxy' }
187
+ return { node, persistenceInfo, mode: 'proxy' as const }
188
188
  }),
189
189
  }
190
190
  })
@@ -1,12 +1,13 @@
1
1
  import './thread-polyfill.ts'
2
-
3
2
  import inspector from 'node:inspector'
4
3
 
5
- if (process.execArgv.includes('--inspect')) {
4
+ if (process.execArgv.includes('--inspect') === true) {
6
5
  inspector.open()
7
6
  inspector.waitForDebugger()
8
7
  }
9
8
 
9
+ import type * as otel from '@opentelemetry/api'
10
+
10
11
  import type { SyncOptions } from '@livestore/common'
11
12
  import { LogConfig, UnknownError } from '@livestore/common'
12
13
  import type { StreamEventsOptions } from '@livestore/common/leader-thread'
@@ -17,7 +18,6 @@ import { loadSqlite3Wasm } from '@livestore/sqlite-wasm/load-wasm'
17
18
  import { sqliteDbFactory } from '@livestore/sqlite-wasm/node'
18
19
  import { Effect, FetchHttpClient, Layer, OtelTracer, Schema, Stream, WorkerRunner } from '@livestore/utils/effect'
19
20
  import { PlatformNode } from '@livestore/utils/node'
20
- import type * as otel from '@opentelemetry/api'
21
21
 
22
22
  import type { TestingOverrides } from './leader-thread-shared.ts'
23
23
  import { makeLeaderThread } from './leader-thread-shared.ts'
@@ -42,7 +42,7 @@ export const makeWorker = (options: WorkerOptions) => {
42
42
  }
43
43
 
44
44
  export const makeWorkerEffect = (options: WorkerOptions) => {
45
- const TracingLive = options.otelOptions?.tracer
45
+ const TracingLive = options.otelOptions?.tracer !== undefined
46
46
  ? Layer.unwrapEffect(Effect.map(OtelTracer.make, Layer.setTracer)).pipe(
47
47
  Layer.provideMerge(Layer.succeed(OtelTracer.OtelTracer, options.otelOptions.tracer)),
48
48
  )
@@ -103,69 +103,60 @@ export const makeWorkerEffect = (options: WorkerOptions) => {
103
103
  ),
104
104
  Export: () =>
105
105
  Effect.andThen(LeaderThreadCtx, (_) => _.dbState.export()).pipe(
106
- UnknownError.mapToUnknownError,
107
106
  Effect.withSpan('@livestore/adapter-node:worker:Export'),
108
107
  ),
109
108
  ExportEventlog: () =>
110
109
  Effect.andThen(LeaderThreadCtx, (_) => _.dbEventlog.export()).pipe(
111
- UnknownError.mapToUnknownError,
112
110
  Effect.withSpan('@livestore/adapter-node:worker:ExportEventlog'),
113
111
  ),
114
- GetLeaderHead: () =>
115
- Effect.gen(function* () {
116
- const workerCtx = yield* LeaderThreadCtx
117
- return Eventlog.getClientHeadFromDb(workerCtx.dbEventlog)
118
- }).pipe(UnknownError.mapToUnknownError, Effect.withSpan('@livestore/adapter-node:worker:GetLeaderHead')),
119
- GetLeaderSyncState: () =>
120
- Effect.gen(function* () {
121
- const workerCtx = yield* LeaderThreadCtx
122
- return yield* workerCtx.syncProcessor.syncState
123
- }).pipe(UnknownError.mapToUnknownError, Effect.withSpan('@livestore/adapter-node:worker:GetLeaderSyncState')),
112
+ GetLeaderHead: Effect.fn('@livestore/adapter-node:worker:GetLeaderHead')(function* () {
113
+ const workerCtx = yield* LeaderThreadCtx
114
+ return Eventlog.getClientHeadFromDb(workerCtx.dbEventlog)
115
+ }),
116
+ GetLeaderSyncState: Effect.fn('@livestore/adapter-node:worker:GetLeaderSyncState')(function* () {
117
+ const workerCtx = yield* LeaderThreadCtx
118
+ return yield* workerCtx.syncProcessor.syncState
119
+ }),
124
120
  SyncStateStream: () =>
125
121
  Effect.gen(function* () {
126
122
  const workerCtx = yield* LeaderThreadCtx
127
123
  return workerCtx.syncProcessor.syncState.changes
128
124
  }).pipe(Stream.unwrapScoped),
129
- GetNetworkStatus: () =>
130
- Effect.gen(function* () {
131
- const workerCtx = yield* LeaderThreadCtx
132
- return yield* workerCtx.networkStatus
133
- }).pipe(UnknownError.mapToUnknownError, Effect.withSpan('@livestore/adapter-node:worker:GetNetworkStatus')),
125
+ GetNetworkStatus: Effect.fn('@livestore/adapter-node:worker:GetNetworkStatus')(function* () {
126
+ const workerCtx = yield* LeaderThreadCtx
127
+ return yield* workerCtx.networkStatus
128
+ }),
134
129
  NetworkStatusStream: () =>
135
130
  Effect.gen(function* () {
136
131
  const workerCtx = yield* LeaderThreadCtx
137
132
  return workerCtx.networkStatus.changes
138
133
  }).pipe(Stream.unwrapScoped),
139
- GetRecreateSnapshot: () =>
140
- Effect.gen(function* () {
141
- const workerCtx = yield* LeaderThreadCtx
142
- // const result = yield* Deferred.await(workerCtx.initialSetupDeferred)
143
- // NOTE we can only return the cached snapshot once as it's transferred (i.e. disposed), so we need to set it to undefined
144
- // const cachedSnapshot =
145
- // result._tag === 'Recreate' ? yield* Ref.getAndSet(result.snapshotRef, undefined) : undefined
146
- // return cachedSnapshot ?? workerCtx.db.export()
147
- const snapshot = workerCtx.dbState.export()
148
- return { snapshot, migrationsReport: workerCtx.initialState.migrationsReport }
149
- }).pipe(UnknownError.mapToUnknownError, Effect.withSpan('@livestore/adapter-node:worker:GetRecreateSnapshot')),
150
- Shutdown: () =>
151
- // @effect-diagnostics-next-line unnecessaryEffectGen:off
152
- Effect.gen(function* () {
153
- // const { db, dbEventlog } = yield* LeaderThreadCtx
154
- yield* Effect.logDebug('[@livestore/adapter-node:worker] Shutdown')
134
+ GetRecreateSnapshot: Effect.fn('@livestore/adapter-node:worker:GetRecreateSnapshot')(function* () {
135
+ const workerCtx = yield* LeaderThreadCtx
136
+ // const result = yield* Deferred.await(workerCtx.initialSetupDeferred)
137
+ // NOTE we can only return the cached snapshot once as it's transferred (i.e. disposed), so we need to set it to undefined
138
+ // const cachedSnapshot =
139
+ // result._tag === 'Recreate' ? yield* Ref.getAndSet(result.snapshotRef, undefined) : undefined
140
+ // return cachedSnapshot ?? workerCtx.db.export()
141
+ const snapshot = workerCtx.dbState.export()
142
+ return { snapshot, migrationsReport: workerCtx.initialState.migrationsReport }
143
+ }),
144
+ Shutdown: Effect.fn('@livestore/adapter-node:worker:Shutdown')(function* () {
145
+ // const { db, dbEventlog } = yield* LeaderThreadCtx
146
+ yield* Effect.logDebug('[@livestore/adapter-node:worker] Shutdown')
155
147
 
156
- // if (devtools.enabled) {
157
- // yield* FiberSet.clear(devtools.connections)
158
- // }
159
- // db.close()
160
- // dbEventlog.close()
148
+ // if (devtools.enabled) {
149
+ // yield* FiberSet.clear(devtools.connections)
150
+ // }
151
+ // db.close()
152
+ // dbEventlog.close()
161
153
 
162
- // Buy some time for Otel to flush
163
- // TODO find a cleaner way to do this
164
- // yield* Effect.sleep(1000)
165
- }).pipe(UnknownError.mapToUnknownError, Effect.withSpan('@livestore/adapter-node:worker:Shutdown')),
154
+ // Buy some time for Otel to flush
155
+ // TODO find a cleaner way to do this
156
+ // yield* Effect.sleep(1000)
157
+ }),
166
158
  ExtraDevtoolsMessage: ({ message }) =>
167
159
  Effect.andThen(LeaderThreadCtx, (_) => _.extraIncomingMessagesQueue.offer(message)).pipe(
168
- UnknownError.mapToUnknownError,
169
160
  Effect.withSpan('@livestore/adapter-node:worker:ExtraDevtoolsMessage'),
170
161
  ),
171
162
  }).pipe(
@@ -1,5 +1,6 @@
1
1
  import { ShutdownChannel } from '@livestore/common/leader-thread'
2
2
  import type { Effect, Scope } from '@livestore/utils/effect'
3
+
3
4
  import { makeBroadcastChannel } from './webchannel.ts'
4
5
 
5
6
  export const makeShutdownChannel = (