@livestore/webmesh 0.4.0 → 0.5.0-dev.1

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 (57) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/channel/direct-channel-internal.d.ts +2 -3
  3. package/dist/channel/direct-channel-internal.d.ts.map +1 -1
  4. package/dist/channel/direct-channel-internal.js +26 -21
  5. package/dist/channel/direct-channel-internal.js.map +1 -1
  6. package/dist/channel/direct-channel.d.ts.map +1 -1
  7. package/dist/channel/direct-channel.js +22 -23
  8. package/dist/channel/direct-channel.js.map +1 -1
  9. package/dist/channel/proxy-channel.d.ts +7 -8
  10. package/dist/channel/proxy-channel.d.ts.map +1 -1
  11. package/dist/channel/proxy-channel.js +28 -24
  12. package/dist/channel/proxy-channel.js.map +1 -1
  13. package/dist/common.d.ts +39 -41
  14. package/dist/common.d.ts.map +1 -1
  15. package/dist/common.js +3 -2
  16. package/dist/common.js.map +1 -1
  17. package/dist/mesh-schema.d.ts +262 -136
  18. package/dist/mesh-schema.d.ts.map +1 -1
  19. package/dist/mesh-schema.js +45 -44
  20. package/dist/mesh-schema.js.map +1 -1
  21. package/dist/mod.js +4 -4
  22. package/dist/node.d.ts +6 -6
  23. package/dist/node.d.ts.map +1 -1
  24. package/dist/node.js +27 -26
  25. package/dist/node.js.map +1 -1
  26. package/dist/node.test.js +80 -77
  27. package/dist/node.test.js.map +1 -1
  28. package/dist/utils.d.ts +1 -1
  29. package/dist/utils.d.ts.map +1 -1
  30. package/dist/utils.js.map +1 -1
  31. package/dist/websocket-edge.d.ts +18 -16
  32. package/dist/websocket-edge.d.ts.map +1 -1
  33. package/dist/websocket-edge.js +32 -43
  34. package/dist/websocket-edge.js.map +1 -1
  35. package/dist/websocket-edge.test.js +17 -17
  36. package/dist/websocket-edge.test.js.map +1 -1
  37. package/dist/worker/mod.d.ts +24 -0
  38. package/dist/worker/mod.d.ts.map +1 -0
  39. package/dist/worker/mod.js +44 -0
  40. package/dist/worker/mod.js.map +1 -0
  41. package/dist/worker/schema.d.ts +10 -0
  42. package/dist/worker/schema.d.ts.map +1 -0
  43. package/dist/worker/schema.js +7 -0
  44. package/dist/worker/schema.js.map +1 -0
  45. package/package.json +35 -52
  46. package/src/channel/direct-channel-internal.ts +30 -22
  47. package/src/channel/direct-channel.ts +26 -23
  48. package/src/channel/proxy-channel.ts +39 -36
  49. package/src/common.ts +6 -3
  50. package/src/mesh-schema.ts +34 -33
  51. package/src/node.test.ts +104 -91
  52. package/src/node.ts +32 -32
  53. package/src/utils.ts +3 -3
  54. package/src/websocket-edge.test.ts +16 -16
  55. package/src/websocket-edge.ts +48 -49
  56. package/src/worker/mod.ts +89 -0
  57. package/src/worker/schema.ts +8 -0
