@livestore/adapter-node 0.4.0-dev.9 → 0.5.0-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/client-session/adapter.d.ts +69 -3
  3. package/dist/client-session/adapter.d.ts.map +1 -1
  4. package/dist/client-session/adapter.js +160 -62
  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 +25 -19
  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 +22 -4
  14. package/dist/devtools/vite-dev-server.d.ts.map +1 -1
  15. package/dist/devtools/vite-dev-server.js +45 -11
  16. package/dist/devtools/vite-dev-server.js.map +1 -1
  17. package/dist/leader-thread-shared.d.ts +8 -7
  18. package/dist/leader-thread-shared.d.ts.map +1 -1
  19. package/dist/leader-thread-shared.js +19 -16
  20. package/dist/leader-thread-shared.js.map +1 -1
  21. package/dist/make-leader-worker.d.ts +5 -3
  22. package/dist/make-leader-worker.d.ts.map +1 -1
  23. package/dist/make-leader-worker.js +75 -59
  24. package/dist/make-leader-worker.js.map +1 -1
  25. package/dist/rpc-worker.d.ts +8 -0
  26. package/dist/rpc-worker.d.ts.map +1 -0
  27. package/dist/rpc-worker.js +5 -0
  28. package/dist/rpc-worker.js.map +1 -0
  29. package/dist/shutdown-channel.d.ts.map +1 -1
  30. package/dist/shutdown-channel.js.map +1 -1
  31. package/dist/webchannel.d.ts +1 -1
  32. package/dist/webchannel.d.ts.map +1 -1
  33. package/dist/webchannel.js +17 -18
  34. package/dist/webchannel.js.map +1 -1
  35. package/dist/worker-schema.d.ts +274 -138
  36. package/dist/worker-schema.d.ts.map +1 -1
  37. package/dist/worker-schema.js +79 -45
  38. package/dist/worker-schema.js.map +1 -1
  39. package/package.json +51 -20
  40. package/src/client-session/adapter.ts +228 -124
  41. package/src/devtools/devtools-server.ts +50 -35
  42. package/src/devtools/mod.ts +1 -1
  43. package/src/devtools/vite-dev-server.ts +65 -15
  44. package/src/leader-thread-shared.ts +37 -34
  45. package/src/make-leader-worker.ts +164 -115
  46. package/src/rpc-worker.ts +28 -0
  47. package/src/shutdown-channel.ts +1 -0
  48. package/src/webchannel.ts +21 -28
  49. package/src/worker-schema.ts +132 -117
@@ -1,6 +1,8 @@
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,
@@ -9,11 +11,10 @@ import {
9
11
  type LockStatus,
10
12
  type MakeSqliteDb,
11
13
  makeClientSession,
12
- type SyncError,
13
14
  type SyncOptions,
14
- UnexpectedError,
15
+ UnknownError,
15
16
  } from '@livestore/common'
16
- import { Eventlog, LeaderThreadCtx } from '@livestore/common/leader-thread'
17
+ import { Eventlog, LeaderThreadCtx, streamEventsWithSyncState } from '@livestore/common/leader-thread'
17
18
  import type { LiveStoreSchema } from '@livestore/common/schema'
18
19
  import { LiveStoreEvent } from '@livestore/common/schema'
19
20
  import { loadSqlite3Wasm } from '@livestore/sqlite-wasm/load-wasm'
@@ -27,20 +28,22 @@ import {
27
28
  Fiber,
28
29
  FileSystem,
29
30
  Layer,
30
- ParseResult,
31
31
  Queue,
32
+ RpcClient,
33
+ RpcWorker,
32
34
  Schedule,
33
35
  Schema,
36
+ Scope,
34
37
  Stream,
38
+ Subscribable,
35
39
  SubscriptionRef,
36
- Worker,
37
- WorkerError,
38
40
  } from '@livestore/utils/effect'
39
41
  import { PlatformNode } from '@livestore/utils/node'
40
42
  import * as Webmesh from '@livestore/webmesh'
41
43
 
42
44
  import type { TestingOverrides } from '../leader-thread-shared.ts'
43
45
  import { makeLeaderThread } from '../leader-thread-shared.ts'
