@livestore/adapter-node 0.3.0-dev.19 → 0.3.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.
@@ -3,6 +3,7 @@ import * as WT from 'node:worker_threads'
3
3
 
4
4
  import type {
5
5
  Adapter,
6
+ BootStatus,
6
7
  ClientSession,
7
8
  ClientSessionLeaderThreadProxy,
8
9
  IntentionalShutdownCause,
@@ -10,7 +11,7 @@ import type {
10
11
  NetworkStatus,
11
12
  } from '@livestore/common'
12
13
  import { Devtools, UnexpectedError } from '@livestore/common'
13
- import { makeNodeDevtoolsChannel } from '@livestore/devtools-node-common/web-channel'
14
+ import * as DevtoolsNode from '@livestore/devtools-node-common/web-channel'
14
15
  import { loadSqlite3Wasm } from '@livestore/sqlite-wasm/load-wasm'
15
16
  import { sqliteDbFactory } from '@livestore/sqlite-wasm/node'
16
17
  import {
@@ -18,6 +19,7 @@ import {
18
19
  Effect,
19
20
  Fiber,
20
21
  ParseResult,
22
+ Queue,
21
23
  Schema,
22
24
  Stream,
23
25
  SubscriptionRef,
@@ -49,6 +51,10 @@ export interface NodeAdapterOptions {
49
51
  * @default 4242
50
52
  */
51
53
  port: number
54
+ /**
55
+ * @default 'localhost'
56
+ */
57
+ host: string
52
58
  }
53
59
  }
54
60
 
@@ -59,13 +65,15 @@ export const makeNodeAdapter = ({
59
65
  workerUrl,
60
66
  schemaPath,
61
67
  baseDirectory,
62
- devtools: devtoolsOptions = { port: 4242 },
68
+ devtools: devtoolsOptions = { port: 4242, host: 'localhost' },
63
69
  clientId = hostname(),
64
70
  // TODO make this dynamic and actually support multiple sessions
65
71
  sessionId = 'static',
66
72
  }: NodeAdapterOptions): Adapter =>
67
- (({ storeId, devtoolsEnabled, shutdown, connectDevtoolsToStore }) =>
73
+ (({ storeId, devtoolsEnabled, shutdown, connectDevtoolsToStore, bootStatusQueue }) =>
68
74
  Effect.gen(function* () {
75
+ yield* Queue.offer(bootStatusQueue, { stage: 'loading' })
76
+
69
77
  const sqlite3 = yield* Effect.promise(() => loadSqlite3Wasm())
70
78
  const makeSqliteDb = yield* sqliteDbFactory({ sqlite3 })
71
79
 
@@ -103,20 +111,34 @@ export const makeNodeAdapter = ({
103
111
  devtoolsEnabled,
104
112
  devtoolsOptions,
105
113
  schemaPath,
114
+ bootStatusQueue,
106
115
  })
107
116
 
108
117
  syncInMemoryDb.import(initialSnapshot)
109
118
 
110
119
  if (devtoolsEnabled) {
111
120
  yield* Effect.gen(function* () {
112
- const storeDevtoolsChannel = yield* makeNodeDevtoolsChannel({
121
+ const webmeshNode = yield* DevtoolsNode.makeNodeDevtoolsConnectedMeshNode({
122
+ url: `ws://${devtoolsOptions.host}:${devtoolsOptions.port}`,
113
123
  nodeName: `client-session-${storeId}-${clientId}-${sessionId}`,
114
- target: `devtools`,
115
- url: `ws://localhost:${devtoolsOptions.port}`,
116
- schema: {
117
- listen: Devtools.ClientSession.MessageToApp,
118
- send: Devtools.ClientSession.MessageFromApp,
119
- },
124
+ })
125
+
126
+ const sessionsChannel = yield* webmeshNode.makeBroadcastChannel({
127
+ channelName: 'session-info',
128
+ schema: Devtools.SessionInfo.Message,
129
+ })
130
+
131
+ yield* Devtools.SessionInfo.provideSessionInfo({
132
+ webChannel: sessionsChannel,
133
+ sessionInfo: Devtools.SessionInfo.SessionInfo.make({ storeId, clientId, sessionId }),
134
+ }).pipe(Effect.tapCauseLogPretty, Effect.forkScoped)
135
+
136
+ webmeshNode.debug.print()
137
+
138
+ const storeDevtoolsChannel = yield* DevtoolsNode.makeChannelForConnectedMeshNode({
139
+ node: webmeshNode,
140
+ target: `devtools-${storeId}-${clientId}-${sessionId}`,
141
+ schema: { listen: Devtools.ClientSession.MessageToApp, send: Devtools.ClientSession.MessageFromApp },
120
142
  })
121
143
 
122
144
  yield* connectDevtoolsToStore(storeDevtoolsChannel)
@@ -154,6 +176,7 @@ const makeLeaderThread = ({
154
176
  devtoolsEnabled,
155
177
  devtoolsOptions,
156
178
  schemaPath,
179
+ // bootStatusQueue,
157
180
  }: {
158
181
  shutdown: (cause: Cause.Cause<UnexpectedError | IntentionalShutdownCause>) => void
159
182
  storeId: string
@@ -162,8 +185,9 @@ const makeLeaderThread = ({
162
185
  workerUrl: URL
163
186
  baseDirectory: string | undefined
164
187
  devtoolsEnabled: boolean
165
- devtoolsOptions: { port: number }
188
+ devtoolsOptions: { port: number; host: string }
166
189
  schemaPath: string
190
+ bootStatusQueue: Queue.Queue<BootStatus>
167
191
  }) =>
168
192
  Effect.gen(function* () {
169
193
  const nodeWorker = new WT.Worker(workerUrl, {
@@ -179,7 +203,7 @@ const makeLeaderThread = ({
179
203
  storeId,
180
204
  clientId,
181
205
  baseDirectory,
182
- devtools: { enabled: devtoolsEnabled, port: devtoolsOptions.port },
206
+ devtools: { enabled: devtoolsEnabled, port: devtoolsOptions.port, host: devtoolsOptions.host },
183
207
  schemaPath,
184
208
  }),
185
209
  }).pipe(
@@ -246,6 +270,39 @@ const makeLeaderThread = ({
246
270
  )
247
271
  }).pipe(Stream.unwrap) as any
248
272
 
273
+ const _bootStatusFiber = yield* runInWorkerStream(new WorkerSchema.LeaderWorkerInner.BootStatusStream()).pipe(
274
+ // TODO bring back when fixed https://github.com/Effect-TS/effect/issues/4576
275
+ // Stream.tap((bootStatus) => Queue.offer(bootStatusQueue, bootStatus)),
276
+ // TODO remove when fixed https://github.com/Effect-TS/effect/issues/4576
277
+ // Stream.tap(
278
+ // Effect.fn(function* (_) {
279
+ // if (yield* Queue.isShutdown(bootStatusQueue)) {
280
+ // return
281
+ // } else {
282
+ // console.log('offering boot status', _)
283
+ // // yield* Queue.offer(bootStatusQueue, _).pipe(
284
+ // // Effect.onInterrupt(() => Effect.log('boot status stream interrupted')),
285
+ // // // Effect.tapErrorCause((cause) => Effect.logError('error offering boot status', cause)),
286
+ // // )
287
+ // }
288
+ // }),
289
+ // ),
290
+ Stream.runDrain,
291
+ Effect.tapErrorCause((cause) =>
292
+ Cause.isInterruptedOnly(cause) ? Effect.void : Effect.sync(() => shutdown(cause)),
293
+ ),
294
+ Effect.interruptible,
295
+ Effect.tapCauseLogPretty,
296
+ Effect.forkScoped,
297
+ )
298
+
299
+ // TODO bring back when fixed https://github.com/Effect-TS/effect/issues/4576
300
+ // yield* Queue.awaitShutdown(bootStatusQueue).pipe(
301
+ // Effect.andThen(Fiber.interrupt(bootStatusFiber)),
302
+ // Effect.tapCauseLogPretty,
303
+ // Effect.forkScoped,
304
+ // )
305
+
249
306
  const initialLeaderHead = yield* runInWorker(new WorkerSchema.LeaderWorkerInner.GetLeaderHead())
250
307
 
251
308
  const networkStatus = yield* SubscriptionRef.make<NetworkStatus>({
@@ -20,11 +20,13 @@ export const startDevtoolsServer = ({
20
20
  clientId,
21
21
  sessionId,
22
22
  port,
23
+ host,
23
24
  }: {
24
25
  schemaPath: string
25
26
  storeId: string
26
27
  clientId: string
27
28
  sessionId: string
29
+ host: string
28
30
  port: number
29
31
  }): Effect.Effect<void, UnexpectedError, Scope.Scope> =>
30
32
  Effect.gen(function* () {
@@ -59,7 +61,7 @@ export const startDevtoolsServer = ({
59
61
  cb(UnexpectedError.make({ cause: err }))
60
62
  })
61
63
 
62
- httpServer.listen(port, '0.0.0.0', () => {
64
+ httpServer.listen(port, host, () => {
63
65
  cb(Effect.succeed(undefined))
64
66
  })
65
67
  })
@@ -67,11 +69,12 @@ export const startDevtoolsServer = ({
67
69
  yield* startServer(port)
68
70
 
69
71
  yield* Effect.logDebug(
70
- `[@livestore/adapter-node:devtools] LiveStore devtools are available at http://localhost:${port}/_livestore`,
72
+ `[@livestore/adapter-node:devtools] LiveStore devtools are available at http://${host}:${port}/_livestore/node/${storeId}/${clientId}/${sessionId}`,
71
73
  )
72
74
 
75
+ const clientSessionInfo = { storeId, clientId, sessionId }
73
76
  const viteServer = yield* makeViteServer({
74
- mode: { _tag: 'node', storeId, clientId, sessionId, url: `ws://localhost:${port}` },
77
+ mode: { _tag: 'node', clientSessionInfo, url: `ws://localhost:${port}` },
75
78
  schemaPath: path.resolve(process.cwd(), schemaPath),
76
79
  viteConfig: (viteConfig) => {
77
80
  if (LS_DEV) {
@@ -91,7 +94,7 @@ export const startDevtoolsServer = ({
91
94
 
92
95
  httpServer.on('request', (req, res) => {
93
96
  if (req.url === '/' || req.url === '') {
94
- res.writeHead(302, { Location: '/_livestore' })
97
+ res.writeHead(302, { Location: '/_livestore/node' })
95
98
  res.end()
96
99
  } else if (req.url?.startsWith('/_livestore')) {
97
100
  return viteServer.middlewares(req, res as any)
@@ -207,6 +207,7 @@ const makeLeaderThread = ({
207
207
  const devtoolsOptions = yield* makeDevtoolsOptions({
208
208
  devtoolsEnabled: devtools.enabled,
209
209
  devtoolsPort: devtools.port,
210
+ devtoolsHost: devtools.host,
210
211
  dbReadModel,
211
212
  dbMutationLog,
212
213
  storeId,
@@ -243,6 +244,7 @@ const makeDevtoolsOptions = ({
243
244
  storeId,
244
245
  clientId,
245
246
  devtoolsPort,
247
+ devtoolsHost,
246
248
  schemaPath,
247
249
  }: {
248
250
  devtoolsEnabled: boolean
@@ -251,6 +253,7 @@ const makeDevtoolsOptions = ({
251
253
  storeId: string
252
254
  clientId: string
253
255
  devtoolsPort: number
256
+ devtoolsHost: string
254
257
  schemaPath: string
255
258
  }): Effect.Effect<DevtoolsOptions, UnexpectedError, Scope.Scope> =>
256
259
  Effect.gen(function* () {
@@ -270,18 +273,18 @@ const makeDevtoolsOptions = ({
270
273
  clientId,
271
274
  sessionId: 'static', // TODO make this dynamic
272
275
  port: devtoolsPort,
276
+ host: devtoolsHost,
277
+ })
278
+
279
+ const devtoolsWebChannel = yield* makeNodeDevtoolsChannel({
280
+ nodeName: `leader-${storeId}-${clientId}`,
281
+ target: `devtools-${storeId}-${clientId}-static`,
282
+ url: `ws://localhost:${devtoolsPort}`,
283
+ schema: { listen: Devtools.Leader.MessageToApp, send: Devtools.Leader.MessageFromApp },
273
284
  })
274
285
 
275
286
  return {
276
- devtoolsWebChannel: yield* makeNodeDevtoolsChannel({
277
- nodeName: `leader-${storeId}-${clientId}`,
278
- target: `devtools`,
279
- url: `ws://localhost:${devtoolsPort}`,
280
- schema: {
281
- listen: Devtools.Leader.MessageToApp,
282
- send: Devtools.Leader.MessageFromApp,
283
- },
284
- }),
287
+ devtoolsWebChannel,
285
288
  persistenceInfo: {
286
289
  readModel: dbReadModel.metadata.persistenceInfo,
287
290
  mutationLog: dbMutationLog.metadata.persistenceInfo,
@@ -67,6 +67,7 @@ export namespace LeaderWorkerInner {
67
67
  schemaPath: Schema.String,
68
68
  devtools: Schema.Struct({
69
69
  port: Schema.Number,
70
+ host: Schema.String,
70
71
  enabled: Schema.Boolean,
71
72
  }),
72
73
  },
@@ -0,0 +1,30 @@
1
+ import { Effect, Queue, Stream } from '@livestore/utils/effect'
2
+ import { PlatformNode } from '@livestore/utils/node'
3
+
4
+ const main = Effect.gen(function* () {
5
+ const queue = yield* Queue.unbounded<number>()
6
+
7
+ yield* Queue.shutdown(queue)
8
+
9
+ // yield* Effect.gen(function* () {
10
+ // yield* Queue.offer(queue, 1)
11
+ // yield* Queue.shutdown(queue)
12
+ // }).pipe(Effect.delay(200), Effect.forkScoped)
13
+
14
+ yield* Effect.addFinalizer((exit) => Effect.log('finalizer', exit))
15
+
16
+ // const exit = yield* Stream.fromQueue(queue).pipe(
17
+ // Stream.tap((n) => Effect.log(n)),
18
+ // Stream.runDrain,
19
+ // Effect.exit,
20
+ // )
21
+
22
+ const exit = yield* Effect.andThen(Effect.void, () => Stream.fromQueue(queue)).pipe(
23
+ Stream.unwrap,
24
+ Stream.tap((n) => Effect.log(n)),
25
+ Stream.runDrain,
26
+ Effect.exit,
27
+ )
28
+
29
+ console.log('exit', exit)
30
+ }).pipe(Effect.scoped, PlatformNode.NodeRuntime.runMain)