@@ -0,0 +1,44 @@
1
+ import { LS_DEV } from '@livestore/utils';
2
+ import { Context, Deferred, Effect, Layer, Queue, Stream, WebChannel } from '@livestore/utils/effect';
3
+ import * as WebmeshSchema from '../mesh-schema.js';
4
+ import { makeMeshNode } from '../node.js';
5
+ import * as WorkerSchema from './schema.js';
6
+ export * as Schema from './schema.js';
7
+ export class CacheService extends Context.Service()('@livestore/webmesh:worker:CacheService') {
8
+ static layer = ({ nodeName }) => Effect.gen(function* () {
9
+ const node = yield* makeMeshNode(nodeName);
10
+ globalThis.__debugWebmeshNode = node;
11
+ return CacheService.of({ node });
12
+ }).pipe(Layer.effect(CacheService));
13
+ }
14
+ export const CreateConnection = ({ from, port }) => Stream.callback((emit) => Effect.gen(function* () {
15
+ const { node } = yield* CacheService;
16
+ const messagePortChannel = yield* WebChannel.messagePortChannel({ port, schema: WebmeshSchema.Packet });
17
+ yield* node.addEdge({ target: from, edgeChannel: messagePortChannel, replaceIfExists: true });
18
+ if (LS_DEV === true) {
19
+ yield* Effect.logDebug(`@livestore/webmesh:worker: accepted edge: ${node.nodeName} <- ${from}`);
20
+ }
21
+ // `Stream.callback` exposes a queue-like emitter in Effect v4. Emit the single success value
22
+ // and then end the stream to preserve the old one-response worker command behavior.
23
+ yield* Queue.offer(emit, {});
24
+ yield* Queue.end(emit);
25
+ yield* Effect.spanEvent({ connectedTo: [...node.edgeKeys] });
26
+ }).pipe(Effect.orDie)).pipe(Stream.withSpan(`@livestore/webmesh:worker:create-connection:${from}`));
27
+ export const connectViaWorker = ({ node, target, worker, }) => Effect.gen(function* () {
28
+ const mc = new MessageChannel();
29
+ const isConnected = yield* Deferred.make();
30
+ if (LS_DEV === true) {
31
+ yield* Effect.addFinalizerLog(`@livestore/webmesh:worker: closing message channel ${node.nodeName} -> ${target}`);
32
+ }
33
+ yield* worker.execute(WorkerSchema.CreateConnection.make({ from: node.nodeName, port: mc.port1 })).pipe(Stream.tap(() => Deferred.succeed(isConnected, true)), Stream.runDrain, Effect.tapCauseLogPretty, Effect.forkScoped);
34
+ yield* Deferred.await(isConnected);
35
+ const workerConnection = yield* WebChannel.messagePortChannel({
36
+ port: mc.port2,
37
+ schema: WebmeshSchema.Packet,
38
+ });
39
+ yield* node.addEdge({ target, edgeChannel: workerConnection, replaceIfExists: true });
40
+ if (LS_DEV === true) {
41
+ yield* Effect.logDebug(`@livestore/webmesh:worker: initiated connection: ${node.nodeName} -> ${target}`);
42
+ }
43
+ });
44
+ //# sourceMappingURL=mod.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mod.js","sourceRoot":"","sources":["../../src/worker/mod.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { Schema, Transferable } from '@livestore/utils/effect';
2
+ export declare const CreateConnection: Schema.TaggedStruct<"WebmeshWorker.CreateConnection", {
3
+ readonly from: Schema.String;
4
+ readonly port: Transferable.Transferable<Schema.declare<MessagePort, MessagePort>>;
5
+ }>;
6
+ export declare const Request: Schema.Union<readonly [Schema.TaggedStruct<"WebmeshWorker.CreateConnection", {
7
+ readonly from: Schema.String;
8
+ readonly port: Transferable.Transferable<Schema.declare<MessagePort, MessagePort>>;
9
+ }>]>;
10
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/worker/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAE9D,eAAO,MAAM,gBAAgB;;;EAG3B,CAAA;AAEF,eAAO,MAAM,OAAO;;;IAAmC,CAAA"}
@@ -0,0 +1,7 @@
1
+ import { Schema, Transferable } from '@livestore/utils/effect';
2
+ export const CreateConnection = Schema.TaggedStruct('WebmeshWorker.CreateConnection', {
3
+ from: Schema.String,
4
+ port: Transferable.MessagePort,
5
+ });
6
+ export const Request = Schema.Union([CreateConnection]);
7
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/worker/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAE9D,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC,YAAY,CAAC,gCAAgC,EAAE;IACpF,IAAI,EAAE,MAAM,CAAC,MAAM;IACnB,IAAI,EAAE,YAAY,CAAC,WAAW;CAC/B,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,79 +1,62 @@
1
1
  {
2
+ "$genie": {
3
+ "source": "package.json.genie.ts",
4
+ "warning": "DO NOT EDIT - changes will be overwritten",
5
+ "workspaceClosureDirs": [
6
+ "packages/@livestore/utils",
7
+ "packages/@livestore/utils-dev",
8
+ "packages/@livestore/webmesh"
9
+ ]
10
+ },
2
11
  "name": "@livestore/webmesh",
3
- "version": "0.4.0",
12
+ "version": "0.5.0-dev.1",
13
+ "type": "module",
14
+ "sideEffects": false,
4
15
  "license": "Apache-2.0",
5
16
  "repository": {
6
17
  "type": "git",
7
18
  "url": "git+https://github.com/livestorejs/livestore.git"
8
19
  },
20
+ "exports": {
21
+ ".": "./dist/mod.js",
22
+ "./worker": "./dist/worker/mod.js"
23
+ },
9
24
  "files": [
10
25
  "dist",
11
26
  "package.json",
12
27
  "src"
13
28
  ],
14
- "type": "module",
15
- "sideEffects": false,
16
- "exports": {
17
- ".": "./dist/mod.js"
18
- },
19
- "publishConfig": {
20
- "access": "public"
21
- },
22
29
  "dependencies": {
23
- "@livestore/utils": "^0.4.0"
30
+ "@livestore/utils": "^0.5.0-dev.1"
24
31
  },
25
32
  "devDependencies": {
26
- "@effect/ai": "0.35.0",
27
- "@effect/cli": "0.75.1",
28
- "@effect/cluster": "0.58.2",
29
- "@effect/experimental": "0.60.0",
30
- "@effect/opentelemetry": "0.63.0",
31
- "@effect/platform": "0.96.1",
32
- "@effect/platform-browser": "0.76.0",
33
- "@effect/platform-bun": "0.89.0",
34
- "@effect/platform-node": "0.106.0",
35
- "@effect/printer": "0.49.0",
36
- "@effect/printer-ansi": "0.49.0",
37
- "@effect/rpc": "0.75.1",
38
- "@effect/sql": "0.51.1",
39
- "@effect/typeclass": "0.40.0",
40
- "@effect/vitest": "0.29.0",
33
+ "@effect/opentelemetry": "4.0.3",
34
+ "@effect/platform-browser": "4.0.3",
35
+ "@effect/platform-bun": "4.0.3",
36
+ "@effect/platform-node": "4.0.3",
37
+ "@effect/platform-node-shared": "4.0.3",
38
+ "@effect/vitest": "4.0.3",
39
+ "@livestore/utils-dev": "^0.5.0-dev.1",
41
40
  "@opentelemetry/api": "1.9.0",
42
41
  "@opentelemetry/resources": "2.2.0",
43
42
  "@standard-schema/spec": "1.1.0",
44
- "effect": "3.21.2",
45
- "vitest": "3.2.4",
46
- "@livestore/utils-dev": "^0.4.0"
43
+ "effect": "4.0.3",
44
+ "vitest": "4.1.9"
47
45
  },
48
46
  "peerDependencies": {
49
- "@effect/ai": "^0.35.0",
50
- "@effect/cli": "^0.75.1",
51
- "@effect/cluster": "^0.58.2",
52
- "@effect/experimental": "^0.60.0",
53
- "@effect/opentelemetry": "^0.63.0",
54
- "@effect/platform": "^0.96.1",
55
- "@effect/platform-browser": "^0.76.0",
56
- "@effect/platform-bun": "^0.89.0",
57
- "@effect/platform-node": "^0.106.0",
58
- "@effect/printer": "^0.49.0",
59
- "@effect/printer-ansi": "^0.49.0",
60
- "@effect/rpc": "^0.75.1",
61
- "@effect/sql": "^0.51.1",
62
- "@effect/typeclass": "^0.40.0",
63
- "@effect/vitest": "^0.29.0",
47
+ "@effect/opentelemetry": "^4.0.3",
48
+ "@effect/platform-browser": "^4.0.3",
49
+ "@effect/platform-bun": "^4.0.3",
50
+ "@effect/platform-node": "^4.0.3",
51
+ "@effect/platform-node-shared": "^4.0.3",
52
+ "@effect/vitest": "^4.0.3",
64
53
  "@opentelemetry/api": "^1.9.0",
65
54
  "@opentelemetry/resources": "^2.2.0",
66
55
  "@standard-schema/spec": "^1.1.0",
67
- "effect": "^3.21.2"
56
+ "effect": "^4.0.3"
68
57
  },
69
- "$genie": {
70
- "source": "package.json.genie.ts",
71
- "warning": "DO NOT EDIT - changes will be overwritten",
72
- "workspaceClosureDirs": [
73
- "packages/@livestore/utils",
74
- "packages/@livestore/utils-dev",
75
- "packages/@livestore/webmesh"
76
- ]
58
+ "publishConfig": {
59
+ "access": "public"
77
60
  },
78
61
  "scripts": {
79
62
  "test": "vitest"
@@ -1,9 +1,10 @@
1
1
  import { casesHandled, shouldNeverHappen } from '@livestore/utils'
2
- import type { PubSub } from '@livestore/utils/effect'
3
2
  import {
3
+ type PubSub,
4
4
  Deferred,
5
5
  Effect,
6
6
  Exit,
7
+ Fiber,
7
8
  Predicate,
8
9
  Queue,
9
10
  Schema,
@@ -52,7 +53,7 @@ export const makeDirectChannelInternal = ({
52
53
  }: MakeDirectChannelArgs & {
53
54
  channelVersion: number
54
55
  /** We're passing in the closeable scope from the wrapping direct channel */
55
- scope: Scope.CloseableScope
56
+ scope: Scope.Closeable
56
57
  sourceId: string
57
58
  }): Effect.Effect<
58
59
  WebChannel.WebChannel<any, any>,
@@ -89,8 +90,8 @@ export const makeDirectChannelInternal = ({
89
90
  const deferred = yield* makeDeferredResult()
90
91
 
91
92
  const schema = {
92
- send: Schema.Union(schema_.send, MeshSchema.DirectChannelPing, MeshSchema.DirectChannelPong),
93
- listen: Schema.Union(schema_.listen, MeshSchema.DirectChannelPing, MeshSchema.DirectChannelPong),
93
+ send: Schema.Union([schema_.send, MeshSchema.DirectChannelPing, MeshSchema.DirectChannelPong]),
94
+ listen: Schema.Union([schema_.listen, MeshSchema.DirectChannelPing, MeshSchema.DirectChannelPong]),
94
95
  }
95
96
 
96
97
  const channelStateRef: { current: ChannelState } = {
@@ -105,7 +106,9 @@ export const makeDirectChannelInternal = ({
105
106
  channelState: channelState._tag,
106
107
  packetId: packet.id,
107
108
  packetReqId: packet.reqId,
108
- ...(Predicate.hasProperty('channelVersion')(packet) === true ? { packetChannelVersion: packet.channelVersion } : {}),
109
+ ...(Predicate.hasProperty('channelVersion')(packet) === true
110
+ ? { packetChannelVersion: packet.channelVersion }
111
+ : {}),
109
112
  })
110
113
 
111
114
  // const reqIdStr =
@@ -185,7 +188,9 @@ export const makeDirectChannelInternal = ({
185
188
  remainingHops: packet.hops,
186
189
  reqId: packet.id,
187
190
  })
188
- yield* Effect.spanEvent(`Re-sending new request (${newRequestPacket.id}) for incoming request (${packet.id})`)
191
+ yield* Effect.spanEvent(
192
+ `Re-sending new request (${newRequestPacket.id}) for incoming request (${packet.id})`,
193
+ )
189
194
 
190
195
  yield* sendPacket(newRequestPacket)
191
196
  }
@@ -223,7 +228,7 @@ export const makeDirectChannelInternal = ({
223
228
 
224
229
  // Now we wait for the other side to respond via the channel
225
230
  yield* channel.listen.pipe(
226
- Stream.flatten(),
231
+ Stream.mapEffect(Effect.fromResult),
227
232
  Stream.filter(Schema.is(MeshSchema.DirectChannelPing)),
228
233
  Stream.take(1),
229
234
  Stream.runDrain,
@@ -258,11 +263,11 @@ export const makeDirectChannelInternal = ({
258
263
  }).pipe(Effect.andThen(WebChannel.toOpenChannel))
259
264
 
260
265
  const waitForPongFiber = yield* channel.listen.pipe(
261
- Stream.flatten(),
266
+ Stream.mapEffect(Effect.fromResult),
262
267
  Stream.filter(Schema.is(MeshSchema.DirectChannelPong)),
263
268
  Stream.take(1),
264
269
  Stream.runDrain,
265
- Effect.fork,
270
+ Effect.forkChild,
266
271
  )
267
272
 
268
273
  // There seems to be some scenario where the initial ping message is lost.
@@ -273,7 +278,7 @@ export const makeDirectChannelInternal = ({
273
278
  .send(MeshSchema.DirectChannelPing.make({}))
274
279
  .pipe(Effect.timeout(10), Effect.retry({ times: 2 }))
275
280
 
276
- yield* waitForPongFiber
281
+ yield* Fiber.join(waitForPongFiber)
277
282
 
278
283
  yield* Effect.spanEvent(`loser side: established`)
279
284
  channelStateRef.current = { _tag: 'Established', otherSourceId: channelState.otherSourceId }
@@ -292,17 +297,6 @@ export const makeDirectChannelInternal = ({
292
297
  }),
293
298
  )
294
299
 
295
- yield* Effect.gen(function* () {
296
- while (true) {
297
- const packet = yield* Queue.take(incomingPacketsQueue)
298
- const res = yield* processMessagePacket(packet)
299
- // We want to give requests another chance to be processed
300
- if (res === 'close') {
301
- return
302
- }
303
- }
304
- }).pipe(Effect.interruptible, Effect.tapCauseLogPretty, Effect.forkScoped)
305
-
306
300
  const channelState = channelStateRef.current
307
301
 
308
302
  if (channelState._tag !== 'Initial') {
@@ -336,7 +330,21 @@ export const makeDirectChannelInternal = ({
336
330
 
337
331
  yield* edgeRequest
338
332
 
339
- const channel = yield* deferred
333
+ // Start draining only after `edgeRequest` moved the channel to `RequestSent`. Under Effect v4
334
+ // an eagerly forked processor can otherwise handle an already-buffered packet while still
335
+ // `Initial`.
336
+ yield* Effect.gen(function* () {
337
+ while (true) {
338
+ const packet = yield* Queue.take(incomingPacketsQueue)
339
+ const res = yield* processMessagePacket(packet)
340
+ // We want to give requests another chance to be processed
341
+ if (res === 'close') {
342
+ return
343
+ }
344
+ }
345
+ }).pipe(Effect.interruptible, Effect.tapCauseLogPretty, Effect.forkScoped)
346
+
347
+ const channel = yield* Deferred.await(deferred)
340
348
 
341
349
  return channel
342
350
  }).pipe(Effect.withSpanScoped(`makeDirectChannel:${channelVersion}`))
@@ -2,14 +2,16 @@ import {
2
2
  Cause,
3
3
  Deferred,
4
4
  Effect,
5
- Either,
5
+ Fiber,
6
+ Result,
6
7
  Exit,
7
8
  Option,
8
9
  Queue,
10
+ References,
9
11
  Schema,
10
12
  Scope,
11
13
  Stream,
12
- TQueue,
14
+ TxQueue,
13
15
  WebChannel,
14
16
  } from '@livestore/utils/effect'
15
17
  import { nanoid } from '@livestore/utils/nanoid'
@@ -49,7 +51,7 @@ export const makeDirectChannel = ({
49
51
  const sourceId = nanoid()
50
52
 
51
53
  const listenQueue = yield* Queue.unbounded<any>()
52
- const sendQueue = yield* TQueue.unbounded<[msg: any, deferred: Deferred.Deferred<void>]>()
54
+ const sendQueue = yield* TxQueue.unbounded<[msg: any, deferred: Deferred.Deferred<void>]>()
53
55
 
54
56
  const initialEdgeDeferred = yield* Deferred.make<void>()
55
57
 
@@ -66,7 +68,7 @@ export const makeDirectChannel = ({
66
68
  const resultDeferred = yield* Deferred.make<{
67
69
  channel: WebChannel.WebChannel<any, any>
68
70
  channelVersion: number
69
- makeDirectChannelScope: Scope.CloseableScope
71
+ makeDirectChannelScope: Scope.Closeable
70
72
  }>()
71
73
 
72
74
  while (true) {
@@ -99,7 +101,7 @@ export const makeDirectChannel = ({
99
101
  Stream.take(1),
100
102
  Stream.runDrain,
101
103
  Effect.as('new-edge' as const),
102
- Effect.fork,
104
+ Effect.forkChild,
103
105
  )
104
106
 
105
107
  const makeChannel = makeDirectChannelInternal({
@@ -115,29 +117,30 @@ export const makeDirectChannel = ({
115
117
  sendPacket,
116
118
  scope: makeDirectChannelScope,
117
119
  }).pipe(
118
- Scope.extend(makeDirectChannelScope),
120
+ Scope.provide(makeDirectChannelScope),
119
121
  Effect.forkIn(makeDirectChannelScope),
120
- // Given we only call `Effect.exit` later when joining the fiber,
122
+ // Given we only await the exit later when observing the fiber,
121
123
  // we don't want Effect to produce a "unhandled error" log message
122
- Effect.withUnhandledErrorLogLevel(Option.none()),
124
+ Effect.provideService(References.UnhandledLogLevel, undefined),
123
125
  )
124
126
 
125
- const raceResult = yield* Effect.raceFirst(makeChannel, waitForNewEdgeFiber.pipe(Effect.disconnect))
127
+ const raceResult = yield* Effect.raceFirst(makeChannel, Fiber.join(waitForNewEdgeFiber))
126
128
 
127
129
  if (raceResult === 'new-edge') {
128
130
  yield* Scope.close(makeDirectChannelScope, Exit.fail('new-edge'))
129
131
  // We'll try again
130
132
  } else {
131
- const channelExit = yield* raceResult.pipe(Effect.exit)
133
+ const channelExit = yield* Fiber.await(raceResult)
132
134
  if (channelExit._tag === 'Failure') {
133
135
  yield* Scope.close(makeDirectChannelScope, channelExit)
134
136
 
135
137
  if (
136
- Cause.isFailType(channelExit.cause) === true &&
137
- Schema.is(WebmeshSchema.DirectChannelResponseNoTransferables)(channelExit.cause.error) === true
138
+ Option.exists(Cause.findErrorOption(channelExit.cause), (error) =>
139
+ Schema.is(WebmeshSchema.DirectChannelResponseNoTransferables)(error),
140
+ ) === true
138
141
  ) {
139
142
  // Only retry when there is a new edge available
140
- yield* waitForNewEdgeFiber.pipe(Effect.exit)
143
+ yield* Effect.exit(Fiber.join(waitForNewEdgeFiber))
141
144
  }
142
145
  } else {
143
146
  const channel = channelExit.value
@@ -149,7 +152,7 @@ export const makeDirectChannel = ({
149
152
  }
150
153
 
151
154
  // Now we wait until the first channel is established
152
- const { channel, makeDirectChannelScope, channelVersion } = yield* resultDeferred
155
+ const { channel, makeDirectChannelScope, channelVersion } = yield* Deferred.await(resultDeferred)
153
156
 
154
157
  yield* Effect.spanEvent(`Connected#${channelVersion}`)
155
158
  debugInfo.isConnected = true
@@ -159,9 +162,9 @@ export const makeDirectChannel = ({
159
162
 
160
163
  // We'll now forward all incoming messages to the listen queue
161
164
  yield* channel.listen.pipe(
162
- Stream.flatten(),
165
+ Stream.mapEffect(Effect.fromResult),
163
166
  // Stream.tap((msg) => Effect.log(`${target}→${channelName}→${nodeName}:message:${msg.message}`)),
164
- Stream.tapChunk((chunk) => Queue.offerAll(listenQueue, chunk)),
167
+ Stream.tapArray((array) => Queue.offerAll(listenQueue, array)),
165
168
  Stream.runDrain,
166
169
  Effect.tapCauseLogPretty,
167
170
  Effect.forkIn(makeDirectChannelScope),
@@ -169,18 +172,18 @@ export const makeDirectChannel = ({
169
172
 
170
173
  yield* Effect.gen(function* () {
171
174
  while (true) {
172
- const [msg, deferred] = yield* TQueue.peek(sendQueue)
175
+ const [msg, deferred] = yield* TxQueue.peek(sendQueue)
173
176
  // NOTE we don't need an explicit retry flow here since in case of the channel being closed,
174
177
  // the send will never succeed. Meanwhile the send-loop fiber will be interrupted and
175
178
  // given we only peeked at the queue, the message to send is still there.
176
179
  yield* channel.send(msg)
177
180
  yield* Deferred.succeed(deferred, void 0)
178
- yield* TQueue.take(sendQueue) // Remove the message from the queue
181
+ yield* TxQueue.take(sendQueue) // Remove the message from the queue
179
182
  }
180
183
  }).pipe(Effect.forkIn(makeDirectChannelScope))
181
184
 
182
185
  // Wait until the channel is closed and then try to reconnect
183
- yield* channel.closedDeferred
186
+ yield* Deferred.await(channel.closedDeferred)
184
187
 
185
188
  yield* Scope.close(makeDirectChannelScope, Exit.succeed('channel-closed'))
186
189
 
@@ -204,16 +207,16 @@ export const makeDirectChannel = ({
204
207
  debugInfo.pendingSends++
205
208
  debugInfo.totalSends++
206
209
 
207
- yield* TQueue.offer(sendQueue, [message, sentDeferred])
210
+ yield* TxQueue.offer(sendQueue, [message, sentDeferred])
208
211
 
209
- yield* sentDeferred
212
+ yield* Deferred.await(sentDeferred)
210
213
 
211
214
  debugInfo.pendingSends--
212
215
  }).pipe(Effect.scoped, Effect.withParentSpan(parentSpan))
213
216
 
214
- const listen = Stream.fromQueue(listenQueue, { maxChunkSize: 1 }).pipe(Stream.map(Either.right))
217
+ const listen = Stream.fromQueue(listenQueue).pipe(Stream.rechunk(1), Stream.map(Result.succeed))
215
218
 
216
- const closedDeferred = yield* Deferred.make<void>().pipe(Effect.acquireRelease(Deferred.done(Exit.void)))
219
+ const closedDeferred = yield* Effect.acquireRelease(Deferred.make<void>(), Deferred.done(Exit.void))
217
220
 
218
221
  const webChannel = {
219
222
  [WebChannel.WebChannelSymbol]: WebChannel.WebChannelSymbol,