46
+ import { type WithoutRpcClientError, dieOnRpcClientError, dieOnRpcClientErrorStream } from '../rpc-worker.ts'
44
47
  import { makeShutdownChannel } from '../shutdown-channel.ts'
45
48
  import * as WorkerSchema from '../worker-schema.ts'
46
49
 
@@ -87,7 +90,43 @@ export interface NodeAdapterOptions {
87
90
  }
88
91
  }
89
92
 
90
- /** Runs everything in the same thread. Use `makeWorkerAdapter` for multi-threaded implementation. */
93
+ /**
94
+ * Creates a single-threaded LiveStore adapter for Node.js applications.
95
+ *
96
+ * This adapter runs the leader thread (persistence and sync) in the same thread as
97
+ * your application. Suitable for CLI tools, scripts, and applications where simplicity
98
+ * is preferred over maximum performance.
99
+ *
100
+ * For production servers or performance-critical applications, consider `makeWorkerAdapter`
101
+ * which runs persistence/sync in a separate worker thread.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * import { makeAdapter } from '@livestore/adapter-node'
106
+ * import { makeWsSync } from '@livestore/sync-cf/client'
107
+ *
108
+ * const adapter = makeAdapter({
109
+ * storage: { type: 'fs', baseDirectory: './data' },
110
+ * sync: {
111
+ * backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
112
+ * },
113
+ * })
114
+ * ```
115
+ *
116
+ * @example
117
+ * ```ts
118
+ * // With DevTools support
119
+ * const adapter = makeAdapter({
120
+ * storage: { type: 'fs', baseDirectory: './data' },
121
+ * devtools: {
122
+ * schemaPath: new URL('./schema.ts', import.meta.url),
123
+ * port: 4242,
124
+ * },
125
+ * })
126
+ * ```
127
+ *
128
+ * @see https://livestore.dev/docs/reference/adapters/node for setup guide
129
+ */
91
130
  export const makeAdapter = ({
92
131
  sync,
93
132
  ...options
@@ -96,7 +135,36 @@ export const makeAdapter = ({
96
135
  }): Adapter => makeAdapterImpl({ ...options, leaderThread: { _tag: 'single-threaded', sync } })
97
136
 
98
137
  /**
99
- * Runs persistence and syncing in a worker thread.
138
+ * Creates a multi-threaded LiveStore adapter for Node.js applications.
139
+ *
140
+ * This adapter runs the leader thread (persistence, sync, and heavy SQLite operations)
141
+ * in a separate worker thread, keeping your main thread responsive. Recommended for
142
+ * production servers and performance-critical applications.
143
+ *
144
+ * You must create a worker file that calls `makeLeaderWorker()` and pass its URL
145
+ * to this function.
146
+ *
147
+ * @example
148
+ * ```ts
149
+ * // In your main file:
150
+ * import { makeWorkerAdapter } from '@livestore/adapter-node'
151
+ *
152
+ * const adapter = makeWorkerAdapter({
153
+ * storage: { type: 'fs', baseDirectory: './data' },
154
+ * workerUrl: new URL('./livestore.worker.ts', import.meta.url),
155
+ * })
156
+ * ```
157
+ *
158
+ * @example
159
+ * ```ts
160
+ * // In livestore.worker.ts:
161
+ * import { makeLeaderWorker } from '@livestore/adapter-node/worker'
162
+ * import { schema } from './schema'
163
+ *
164
+ * makeLeaderWorker({ schema })
165
+ * ```
166
+ *
167
+ * @see https://livestore.dev/docs/reference/adapters/node for setup guide
100
168
  */
101
169
  export const makeWorkerAdapter = ({
102
170
  workerUrl,
@@ -111,7 +179,7 @@ export const makeWorkerAdapter = ({
111
179
  * Extra arguments to pass to the worker which can be accessed in the worker
112
180
  * via `getWorkerArgs()`
113
181
  */
114
- workerExtraArgs?: Schema.JsonValue
182
+ workerExtraArgs?: Schema.Json
115
183
  }): Adapter => makeAdapterImpl({ ...options, leaderThread: { _tag: 'multi-threaded', workerUrl, workerExtraArgs } })
116
184
 
117
185
  const makeAdapterImpl = ({
@@ -132,12 +200,13 @@ const makeAdapterImpl = ({
132
200
  | {
133
201
  _tag: 'multi-threaded'
134
202
  workerUrl: URL
135
- workerExtraArgs: Schema.JsonValue | undefined
203
+ workerExtraArgs: Schema.Json | undefined
136
204
  }
137
205
  }): Adapter =>
138
206
  ((adapterArgs) =>
139
207
  Effect.gen(function* () {
140
- const { storeId, devtoolsEnabled, shutdown, bootStatusQueue, syncPayload, schema } = adapterArgs
208
+ const { storeId, devtoolsEnabled, shutdown, bootStatusQueue, syncPayloadEncoded, syncPayloadSchema, schema } =
209
+ adapterArgs
141
210
 
142
211
  yield* Queue.offer(bootStatusQueue, { stage: 'loading' })
143
212
 
@@ -157,15 +226,15 @@ const makeAdapterImpl = ({
157
226
  if (resetPersistence === true) {
158
227
  yield* shutdownChannel
159
228
  .send(IntentionalShutdownCause.make({ reason: 'adapter-reset' }))
160
- .pipe(UnexpectedError.mapToUnexpectedError)
229
+ .pipe(UnknownError.mapToUnknownError)
161
230
 
162
231
  yield* resetNodePersistence({ storage, storeId })
163
232
  }
164
233
 
165
234
  yield* shutdownChannel.listen.pipe(
166
- Stream.flatten(),
235
+ Stream.mapEffect(Effect.fromResult),
167
236
  Stream.tap((cause) =>
168
- shutdown(cause._tag === 'LiveStore.IntentionalShutdownCause' ? Exit.succeed(cause) : Exit.fail(cause)),
237
+ shutdown(cause._tag === 'IntentionalShutdownCause' ? Exit.succeed(cause) : Exit.fail(cause)),
169
238
  ),
170
239
  Stream.runDrain,
171
240
  Effect.interruptible,
@@ -178,8 +247,8 @@ const makeAdapterImpl = ({
178
247
  // TODO actually implement this multi-session support
179
248
  const lockStatus = yield* SubscriptionRef.make<LockStatus>('has-lock')
180
249
 
181
- const devtoolsOptions: WorkerSchema.LeaderWorkerInnerInitialMessage['devtools'] =
182
- devtoolsEnabled && devtoolsOptionsInput !== undefined
250
+ const devtoolsOptions: WorkerSchema.LeaderWorkerInnerInitialMessagePayload['devtools'] =
251
+ devtoolsEnabled === true && devtoolsOptionsInput !== undefined
183
252
  ? {
184
253
  enabled: true,
185
254
  schemaPath:
@@ -204,10 +273,11 @@ const makeAdapterImpl = ({
204
273
  storage,
205
274
  ...omitUndefineds({
206
275
  syncOptions: leaderThreadInput.sync,
207
- syncPayload,
276
+ syncPayloadEncoded,
277
+ syncPayloadSchema,
208
278
  testing,
209
279
  }),
210
- }).pipe(UnexpectedError.mapToUnexpectedError)
280
+ }).pipe(UnknownError.mapToUnknownError)
211
281
  : yield* makeWorkerLeaderThread({
212
282
  shutdown,
213
283
  storeId,
@@ -218,8 +288,8 @@ const makeAdapterImpl = ({
218
288
  storage,
219
289
  devtools: devtoolsOptions,
220
290
  bootStatusQueue,
221
- syncPayload,
222
- })
291
+ syncPayloadEncoded,
292
+ }).pipe(UnknownError.mapToUnknownError)
223
293
 
224
294
  syncInMemoryDb.import(initialSnapshot)
225
295
  syncInMemoryDb.debug.head = leaderThread.initialState.leaderHead
@@ -229,7 +299,7 @@ const makeAdapterImpl = ({
229
299
  sqliteDb: syncInMemoryDb,
230
300
  webmeshMode: 'proxy',
231
301
  connectWebmeshNode: Effect.fnUntraced(function* ({ webmeshNode }) {
232
- if (devtoolsOptions.enabled) {
302
+ if (devtoolsOptions.enabled === true) {
233
303
  yield* Webmesh.connectViaWebSocket({
234
304
  node: webmeshNode,
235
305
  url: `ws://${devtoolsOptions.host}:${devtoolsOptions.port}`,
@@ -244,6 +314,7 @@ const makeAdapterImpl = ({
244
314
  isLeader: true,
245
315
  // Not really applicable for node as there is no "reload the app" concept
246
316
  registerBeforeUnload: (_onBeforeUnload) => () => {},
317
+ origin: undefined,
247
318
  })
248
319
 
249
320
  return clientSession
@@ -258,7 +329,7 @@ const resetNodePersistence = ({
258
329
  }: {
259
330
  storage: WorkerSchema.StorageType
260
331
  storeId: string
261
- }): Effect.Effect<void, UnexpectedError, FileSystem.FileSystem> => {
332
+ }): Effect.Effect<void, UnknownError, FileSystem.FileSystem> => {
262
333
  if (storage.type !== 'fs') {
263
334
  return Effect.void
264
335
  }
@@ -268,13 +339,13 @@ const resetNodePersistence = ({
268
339
  return Effect.gen(function* () {
269
340
  const fs = yield* FileSystem.FileSystem
270
341
 
271
- const directoryExists = yield* fs.exists(directory).pipe(UnexpectedError.mapToUnexpectedError)
342
+ const directoryExists = yield* fs.exists(directory).pipe(UnknownError.mapToUnknownError)
272
343
 
273
344
  if (directoryExists === false) {
274
345
  return
275
346
  }
276
347
 
277
- yield* fs.remove(directory, { recursive: true }).pipe(UnexpectedError.mapToUnexpectedError)
348
+ yield* fs.remove(directory, { recursive: true }).pipe(UnknownError.mapToUnknownError)
278
349
  }).pipe(
279
350
  Effect.retry({ schedule: Schedule.exponentialBackoff10Sec }),
280
351
  Effect.withSpan('@livestore/adapter-node:resetPersistence', { attributes: { directory } }),
@@ -287,7 +358,8 @@ const makeLocalLeaderThread = ({
287
358
  schema,
288
359
  makeSqliteDb,
289
360
  syncOptions,
290
- syncPayload,
361
+ syncPayloadEncoded,
362
+ syncPayloadSchema,
291
363
  storage,
292
364
  devtools,
293
365
  testing,
@@ -298,29 +370,46 @@ const makeLocalLeaderThread = ({
298
370
  makeSqliteDb: MakeSqliteDb
299
371
  syncOptions: SyncOptions | undefined
300
372
  storage: WorkerSchema.StorageType
301
- syncPayload: Schema.JsonValue | undefined
302
- devtools: WorkerSchema.LeaderWorkerInnerInitialMessage['devtools']
373
+ syncPayloadEncoded: Schema.Json | undefined
374
+ syncPayloadSchema: Schema.Schema<any>
375
+ devtools: WorkerSchema.LeaderWorkerInnerInitialMessagePayload['devtools']
303
376
  testing?: {
304
377
  overrides?: TestingOverrides
305
378
  }
306
379
  }) =>
307
380
  Effect.gen(function* () {
308
- const layer = yield* Layer.build(
309
- makeLeaderThread({
310
- storeId,
311
- clientId,
312
- schema,
313
- syncOptions,
314
- storage,
315
- syncPayload,
316
- devtools,
317
- makeSqliteDb,
318
- ...omitUndefineds({ testing: testing?.overrides }),
319
- }).pipe(Layer.unwrapScoped),
320
- )
381
+ // The single-threaded node adapter runs the leader thread co-located in the same runtime as the
382
+ // client session. Unlike the worker adapters (where the leader lives on its own event loop and drains
383
+ // pushes independently), a co-located leader only makes progress when the surrounding fibers yield.
384
+ //
385
+ // We give the leader thread its own dedicated scope whose teardown finalizer is registered *before*
386
+ // `makeClientSession`. Finalizers run in reverse registration order, so on store shutdown the client
387
+ // session tears down first and only then is the leader thread scope closed — keeping the leader (and
388
+ // its SQLite connections) alive while the client-session pusher's in-flight batch is still being
389
+ // persisted. Building the leader into the ambient store scope instead (via `Layer.build`) interrupts
390
+ // the leader's push drainer concurrently with the client session, dropping the in-flight batch and
391
+ // causing silent data loss on commit-then-shutdown.
392
+ const leaderThreadScope = yield* Scope.make()
393
+ yield* Effect.addFinalizer((exit) => Scope.close(leaderThreadScope, exit))
394
+
395
+ const leaderThreadLayer = yield* makeLeaderThread({
396
+ storeId,
397
+ clientId,
398
+ schema,
399
+ syncOptions,
400
+ storage,
401
+ syncPayloadEncoded,
402
+ syncPayloadSchema,
403
+ devtools,
404
+ makeSqliteDb,
405
+ ...omitUndefineds({ testing: testing?.overrides }),
406
+ }).pipe(Scope.provide(leaderThreadScope))
407
+
408
+ const layer = yield* Layer.buildWithScope(leaderThreadLayer, leaderThreadScope)
321
409
 
322
410
  return yield* Effect.gen(function* () {
323
- const { dbState, dbEventlog, syncProcessor, extraIncomingMessagesQueue, initialState } = yield* LeaderThreadCtx
411
+ const { dbState, dbEventlog, syncProcessor, extraIncomingMessagesQueue, initialState, networkStatus } =
412
+ yield* LeaderThreadCtx
324
413
 
325
414
  const initialLeaderHead = Eventlog.getClientHeadFromDb(dbEventlog)
326
415
 
@@ -329,16 +418,30 @@ const makeLocalLeaderThread = ({
329
418
  events: {
330
419
  pull: ({ cursor }) => syncProcessor.pull({ cursor }),
331
420
  push: (batch) =>
332
- syncProcessor.push(
333
- batch.map((item) => new LiveStoreEvent.EncodedWithMeta(item)),
334
- { waitForProcessing: true },
335
- ),
421
+ // Mirror the worker `PushToLeader` handler which runs the leader push uninterruptibly.
422
+ // Together with the dedicated `leaderThreadScope` above, this guarantees an in-flight batch is
423
+ // fully persisted by the (still-live) leader before the co-located pusher fiber is interrupted
424
+ // on store shutdown.
425
+ syncProcessor
426
+ .push(batch.map((item) => new LiveStoreEvent.Client.EncodedWithMeta(item)))
427
+ .pipe(Effect.uninterruptible),
428
+ stream: (options) =>
429
+ streamEventsWithSyncState({
430
+ dbEventlog,
431
+ syncState: syncProcessor.syncState,
432
+ options,
433
+ }),
434
+ },
435
+ initialState: {
436
+ leaderHead: initialLeaderHead,
437
+ migrationsReport: initialState.migrationsReport,
438
+ storageMode: 'persisted',
336
439
  },
337
- initialState: { leaderHead: initialLeaderHead, migrationsReport: initialState.migrationsReport },
338
440
  export: Effect.sync(() => dbState.export()),
339
441
  getEventlogData: Effect.sync(() => dbEventlog.export()),
340
- getSyncState: syncProcessor.syncState,
341
- sendDevtoolsMessage: (message) => extraIncomingMessagesQueue.offer(message),
442
+ syncState: syncProcessor.syncState,
443
+ sendDevtoolsMessage: (message) => Queue.offer(extraIncomingMessagesQueue, message),
444
+ networkStatus,
342
445
  },
343
446
  { ...omitUndefineds({ overrides: testing?.overrides?.clientSession?.leaderThreadProxy }) },
344
447
  )
@@ -359,139 +462,140 @@ const makeWorkerLeaderThread = ({
359
462
  storage,
360
463
  devtools,
361
464
  bootStatusQueue,
362
- syncPayload,
465
+ syncPayloadEncoded,
363
466
  testing,
364
467
  }: {
365
- shutdown: (cause: Exit.Exit<IntentionalShutdownCause, UnexpectedError | SyncError>) => Effect.Effect<void>
468
+ shutdown: (cause: Exit.Exit<IntentionalShutdownCause, UnknownError>) => Effect.Effect<void>
366
469
  storeId: string
367
470
  clientId: string
368
471
  sessionId: string
369
472
  workerUrl: URL
370
- workerExtraArgs: Schema.JsonValue | undefined
473
+ workerExtraArgs: Schema.Json | undefined
371
474
  storage: WorkerSchema.StorageType
372
- devtools: WorkerSchema.LeaderWorkerInnerInitialMessage['devtools']
475
+ devtools: WorkerSchema.LeaderWorkerInnerInitialMessagePayload['devtools']
373
476
  bootStatusQueue: Queue.Queue<BootStatus>
374
- syncPayload: Schema.JsonValue | undefined
477
+ syncPayloadEncoded: Schema.Json | undefined
375
478
  testing?: {
376
479
  overrides?: TestingOverrides
377
480
  }
378
481
  }) =>
379
482
  Effect.gen(function* () {
380
483
  const nodeWorker = new WT.Worker(workerUrl, {
381
- execArgv: process.env.DEBUG_WORKER ? ['--inspect --enable-source-maps'] : ['--enable-source-maps'],
382
- argv: [Schema.encodeSync(WorkerSchema.WorkerArgv)({ storeId, clientId, sessionId, extraArgs: workerExtraArgs })],
383
- })
384
- const nodeWorkerLayer = yield* Layer.build(PlatformNode.NodeWorker.layer(() => nodeWorker))
385
-
386
- const worker = yield* Worker.makePoolSerialized<typeof WorkerSchema.LeaderWorkerInnerRequest.Type>({
387
- size: 1,
388
- concurrency: 100,
389
- initialMessage: () =>
390
- new WorkerSchema.LeaderWorkerInnerInitialMessage({
484
+ execArgv: process.env.DEBUG_WORKER !== undefined ? ['--inspect --enable-source-maps'] : ['--enable-source-maps'],
485
+ argv: [
486
+ yield* Schema.encodeEffect(WorkerSchema.WorkerArgv)({
391
487
  storeId,
392
488
  clientId,
393
- storage,
394
- devtools,
395
- syncPayload,
396
- }),
397
- }).pipe(
398
- Effect.provide(nodeWorkerLayer),
399
- UnexpectedError.mapToUnexpectedError,
400
- Effect.tapErrorCause((cause) => shutdown(Exit.failCause(cause))),
489
+ sessionId,
490
+ extraArgs: workerExtraArgs,
491
+ }).pipe(Effect.orDie),
492
+ ],
493
+ })
494
+ const innerWorkerContext = yield* Layer.build(
495
+ RpcClient.layerProtocolWorker({ size: 1, concurrency: 100 }).pipe(
496
+ Layer.provide(PlatformNode.NodeWorker.layer(() => nodeWorker)),
497
+ Layer.provide(
498
+ RpcWorker.layerInitialMessage(
499
+ WorkerSchema.LeaderWorkerInnerInitialMessage.payloadSchema,
500
+ Effect.succeed({ storeId, clientId, storage, devtools, syncPayloadEncoded }),
501
+ ),
502
+ ),
503
+ ),
504
+ )
505
+
506
+ const worker = yield* RpcClient.make(WorkerSchema.LeaderWorkerInnerRpcs).pipe(
507
+ Effect.provide(innerWorkerContext),
508
+ UnknownError.mapToUnknownError,
509
+ Effect.tapCause((cause) => shutdown(Exit.failCause(cause))),
401
510
  Effect.withSpan('@livestore/adapter-node:adapter:setupLeaderThread'),
402
511
  )
403
512
 
404
- const runInWorker = <TReq extends typeof WorkerSchema.LeaderWorkerInnerRequest.Type>(
405
- req: TReq,
406
- ): TReq extends Schema.WithResult<infer A, infer _I, infer _E, infer _EI, infer R>
407
- ? Effect.Effect<A, UnexpectedError, R>
408
- : never =>
409
- (worker.executeEffect(req) as any).pipe(
513
+ const runInWorker = <A, E, R>(
514
+ tag: string,
515
+ effect: Effect.Effect<A, E, R>,
516
+ ): Effect.Effect<A, WithoutRpcClientError<E>, R> =>
517
+ effect.pipe(
518
+ dieOnRpcClientError,
410
519
  Effect.logWarnIfTakesLongerThan({
411
- label: `@livestore/adapter-node:client-session:runInWorker:${req._tag}`,
520
+ label: `@livestore/adapter-node:client-session:runInWorker:${tag}`,
412
521
  duration: 2000,
413
522
  }),
414
- Effect.withSpan(`@livestore/adapter-node:client-session:runInWorker:${req._tag}`),
415
- Effect.mapError((cause) =>
416
- Schema.is(UnexpectedError)(cause)
417
- ? cause
418
- : ParseResult.isParseError(cause) || Schema.is(WorkerError.WorkerError)(cause)
419
- ? new UnexpectedError({ cause })
420
- : cause,
421
- ),
422
- Effect.catchAllDefect((cause) => new UnexpectedError({ cause })),
423
- ) as any
424
-
425
- const runInWorkerStream = <TReq extends typeof WorkerSchema.LeaderWorkerInnerRequest.Type>(
426
- req: TReq,
427
- ): TReq extends Schema.WithResult<infer A, infer _I, infer _E, infer _EI, infer R>
428
- ? Stream.Stream<A, UnexpectedError, R>
429
- : never =>
430
- worker.execute(req as any).pipe(
431
- Stream.mapError((cause) =>
432
- Schema.is(UnexpectedError)(cause)
433
- ? cause
434
- : ParseResult.isParseError(cause) || Schema.is(WorkerError.WorkerError)(cause)
435
- ? new UnexpectedError({ cause })
436
- : cause,
437
- ),
438
- Stream.withSpan(`@livestore/adapter-node:client-session:runInWorkerStream:${req._tag}`),
439
- ) as any
523
+ Effect.withSpan(`@livestore/adapter-node:client-session:runInWorker:${tag}`),
524
+ )
440
525
 
441
- const bootStatusFiber = yield* runInWorkerStream(new WorkerSchema.LeaderWorkerInnerBootStatusStream()).pipe(
526
+ const runInWorkerStream = <A, E, R>(
527
+ tag: string,
528
+ stream: Stream.Stream<A, E, R>,
529
+ ): Stream.Stream<A, WithoutRpcClientError<E>, R> =>
530
+ stream.pipe(
531
+ dieOnRpcClientErrorStream,
532
+ Stream.withSpan(`@livestore/adapter-node:client-session:runInWorkerStream:${tag}`),
533
+ )
534
+
535
+ const bootStatusFiber = yield* runInWorkerStream('BootStatusStream', worker.BootStatusStream({})).pipe(
442
536
  Stream.tap((bootStatus) => Queue.offer(bootStatusQueue, bootStatus)),
443
537
  Stream.runDrain,
444
- Effect.tapErrorCause((cause) => (Cause.isInterruptedOnly(cause) ? Effect.void : shutdown(Exit.failCause(cause)))),
538
+ Effect.tapCause((cause) =>
539
+ Cause.hasInterruptsOnly(cause) === true ? Effect.void : shutdown(Exit.failCause(cause)),
540
+ ),
445
541
  Effect.interruptible,
446
542
  Effect.tapCauseLogPretty,
447
543
  Effect.forkScoped,
448
544
  )
449
545
 
450
- yield* Queue.awaitShutdown(bootStatusQueue).pipe(
546
+ yield* Queue.await(bootStatusQueue).pipe(
451
547
  Effect.andThen(Fiber.interrupt(bootStatusFiber)),
452
548
  Effect.tapCauseLogPretty,
453
549
  Effect.forkScoped,
454
550
  )
455
551
 
456
- const initialLeaderHead = yield* runInWorker(new WorkerSchema.LeaderWorkerInnerGetLeaderHead())
552
+ const initialLeaderHead = yield* runInWorker('GetLeaderHead', worker.GetLeaderHead({}))
457
553
 
458
- const bootResult = yield* runInWorker(new WorkerSchema.LeaderWorkerInnerGetRecreateSnapshot()).pipe(
459
- Effect.timeout(10_000),
460
- UnexpectedError.mapToUnexpectedError,
554
+ const bootResult = yield* runInWorker('GetRecreateSnapshot', worker.GetRecreateSnapshot({})).pipe(
555
+ Effect.timeoutOrDie(10_000),
461
556
  Effect.withSpan('@livestore/adapter-node:client-session:export'),
462
557
  )
463
558
 
464
559
  const leaderThread = ClientSessionLeaderThreadProxy.of(
465
560
  {
466
561
  events: {
467
- pull: ({ cursor }) =>
468
- runInWorkerStream(new WorkerSchema.LeaderWorkerInnerPullStream({ cursor })).pipe(Stream.orDie),
562
+ pull: ({ cursor }) => runInWorkerStream('PullStream', worker.PullStream({ cursor })).pipe(Stream.orDie),
469
563
  push: (batch) =>
470
- runInWorker(new WorkerSchema.LeaderWorkerInnerPushToLeader({ batch })).pipe(
564
+ runInWorker('PushToLeader', worker.PushToLeader({ batch })).pipe(
471
565
  Effect.withSpan('@livestore/adapter-node:client-session:pushToLeader', {
472
566
  attributes: { batchSize: batch.length },
473
567
  }),
474
568
  ),
569
+ stream: (options) =>
570
+ runInWorkerStream('StreamEvents', worker.StreamEvents(options)).pipe(
571
+ Stream.withSpan('@livestore/adapter-node:client-session:streamEvents'),
572
+ Stream.orDie,
573
+ ),
475
574
  },
476
575
  initialState: {
477
576
  leaderHead: initialLeaderHead,
478
577
  migrationsReport: bootResult.migrationsReport,
578
+ storageMode: 'persisted',
479
579
  },
480
- export: runInWorker(new WorkerSchema.LeaderWorkerInnerExport()).pipe(
481
- Effect.timeout(10_000),
482
- UnexpectedError.mapToUnexpectedError,
580
+ export: runInWorker('Export', worker.Export({})).pipe(
581
+ Effect.timeoutOrDie(10_000),
483
582
  Effect.withSpan('@livestore/adapter-node:client-session:export'),
484
583
  ),
485
- getEventlogData: Effect.dieMessage('Not implemented'),
486
- getSyncState: runInWorker(new WorkerSchema.LeaderWorkerInnerGetLeaderSyncState()).pipe(
487
- UnexpectedError.mapToUnexpectedError,
488
- Effect.withSpan('@livestore/adapter-node:client-session:getLeaderSyncState'),
489
- ),
584
+ getEventlogData: Effect.die('Not implemented'),
585
+ syncState: Subscribable.make({
586
+ get: runInWorker('GetLeaderSyncState', worker.GetLeaderSyncState({})).pipe(
587
+ Effect.withSpan('@livestore/adapter-node:client-session:getLeaderSyncState'),
588
+ ),
589
+ changes: runInWorkerStream('SyncStateStream', worker.SyncStateStream({})).pipe(Stream.orDie),
590
+ }),
490
591
  sendDevtoolsMessage: (message) =>
491
- runInWorker(new WorkerSchema.LeaderWorkerInnerExtraDevtoolsMessage({ message })).pipe(
492
- UnexpectedError.mapToUnexpectedError,
592
+ runInWorker('ExtraDevtoolsMessage', worker.ExtraDevtoolsMessage({ message })).pipe(
493
593
  Effect.withSpan('@livestore/adapter-node:client-session:devtoolsMessageForLeader'),
494
594
  ),
595
+ networkStatus: Subscribable.make({
596
+ get: runInWorker('GetNetworkStatus', worker.GetNetworkStatus({})).pipe(Effect.orDie),
597
+ changes: runInWorkerStream('NetworkStatusStream', worker.NetworkStatusStream({})).pipe(Stream.orDie),
598
+ }),
495
599
  },
496
600
  {
497
601
  ...omitUndefineds({ overrides: testing?.overrides?.clientSession?.leaderThreadProxy }),