@livestore/adapter-node 0.3.0-dev.16 → 0.3.0-dev.18

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.
@@ -28,10 +28,23 @@ export const startDevtoolsServer = ({
28
28
  port: number
29
29
  }): Effect.Effect<void, UnexpectedError, Scope.Scope> =>
30
30
  Effect.gen(function* () {
31
- const httpServer = http.createServer()
32
- const webSocketServer = yield* makeWebSocketServer({ relayNodeName: 'ws' })
31
+ const httpServer = yield* Effect.sync(() => http.createServer()).pipe(
32
+ Effect.acquireRelease((httpServer) =>
33
+ Effect.async<void, UnexpectedError>((cb) => {
34
+ httpServer.removeAllListeners()
35
+ httpServer.closeAllConnections()
36
+ httpServer.close((err) => {
37
+ if (err) {
38
+ cb(Effect.fail(UnexpectedError.make({ cause: err })))
39
+ } else {
40
+ cb(Effect.succeed(undefined))
41
+ }
42
+ })
43
+ }).pipe(Effect.orDie),
44
+ ),
45
+ )
33
46
 
34
- yield* Effect.addFinalizer(() => Effect.sync(() => httpServer.close()))
47
+ const webSocketServer = yield* makeWebSocketServer({ relayNodeName: 'ws' })
35
48
 
36
49
  // Handle upgrade manually
37
50
  httpServer.on('upgrade', (request, socket, head) => {
@@ -46,7 +59,7 @@ export const startDevtoolsServer = ({
46
59
  cb(UnexpectedError.make({ cause: err }))
47
60
  })
48
61
 
49
- httpServer.listen(port, () => {
62
+ httpServer.listen(port, '0.0.0.0', () => {
50
63
  cb(Effect.succeed(undefined))
51
64
  })
52
65
  })
@@ -54,7 +67,7 @@ export const startDevtoolsServer = ({
54
67
  yield* startServer(port)
55
68
 
56
69
  yield* Effect.logDebug(
57
- `[@livestore/adapter-node:devtools] LiveStore devtools are available at http://localhost:${port}/livestore-devtools`,
70
+ `[@livestore/adapter-node:devtools] LiveStore devtools are available at http://localhost:${port}/_livestore`,
58
71
  )
59
72
 
60
73
  const viteServer = yield* makeViteServer({
@@ -12,9 +12,11 @@ import type { LiveStoreSchema } from '@livestore/common/schema'
12
12
  import { MutationEvent } from '@livestore/common/schema'
13
13
  import { sqliteDbFactory } from '@livestore/sqlite-wasm/browser'
14
14
  import { loadSqlite3Wasm } from '@livestore/sqlite-wasm/load-wasm'
15
- import { Effect, FetchHttpClient, Layer, Stream, SubscriptionRef, WebChannel } from '@livestore/utils/effect'
15
+ import { Cause, Effect, FetchHttpClient, Layer, Stream, SubscriptionRef } from '@livestore/utils/effect'
16
16
  import { nanoid } from '@livestore/utils/nanoid'
17
17
 
18
+ import { makeShutdownChannel } from '../shutdown-channel.js'
19
+
18
20
  // TODO unify in-memory adapter with other in-memory adapter implementations
19
21
 
20
22
  export interface InMemoryAdapterOptions {
@@ -31,6 +33,7 @@ export const makeInMemoryAdapter =
31
33
  ({
32
34
  schema,
33
35
  storeId,
36
+ shutdown,
34
37
  // devtoolsEnabled, bootStatusQueue, shutdown, connectDevtoolsToStore
35
38
  }) =>
36
39
  Effect.gen(function* () {
@@ -43,6 +46,17 @@ export const makeInMemoryAdapter =
43
46
 
44
47
  const sessionId = nanoid(6)
45
48
 
49
+ const shutdownChannel = yield* makeShutdownChannel(storeId)
50
+
51
+ yield* shutdownChannel.listen.pipe(
52
+ Stream.flatten(),
53
+ Stream.tap((error) => Effect.sync(() => shutdown(Cause.fail(error)))),
54
+ Stream.runDrain,
55
+ Effect.interruptible,
56
+ Effect.tapCauseLogPretty,
57
+ Effect.forkScoped,
58
+ )
59
+
46
60
  const { leaderThread, initialSnapshot } = yield* makeLeaderThread({
47
61
  storeId,
48
62
  clientId,
@@ -60,7 +74,7 @@ export const makeInMemoryAdapter =
60
74
  sessionId,
61
75
  lockStatus,
62
76
  leaderThread,
63
- shutdown: () => Effect.dieMessage('TODO implement shutdown'),
77
+ shutdown,
64
78
  } satisfies ClientSession
65
79
 
66
80
  return clientSession
@@ -88,7 +102,8 @@ const makeLeaderThread = ({
88
102
  devtoolsOptions: { enabled: false },
89
103
  makeSqliteDb,
90
104
  schema,
91
- shutdownChannel: yield* WebChannel.noopChannel<any, any>(),
105
+ // NOTE we're creating a separate channel here since you can't listen to your own channel messages
106
+ shutdownChannel: yield* makeShutdownChannel(storeId),
92
107
  storeId,
93
108
  syncOptions,
94
109
  }).pipe(Layer.provideMerge(FetchHttpClient.layer)),
@@ -112,7 +127,10 @@ const makeLeaderThread = ({
112
127
  pull: Stream.fromQueue(pullQueue),
113
128
  push: (batch) =>
114
129
  syncProcessor
115
- .push(batch.map((item) => new MutationEvent.EncodedWithMeta(item)))
130
+ .push(
131
+ batch.map((item) => new MutationEvent.EncodedWithMeta(item)),
132
+ { waitForProcessing: true },
133
+ )
116
134
  .pipe(Effect.provide(layer), Effect.scoped),
117
135
  },
118
136
  initialState: { leaderHead: initialLeaderHead, migrationsReport: initialState.migrationsReport },
@@ -68,7 +68,11 @@ export const makeWorkerEffect = (options: WorkerOptions) => {
68
68
  InitialMessage: (args) => makeLeaderThread({ ...args, syncOptions: options.sync }),
69
69
  PushToLeader: ({ batch }) =>
70
70
  Effect.andThen(LeaderThreadCtx, (_) =>
71
- _.syncProcessor.push(batch.map((item) => new MutationEvent.EncodedWithMeta(item))),
71
+ _.syncProcessor.push(
72
+ batch.map((item) => new MutationEvent.EncodedWithMeta(item)),
73
+ // We'll wait in order to keep back pressure on the client session
74
+ { waitForProcessing: true },
75
+ ),
72
76
  ).pipe(Effect.uninterruptible, Effect.withSpan('@livestore/adapter-node:worker:PushToLeader')),
73
77
  BootStatusStream: () =>
74
78
  Effect.andThen(LeaderThreadCtx, (_) => Stream.fromQueue(_.bootStatusQueue)).pipe(Stream.unwrap),
@@ -149,10 +153,13 @@ export const makeWorkerEffect = (options: WorkerOptions) => {
149
153
  ),
150
154
  }).pipe(
151
155
  Layer.provide(PlatformNode.NodeWorkerRunner.layer),
152
- Layer.launch,
156
+ WorkerRunner.launch,
153
157
  Effect.scoped,
154
158
  Effect.tapCauseLogPretty,
155
- Effect.annotateLogs({ thread: options.otelOptions?.serviceName ?? 'livestore-node-leader-thread' }),
159
+ Effect.annotateLogs({
160
+ thread: options.otelOptions?.serviceName ?? 'livestore-node-leader-thread',
161
+ processId: process.pid,
162
+ }),
156
163
  Effect.provide(Logger.prettyWithThread(options.otelOptions?.serviceName ?? 'livestore-node-leader-thread')),
157
164
  Effect.provide(FetchHttpClient.layer),
158
165
  Effect.provide(PlatformNode.NodeFileSystem.layer),
package/src/webchannel.ts CHANGED
@@ -28,14 +28,17 @@ export const makeBroadcastChannel = <Msg, MsgEncoded>({
28
28
  // )
29
29
 
30
30
  const listen = Stream.asyncPush<Either.Either<Msg, ParseResult.ParseError>>((emit) =>
31
- Effect.gen(function* () {
32
- // eslint-disable-next-line unicorn/prefer-add-event-listener
33
- channel.onmessage = (event: any) => {
34
- return emit.single(Schema.decodeEither(schema)(event.data))
35
- }
36
-
37
- return () => channel.unref()
38
- }),
31
+ Effect.acquireRelease(
32
+ Effect.gen(function* () {
33
+ // eslint-disable-next-line unicorn/prefer-add-event-listener
34
+ channel.onmessage = (event: any) => {
35
+ return emit.single(Schema.decodeEither(schema)(event.data))
36
+ }
37
+
38
+ return channel
39
+ }),
40
+ (channel) => Effect.sync(() => channel.unref()),
41
+ ),
39
42
  )
40
43
 
41
44
  const closedDeferred = yield* Deferred.make<void>().pipe(Effect.acquireRelease(Deferred.done(Exit.void)))
@@ -1,4 +1,4 @@
1
- import { BootStatus, Devtools, InvalidPushError, MigrationsReport, SyncState, UnexpectedError } from '@livestore/common'
1
+ import { BootStatus, Devtools, LeaderAheadError, MigrationsReport, SyncState, UnexpectedError } from '@livestore/common'
2
2
  import { EventId, MutationEvent } from '@livestore/common/schema'
3
3
  import { Schema, Transferable } from '@livestore/utils/effect'
4
4
 
@@ -85,8 +85,6 @@ export namespace LeaderWorkerInner {
85
85
  cursor: EventId.EventId,
86
86
  },
87
87
  success: Schema.Struct({
88
- // mutationEvents: Schema.Array(EncodedAny),
89
- // backendHead: Schema.Number,
90
88
  payload: SyncState.PayloadUpstream,
91
89
  remaining: Schema.Number,
92
90
  }),
@@ -98,7 +96,7 @@ export namespace LeaderWorkerInner {
98
96
  batch: Schema.Array(MutationEvent.AnyEncoded),
99
97
  },
100
98
  success: Schema.Void,
101
- failure: Schema.Union(UnexpectedError, InvalidPushError),
99
+ failure: Schema.Union(UnexpectedError, LeaderAheadError),
102
100
  }) {}
103
101
 
104
102
  export class Export extends Schema.TaggedRequest<Export>()('Export', {
package/tmp/pack.tgz ADDED
Binary file