@livestore/adapter-node 0.4.0-dev.20 → 0.4.0-dev.22

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.
@@ -13,7 +13,7 @@ import {
13
13
  type SyncOptions,
14
14
  UnknownError,
15
15
  } from '@livestore/common'
16
- import { Eventlog, LeaderThreadCtx } from '@livestore/common/leader-thread'
16
+ import { Eventlog, LeaderThreadCtx, streamEventsWithSyncState } from '@livestore/common/leader-thread'
17
17
  import type { LiveStoreSchema } from '@livestore/common/schema'
18
18
  import { LiveStoreEvent } from '@livestore/common/schema'
19
19
  import { loadSqlite3Wasm } from '@livestore/sqlite-wasm/load-wasm'
@@ -88,7 +88,43 @@ export interface NodeAdapterOptions {
88
88
  }
89
89
  }
90
90
 
91
- /** Runs everything in the same thread. Use `makeWorkerAdapter` for multi-threaded implementation. */
91
+ /**
92
+ * Creates a single-threaded LiveStore adapter for Node.js applications.
93
+ *
94
+ * This adapter runs the leader thread (persistence and sync) in the same thread as
95
+ * your application. Suitable for CLI tools, scripts, and applications where simplicity
96
+ * is preferred over maximum performance.
97
+ *
98
+ * For production servers or performance-critical applications, consider `makeWorkerAdapter`
99
+ * which runs persistence/sync in a separate worker thread.
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * import { makeAdapter } from '@livestore/adapter-node'
104
+ * import { makeWsSync } from '@livestore/sync-cf/client'
105
+ *
106
+ * const adapter = makeAdapter({
107
+ * storage: { type: 'fs', baseDirectory: './data' },
108
+ * sync: {
109
+ * backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
110
+ * },
111
+ * })
112
+ * ```
113
+ *
114
+ * @example
115
+ * ```ts
116
+ * // With DevTools support
117
+ * const adapter = makeAdapter({
118
+ * storage: { type: 'fs', baseDirectory: './data' },
119
+ * devtools: {
120
+ * schemaPath: new URL('./schema.ts', import.meta.url),
121
+ * port: 4242,
122
+ * },
123
+ * })
124
+ * ```
125
+ *
126
+ * @see https://livestore.dev/docs/reference/adapters/node for setup guide
127
+ */
92
128
  export const makeAdapter = ({
93
129
  sync,
94
130
  ...options
@@ -97,7 +133,36 @@ export const makeAdapter = ({
97
133
  }): Adapter => makeAdapterImpl({ ...options, leaderThread: { _tag: 'single-threaded', sync } })
98
134
 
99
135
  /**
100
- * Runs persistence and syncing in a worker thread.
136
+ * Creates a multi-threaded LiveStore adapter for Node.js applications.
137
+ *
138
+ * This adapter runs the leader thread (persistence, sync, and heavy SQLite operations)
139
+ * in a separate worker thread, keeping your main thread responsive. Recommended for
140
+ * production servers and performance-critical applications.
141
+ *
142
+ * You must create a worker file that calls `makeLeaderWorker()` and pass its URL
143
+ * to this function.
144
+ *
145
+ * @example
146
+ * ```ts
147
+ * // In your main file:
148
+ * import { makeWorkerAdapter } from '@livestore/adapter-node'
149
+ *
150
+ * const adapter = makeWorkerAdapter({
151
+ * storage: { type: 'fs', baseDirectory: './data' },
152
+ * workerUrl: new URL('./livestore.worker.ts', import.meta.url),
153
+ * })
154
+ * ```
155
+ *
156
+ * @example
157
+ * ```ts
158
+ * // In livestore.worker.ts:
159
+ * import { makeLeaderWorker } from '@livestore/adapter-node/worker'
160
+ * import { schema } from './schema'
161
+ *
162
+ * makeLeaderWorker({ schema })
163
+ * ```
164
+ *
165
+ * @see https://livestore.dev/docs/reference/adapters/node for setup guide
101
166
  */
102
167
  export const makeWorkerAdapter = ({
103
168
  workerUrl,
@@ -341,8 +406,18 @@ const makeLocalLeaderThread = ({
341
406
  batch.map((item) => new LiveStoreEvent.Client.EncodedWithMeta(item)),
342
407
  { waitForProcessing: true },
343
408
  ),
409
+ stream: (options) =>
410
+ streamEventsWithSyncState({
411
+ dbEventlog,
412
+ syncState: syncProcessor.syncState,
413
+ options,
414
+ }),
415
+ },
416
+ initialState: {
417
+ leaderHead: initialLeaderHead,
418
+ migrationsReport: initialState.migrationsReport,
419
+ storageMode: 'persisted',
344
420
  },
345
- initialState: { leaderHead: initialLeaderHead, migrationsReport: initialState.migrationsReport },
346
421
  export: Effect.sync(() => dbState.export()),
347
422
  getEventlogData: Effect.sync(() => dbEventlog.export()),
348
423
  syncState: syncProcessor.syncState,
@@ -481,10 +556,16 @@ const makeWorkerLeaderThread = ({
481
556
  attributes: { batchSize: batch.length },
482
557
  }),
483
558
  ),
559
+ stream: (options) =>
560
+ runInWorkerStream(new WorkerSchema.LeaderWorkerInnerStreamEvents(options)).pipe(
561
+ Stream.withSpan('@livestore/adapter-node:client-session:streamEvents'),
562
+ Stream.orDie,
563
+ ),
484
564
  },
485
565
  initialState: {
486
566
  leaderHead: initialLeaderHead,
487
567
  migrationsReport: bootResult.migrationsReport,
568
+ storageMode: 'persisted',
488
569
  },
489
570
  export: runInWorker(new WorkerSchema.LeaderWorkerInnerExport()).pipe(
490
571
  Effect.timeout(10_000),
@@ -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
@@ -96,7 +106,7 @@ export const startDevtoolsServer = ({
96
106
  } else {
97
107
  if (req.url === '/' || req.url === '') {
98
108
  return HttpServerResponse.redirect('/_livestore/node')
99
- } else if (req.url.startsWith('/_livestore')) {
109
+ } else if (shouldRouteToVite(req.url)) {
100
110
  // Here we're delegating to the Vite middleware
101
111
 
102
112
  // TODO replace this once @effect/platform-node supports Node HTTP middlewares
@@ -9,7 +9,8 @@ if (process.execArgv.includes('--inspect')) {
9
9
 
10
10
  import type { SyncOptions } from '@livestore/common'
11
11
  import { LogConfig, UnknownError } from '@livestore/common'
12
- import { Eventlog, LeaderThreadCtx } from '@livestore/common/leader-thread'
12
+ import type { StreamEventsOptions } from '@livestore/common/leader-thread'
13
+ import { Eventlog, LeaderThreadCtx, streamEventsWithSyncState } from '@livestore/common/leader-thread'
13
14
  import type { LiveStoreSchema } from '@livestore/common/schema'
14
15
  import { LiveStoreEvent } from '@livestore/common/schema'
15
16
  import { loadSqlite3Wasm } from '@livestore/sqlite-wasm/load-wasm'
@@ -86,6 +87,20 @@ export const makeWorkerEffect = (options: WorkerOptions) => {
86
87
  const { syncProcessor } = yield* LeaderThreadCtx
87
88
  return syncProcessor.pull({ cursor })
88
89
  }).pipe(Stream.unwrapScoped),
90
+ StreamEvents: (options: WorkerSchema.LeaderWorkerInnerStreamEvents) =>
91
+ LeaderThreadCtx.pipe(
92
+ Effect.map(({ dbEventlog, syncProcessor }) => {
93
+ const { _tag: _ignored, ...payload } = options
94
+ const streamOptions = payload as StreamEventsOptions
95
+ return streamEventsWithSyncState({
96
+ dbEventlog,
97
+ syncState: syncProcessor.syncState,
98
+ options: streamOptions,
99
+ })
100
+ }),
101
+ Stream.unwrapScoped,
102
+ Stream.withSpan('@livestore/adapter-node:worker:StreamEvents'),
103
+ ),
89
104
  Export: () =>
90
105
  Effect.andThen(LeaderThreadCtx, (_) => _.dbState.export()).pipe(
91
106
  UnknownError.mapToUnknownError,
@@ -7,6 +7,7 @@ import {
7
7
  SyncState,
8
8
  UnknownError,
9
9
  } from '@livestore/common'
10
+ import { StreamEventsOptionsFields } from '@livestore/common/leader-thread'
10
11
  import { EventSequenceNumber, LiveStoreEvent } from '@livestore/common/schema'
11
12
  import { Schema, Transferable } from '@livestore/utils/effect'
12
13
 
@@ -111,6 +112,15 @@ export class LeaderWorkerInnerPullStream extends Schema.TaggedRequest<LeaderWork
111
112
  failure: UnknownError,
112
113
  }) {}
113
114
 
115
+ export class LeaderWorkerInnerStreamEvents extends Schema.TaggedRequest<LeaderWorkerInnerStreamEvents>()(
116
+ 'StreamEvents',
117
+ {
118
+ payload: StreamEventsOptionsFields,
119
+ success: LiveStoreEvent.Client.Encoded,
120
+ failure: UnknownError,
121
+ },
122
+ ) {}
123
+
114
124
  export class LeaderWorkerInnerPushToLeader extends Schema.TaggedRequest<LeaderWorkerInnerPushToLeader>()(
115
125
  'PushToLeader',
116
126
  {
@@ -215,6 +225,7 @@ export const LeaderWorkerInnerRequest = Schema.Union(
215
225
  LeaderWorkerInnerInitialMessage,
216
226
  LeaderWorkerInnerBootStatusStream,
217
227
  LeaderWorkerInnerPullStream,
228
+ LeaderWorkerInnerStreamEvents,
218
229
  LeaderWorkerInnerPushToLeader,
219
230
  LeaderWorkerInnerExport,
220
231
  LeaderWorkerInnerGetRecreateSnapshot,