@livestore/webmesh 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.
- package/README.md +5 -3
- package/dist/.tsbuildinfo +1 -1
- package/dist/channel/direct-channel-internal.d.ts +2 -3
- package/dist/channel/direct-channel-internal.d.ts.map +1 -1
- package/dist/channel/direct-channel-internal.js +35 -38
- package/dist/channel/direct-channel-internal.js.map +1 -1
- package/dist/channel/direct-channel.d.ts.map +1 -1
- package/dist/channel/direct-channel.js +22 -23
- package/dist/channel/direct-channel.js.map +1 -1
- package/dist/channel/proxy-channel.d.ts +29 -5
- package/dist/channel/proxy-channel.d.ts.map +1 -1
- package/dist/channel/proxy-channel.js +87 -37
- package/dist/channel/proxy-channel.js.map +1 -1
- package/dist/common.d.ts +33 -35
- package/dist/common.d.ts.map +1 -1
- package/dist/common.js +7 -7
- package/dist/common.js.map +1 -1
- package/dist/mesh-schema.d.ts +262 -136
- package/dist/mesh-schema.d.ts.map +1 -1
- package/dist/mesh-schema.js +45 -44
- package/dist/mesh-schema.js.map +1 -1
- package/dist/node.d.ts +12 -6
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +47 -44
- package/dist/node.js.map +1 -1
- package/dist/node.test.js +253 -63
- package/dist/node.test.js.map +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/websocket-edge.d.ts +20 -18
- package/dist/websocket-edge.d.ts.map +1 -1
- package/dist/websocket-edge.js +33 -41
- package/dist/websocket-edge.js.map +1 -1
- package/dist/websocket-edge.test.d.ts +7 -0
- package/dist/websocket-edge.test.d.ts.map +1 -0
- package/dist/websocket-edge.test.js +74 -0
- package/dist/websocket-edge.test.js.map +1 -0
- package/dist/worker/mod.d.ts +24 -0
- package/dist/worker/mod.d.ts.map +1 -0
- package/dist/worker/mod.js +44 -0
- package/dist/worker/mod.js.map +1 -0
- package/dist/worker/schema.d.ts +10 -0
- package/dist/worker/schema.d.ts.map +1 -0
- package/dist/worker/schema.js +7 -0
- package/dist/worker/schema.js.map +1 -0
- package/package.json +48 -12
- package/src/channel/direct-channel-internal.ts +41 -47
- package/src/channel/direct-channel.ts +28 -25
- package/src/channel/proxy-channel.ts +120 -57
- package/src/common.ts +9 -7
- package/src/mesh-schema.ts +34 -33
- package/src/node.test.ts +356 -80
- package/src/node.ts +63 -55
- package/src/utils.ts +3 -3
- package/src/websocket-edge.test.ts +98 -0
- package/src/websocket-edge.ts +52 -48
- package/src/worker/mod.ts +89 -0
- 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,26 +1,62 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livestore/webmesh",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0-dev.0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/livestorejs/livestore.git"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"package.json",
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
4
14
|
"type": "module",
|
|
5
15
|
"sideEffects": false,
|
|
6
16
|
"exports": {
|
|
7
17
|
".": "./dist/mod.js",
|
|
8
|
-
"./
|
|
18
|
+
"./worker": "./dist/worker/mod.js"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
9
22
|
},
|
|
10
23
|
"dependencies": {
|
|
11
|
-
"@livestore/utils": "0.
|
|
24
|
+
"@livestore/utils": "^0.5.0-dev.0"
|
|
12
25
|
},
|
|
13
26
|
"devDependencies": {
|
|
14
|
-
"
|
|
15
|
-
"@
|
|
27
|
+
"@effect/opentelemetry": "4.0.0-rc.111",
|
|
28
|
+
"@effect/platform-browser": "4.0.0-rc.111",
|
|
29
|
+
"@effect/platform-bun": "4.0.0-rc.111",
|
|
30
|
+
"@effect/platform-node": "4.0.0-rc.111",
|
|
31
|
+
"@effect/platform-node-shared": "4.0.0-rc.111",
|
|
32
|
+
"@effect/vitest": "4.0.0-rc.111",
|
|
33
|
+
"@opentelemetry/api": "1.9.0",
|
|
34
|
+
"@opentelemetry/resources": "2.2.0",
|
|
35
|
+
"@standard-schema/spec": "1.1.0",
|
|
36
|
+
"effect": "4.0.0-rc.111",
|
|
37
|
+
"vitest": "4.1.9",
|
|
38
|
+
"@livestore/utils-dev": "^0.5.0-dev.0"
|
|
16
39
|
},
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@effect/opentelemetry": "^4.0.0-rc.111",
|
|
42
|
+
"@effect/platform-browser": "^4.0.0-rc.111",
|
|
43
|
+
"@effect/platform-bun": "^4.0.0-rc.111",
|
|
44
|
+
"@effect/platform-node": "^4.0.0-rc.111",
|
|
45
|
+
"@effect/platform-node-shared": "^4.0.0-rc.111",
|
|
46
|
+
"@effect/vitest": "^4.0.0-rc.111",
|
|
47
|
+
"@opentelemetry/api": "^1.9.0",
|
|
48
|
+
"@opentelemetry/resources": "^2.2.0",
|
|
49
|
+
"@standard-schema/spec": "^1.1.0",
|
|
50
|
+
"effect": "^4.0.0-rc.111"
|
|
51
|
+
},
|
|
52
|
+
"$genie": {
|
|
53
|
+
"source": "package.json.genie.ts",
|
|
54
|
+
"warning": "DO NOT EDIT - changes will be overwritten",
|
|
55
|
+
"workspaceClosureDirs": [
|
|
56
|
+
"packages/@livestore/utils",
|
|
57
|
+
"packages/@livestore/utils-dev",
|
|
58
|
+
"packages/@livestore/webmesh"
|
|
59
|
+
]
|
|
24
60
|
},
|
|
25
61
|
"scripts": {
|
|
26
62
|
"test": "vitest"
|
|
@@ -1,10 +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
|
-
|
|
7
|
+
Fiber,
|
|
8
8
|
Predicate,
|
|
9
9
|
Queue,
|
|
10
10
|
Schema,
|
|
@@ -53,7 +53,7 @@ export const makeDirectChannelInternal = ({
|
|
|
53
53
|
}: MakeDirectChannelArgs & {
|
|
54
54
|
channelVersion: number
|
|
55
55
|
/** We're passing in the closeable scope from the wrapping direct channel */
|
|
56
|
-
scope: Scope.
|
|
56
|
+
scope: Scope.Closeable
|
|
57
57
|
sourceId: string
|
|
58
58
|
}): Effect.Effect<
|
|
59
59
|
WebChannel.WebChannel<any, any>,
|
|
@@ -89,14 +89,9 @@ export const makeDirectChannelInternal = ({
|
|
|
89
89
|
|
|
90
90
|
const deferred = yield* makeDeferredResult()
|
|
91
91
|
|
|
92
|
-
const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.catchAll(() => Effect.succeed(undefined)))
|
|
93
|
-
// const span = {
|
|
94
|
-
// addEvent: (...msg: any[]) => console.log(`${nodeName}→${channelName}→${target}[${channelVersion}]`, ...msg),
|
|
95
|
-
// }
|
|
96
|
-
|
|
97
92
|
const schema = {
|
|
98
|
-
send: Schema.Union(schema_.send, MeshSchema.DirectChannelPing, MeshSchema.DirectChannelPong),
|
|
99
|
-
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]),
|
|
100
95
|
}
|
|
101
96
|
|
|
102
97
|
const channelStateRef: { current: ChannelState } = {
|
|
@@ -107,11 +102,13 @@ export const makeDirectChannelInternal = ({
|
|
|
107
102
|
Effect.gen(function* () {
|
|
108
103
|
const channelState = channelStateRef.current
|
|
109
104
|
|
|
110
|
-
|
|
105
|
+
yield* Effect.spanEvent(`process:${packet._tag}`, {
|
|
111
106
|
channelState: channelState._tag,
|
|
112
107
|
packetId: packet.id,
|
|
113
108
|
packetReqId: packet.reqId,
|
|
114
|
-
|
|
109
|
+
...(Predicate.hasProperty('channelVersion')(packet) === true
|
|
110
|
+
? { packetChannelVersion: packet.channelVersion }
|
|
111
|
+
: {}),
|
|
115
112
|
})
|
|
116
113
|
|
|
117
114
|
// const reqIdStr =
|
|
@@ -130,7 +127,7 @@ export const makeDirectChannelInternal = ({
|
|
|
130
127
|
// If the other side has a higher version, we need to close this channel and
|
|
131
128
|
// recreate it with the new version
|
|
132
129
|
if (packet.channelVersion > channelVersion) {
|
|
133
|
-
|
|
130
|
+
yield* Effect.spanEvent(`incoming packet has higher version (${packet.channelVersion}), closing channel`)
|
|
134
131
|
yield* Scope.close(scope, Exit.succeed('higher-version-expected'))
|
|
135
132
|
// TODO include expected version in the error so the channel gets recreated with the new version
|
|
136
133
|
return 'close'
|
|
@@ -149,7 +146,7 @@ export const makeDirectChannelInternal = ({
|
|
|
149
146
|
remainingHops: packet.hops,
|
|
150
147
|
reqId: undefined,
|
|
151
148
|
})
|
|
152
|
-
|
|
149
|
+
yield* Effect.spanEvent(
|
|
153
150
|
`incoming packet has lower version (${packet.channelVersion}), sending request to reconnect (${newPacket.id})`,
|
|
154
151
|
)
|
|
155
152
|
|
|
@@ -164,7 +161,7 @@ export const makeDirectChannelInternal = ({
|
|
|
164
161
|
} else {
|
|
165
162
|
// In case the instance of the source has changed, we need to close the channel
|
|
166
163
|
// and reconnect with a new channel
|
|
167
|
-
|
|
164
|
+
yield* Effect.spanEvent(`force-new-channel`)
|
|
168
165
|
yield* Scope.close(scope, Exit.succeed('force-new-channel'))
|
|
169
166
|
return 'close'
|
|
170
167
|
}
|
|
@@ -191,15 +188,17 @@ export const makeDirectChannelInternal = ({
|
|
|
191
188
|
remainingHops: packet.hops,
|
|
192
189
|
reqId: packet.id,
|
|
193
190
|
})
|
|
194
|
-
|
|
191
|
+
yield* Effect.spanEvent(
|
|
192
|
+
`Re-sending new request (${newRequestPacket.id}) for incoming request (${packet.id})`,
|
|
193
|
+
)
|
|
195
194
|
|
|
196
195
|
yield* sendPacket(newRequestPacket)
|
|
197
196
|
}
|
|
198
197
|
|
|
199
198
|
const isWinner = nodeName > target
|
|
200
199
|
|
|
201
|
-
if (isWinner) {
|
|
202
|
-
|
|
200
|
+
if (isWinner === true) {
|
|
201
|
+
yield* Effect.spanEvent(`winner side: creating direct channel and sending response`)
|
|
203
202
|
const mc = new MessageChannel()
|
|
204
203
|
|
|
205
204
|
// We're using a direct channel with acks here to make sure messages are not lost
|
|
@@ -227,31 +226,27 @@ export const makeDirectChannelInternal = ({
|
|
|
227
226
|
|
|
228
227
|
channelStateRef.current = { _tag: 'winner:ResponseSent', channel, otherSourceId: packet.sourceId }
|
|
229
228
|
|
|
230
|
-
// span?.addEvent(`winner side: waiting for ping`)
|
|
231
|
-
|
|
232
229
|
// Now we wait for the other side to respond via the channel
|
|
233
230
|
yield* channel.listen.pipe(
|
|
234
|
-
Stream.
|
|
231
|
+
Stream.mapEffect(Effect.fromResult),
|
|
235
232
|
Stream.filter(Schema.is(MeshSchema.DirectChannelPing)),
|
|
236
233
|
Stream.take(1),
|
|
237
234
|
Stream.runDrain,
|
|
238
235
|
)
|
|
239
236
|
|
|
240
|
-
// span?.addEvent(`winner side: sending pong`)
|
|
241
|
-
|
|
242
237
|
yield* channel.send(MeshSchema.DirectChannelPong.make({}))
|
|
243
238
|
|
|
244
|
-
|
|
239
|
+
yield* Effect.spanEvent(`winner side: established`)
|
|
245
240
|
channelStateRef.current = { _tag: 'Established', otherSourceId: packet.sourceId }
|
|
246
241
|
|
|
247
242
|
yield* Deferred.succeed(deferred, channel)
|
|
248
243
|
} else {
|
|
249
|
-
|
|
244
|
+
yield* Effect.spanEvent(`loser side: waiting for response`)
|
|
250
245
|
// Wait for `DirectChannelResponseSuccess` packet
|
|
251
246
|
channelStateRef.current = { _tag: 'loser:WaitingForResponse', otherSourceId: packet.sourceId }
|
|
252
247
|
}
|
|
253
248
|
|
|
254
|
-
|
|
249
|
+
return
|
|
255
250
|
}
|
|
256
251
|
case 'DirectChannelResponseSuccess': {
|
|
257
252
|
if (channelState._tag !== 'loser:WaitingForResponse') {
|
|
@@ -268,15 +263,13 @@ export const makeDirectChannelInternal = ({
|
|
|
268
263
|
}).pipe(Effect.andThen(WebChannel.toOpenChannel))
|
|
269
264
|
|
|
270
265
|
const waitForPongFiber = yield* channel.listen.pipe(
|
|
271
|
-
Stream.
|
|
266
|
+
Stream.mapEffect(Effect.fromResult),
|
|
272
267
|
Stream.filter(Schema.is(MeshSchema.DirectChannelPong)),
|
|
273
268
|
Stream.take(1),
|
|
274
269
|
Stream.runDrain,
|
|
275
|
-
Effect.
|
|
270
|
+
Effect.forkChild,
|
|
276
271
|
)
|
|
277
272
|
|
|
278
|
-
// span?.addEvent(`loser side: sending ping`)
|
|
279
|
-
|
|
280
273
|
// There seems to be some scenario where the initial ping message is lost.
|
|
281
274
|
// As a workaround until we find the root cause, we're retrying the ping a few times.
|
|
282
275
|
// TODO write a test that reproduces this issue and fix the root cause ()
|
|
@@ -285,11 +278,9 @@ export const makeDirectChannelInternal = ({
|
|
|
285
278
|
.send(MeshSchema.DirectChannelPing.make({}))
|
|
286
279
|
.pipe(Effect.timeout(10), Effect.retry({ times: 2 }))
|
|
287
280
|
|
|
288
|
-
|
|
281
|
+
yield* Fiber.join(waitForPongFiber)
|
|
289
282
|
|
|
290
|
-
yield*
|
|
291
|
-
|
|
292
|
-
span?.addEvent(`loser side: established`)
|
|
283
|
+
yield* Effect.spanEvent(`loser side: established`)
|
|
293
284
|
channelStateRef.current = { _tag: 'Established', otherSourceId: channelState.otherSourceId }
|
|
294
285
|
|
|
295
286
|
yield* Deferred.succeed(deferred, channel)
|
|
@@ -306,17 +297,6 @@ export const makeDirectChannelInternal = ({
|
|
|
306
297
|
}),
|
|
307
298
|
)
|
|
308
299
|
|
|
309
|
-
yield* Effect.gen(function* () {
|
|
310
|
-
while (true) {
|
|
311
|
-
const packet = yield* Queue.take(incomingPacketsQueue)
|
|
312
|
-
const res = yield* processMessagePacket(packet)
|
|
313
|
-
// We want to give requests another chance to be processed
|
|
314
|
-
if (res === 'close') {
|
|
315
|
-
return
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}).pipe(Effect.interruptible, Effect.tapCauseLogPretty, Effect.forkScoped)
|
|
319
|
-
|
|
320
300
|
const channelState = channelStateRef.current
|
|
321
301
|
|
|
322
302
|
if (channelState._tag !== 'Initial') {
|
|
@@ -345,12 +325,26 @@ export const makeDirectChannelInternal = ({
|
|
|
345
325
|
}
|
|
346
326
|
|
|
347
327
|
yield* sendPacket(packet)
|
|
348
|
-
|
|
328
|
+
yield* Effect.spanEvent(`initial edge request sent (${packet.id})`)
|
|
349
329
|
})
|
|
350
330
|
|
|
351
331
|
yield* edgeRequest
|
|
352
332
|
|
|
353
|
-
|
|
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)
|
|
354
348
|
|
|
355
349
|
return channel
|
|
356
350
|
}).pipe(Effect.withSpanScoped(`makeDirectChannel:${channelVersion}`))
|
|
@@ -2,14 +2,16 @@ import {
|
|
|
2
2
|
Cause,
|
|
3
3
|
Deferred,
|
|
4
4
|
Effect,
|
|
5
|
-
|
|
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
|
-
|
|
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*
|
|
54
|
+
const sendQueue = yield* TxQueue.unbounded<[msg: any, deferred: Deferred.Deferred<void>]>()
|
|
53
55
|
|
|
54
56
|
const initialEdgeDeferred = yield* Deferred.make<void>()
|
|
55
57
|
|
|
@@ -61,12 +63,12 @@ export const makeDirectChannel = ({
|
|
|
61
63
|
innerChannelRef: { current: undefined as WebChannel.WebChannel<any, any> | undefined },
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
//#region reconnect-loop
|
|
65
67
|
yield* Effect.gen(function* () {
|
|
66
68
|
const resultDeferred = yield* Deferred.make<{
|
|
67
69
|
channel: WebChannel.WebChannel<any, any>
|
|
68
70
|
channelVersion: number
|
|
69
|
-
makeDirectChannelScope: Scope.
|
|
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.
|
|
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.
|
|
120
|
+
Scope.provide(makeDirectChannelScope),
|
|
119
121
|
Effect.forkIn(makeDirectChannelScope),
|
|
120
|
-
// Given we only
|
|
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.
|
|
124
|
+
Effect.provideService(References.UnhandledLogLevel, undefined),
|
|
123
125
|
)
|
|
124
126
|
|
|
125
|
-
const raceResult = yield* Effect.raceFirst(makeChannel,
|
|
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*
|
|
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.
|
|
137
|
-
|
|
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*
|
|
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.
|
|
165
|
+
Stream.mapEffect(Effect.fromResult),
|
|
163
166
|
// Stream.tap((msg) => Effect.log(`${target}→${channelName}→${nodeName}:message:${msg.message}`)),
|
|
164
|
-
Stream.
|
|
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*
|
|
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*
|
|
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
|
|
|
@@ -193,7 +196,7 @@ export const makeDirectChannel = ({
|
|
|
193
196
|
Effect.tapCauseLogPretty,
|
|
194
197
|
Effect.forkScoped,
|
|
195
198
|
)
|
|
196
|
-
|
|
199
|
+
//#endregion reconnect-loop
|
|
197
200
|
|
|
198
201
|
const parentSpan = yield* Effect.currentSpan.pipe(Effect.orDie)
|
|
199
202
|
|
|
@@ -204,16 +207,16 @@ export const makeDirectChannel = ({
|
|
|
204
207
|
debugInfo.pendingSends++
|
|
205
208
|
debugInfo.totalSends++
|
|
206
209
|
|
|
207
|
-
yield*
|
|
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
|
|
217
|
+
const listen = Stream.fromQueue(listenQueue).pipe(Stream.rechunk(1), Stream.map(Result.succeed))
|
|
215
218
|
|
|
216
|
-
const closedDeferred = yield* Deferred.make<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,
|