@livestore/webmesh 0.4.0 → 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/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 +23 -18
- 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 +20 -21
- package/dist/channel/direct-channel.js.map +1 -1
- package/dist/channel/proxy-channel.d.ts +7 -8
- package/dist/channel/proxy-channel.d.ts.map +1 -1
- package/dist/channel/proxy-channel.js +26 -22
- 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 +3 -2
- 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 +6 -6
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +22 -21
- package/dist/node.js.map +1 -1
- package/dist/node.test.js +74 -71
- 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 +18 -16
- package/dist/websocket-edge.d.ts.map +1 -1
- package/dist/websocket-edge.js +30 -36
- package/dist/websocket-edge.js.map +1 -1
- package/dist/websocket-edge.test.js +14 -14
- package/dist/websocket-edge.test.js.map +1 -1
- 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 +20 -37
- package/src/channel/direct-channel-internal.ts +30 -22
- package/src/channel/direct-channel.ts +26 -23
- package/src/channel/proxy-channel.ts +39 -36
- package/src/common.ts +6 -3
- package/src/mesh-schema.ts +34 -33
- package/src/node.test.ts +99 -86
- package/src/node.ts +32 -32
- package/src/utils.ts +3 -3
- package/src/websocket-edge.test.ts +14 -14
- package/src/websocket-edge.ts +47 -41
- 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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livestore/webmesh",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0-dev.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -14,57 +14,40 @@
|
|
|
14
14
|
"type": "module",
|
|
15
15
|
"sideEffects": false,
|
|
16
16
|
"exports": {
|
|
17
|
-
".": "./dist/mod.js"
|
|
17
|
+
".": "./dist/mod.js",
|
|
18
|
+
"./worker": "./dist/worker/mod.js"
|
|
18
19
|
},
|
|
19
20
|
"publishConfig": {
|
|
20
21
|
"access": "public"
|
|
21
22
|
},
|
|
22
23
|
"dependencies": {
|
|
23
|
-
"@livestore/utils": "^0.
|
|
24
|
+
"@livestore/utils": "^0.5.0-dev.0"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
|
-
"@effect/
|
|
27
|
-
"@effect/
|
|
28
|
-
"@effect/
|
|
29
|
-
"@effect/
|
|
30
|
-
"@effect/
|
|
31
|
-
"@effect/
|
|
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",
|
|
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",
|
|
41
33
|
"@opentelemetry/api": "1.9.0",
|
|
42
34
|
"@opentelemetry/resources": "2.2.0",
|
|
43
35
|
"@standard-schema/spec": "1.1.0",
|
|
44
|
-
"effect": "
|
|
45
|
-
"vitest": "
|
|
46
|
-
"@livestore/utils-dev": "^0.
|
|
36
|
+
"effect": "4.0.0-rc.111",
|
|
37
|
+
"vitest": "4.1.9",
|
|
38
|
+
"@livestore/utils-dev": "^0.5.0-dev.0"
|
|
47
39
|
},
|
|
48
40
|
"peerDependencies": {
|
|
49
|
-
"@effect/
|
|
50
|
-
"@effect/
|
|
51
|
-
"@effect/
|
|
52
|
-
"@effect/
|
|
53
|
-
"@effect/
|
|
54
|
-
"@effect/
|
|
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",
|
|
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",
|
|
64
47
|
"@opentelemetry/api": "^1.9.0",
|
|
65
48
|
"@opentelemetry/resources": "^2.2.0",
|
|
66
49
|
"@standard-schema/spec": "^1.1.0",
|
|
67
|
-
"effect": "^
|
|
50
|
+
"effect": "^4.0.0-rc.111"
|
|
68
51
|
},
|
|
69
52
|
"$genie": {
|
|
70
53
|
"source": "package.json.genie.ts",
|
|
@@ -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.
|
|
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
|
|
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(
|
|
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.
|
|
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.
|
|
266
|
+
Stream.mapEffect(Effect.fromResult),
|
|
262
267
|
Stream.filter(Schema.is(MeshSchema.DirectChannelPong)),
|
|
263
268
|
Stream.take(1),
|
|
264
269
|
Stream.runDrain,
|
|
265
|
-
Effect.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -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.
|
|
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
|
|
|
@@ -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,
|
|
@@ -1,9 +1,9 @@
|
|
|
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
|
+
Result,
|
|
7
7
|
Exit,
|
|
8
8
|
Fiber,
|
|
9
9
|
FiberHandle,
|
|
@@ -39,11 +39,11 @@ export const ProxyChannelSimulationParams = Schema.Struct({
|
|
|
39
39
|
*/
|
|
40
40
|
onPayload: Schema.Struct({
|
|
41
41
|
/** Delay before sending the ACK response (simulates slow ACK send) */
|
|
42
|
-
beforeAckSend: Schema.Int.
|
|
42
|
+
beforeAckSend: Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 500 })),
|
|
43
43
|
/** Delay after forking the ACK send, before adding message to listen queue */
|
|
44
|
-
afterAckFork: Schema.Int.
|
|
44
|
+
afterAckFork: Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 500 })),
|
|
45
45
|
/** Delay after adding message to listen queue */
|
|
46
|
-
afterListenQueueOffer: Schema.Int.
|
|
46
|
+
afterListenQueueOffer: Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 500 })),
|
|
47
47
|
}),
|
|
48
48
|
})
|
|
49
49
|
|
|
@@ -66,8 +66,8 @@ interface MakeProxyChannelArgs {
|
|
|
66
66
|
channelName: ChannelName
|
|
67
67
|
target: MeshNodeName
|
|
68
68
|
schema: {
|
|
69
|
-
send: Schema.
|
|
70
|
-
listen: Schema.
|
|
69
|
+
send: Schema.Codec<any, any>
|
|
70
|
+
listen: Schema.Codec<any, any>
|
|
71
71
|
}
|
|
72
72
|
/** Optional simulation parameters for testing timing-sensitive behavior */
|
|
73
73
|
simulation?: ProxyChannelSimulationParams
|
|
@@ -105,7 +105,7 @@ export const makeProxyChannel = ({
|
|
|
105
105
|
|
|
106
106
|
type ProxiedChannelStateEstablished = {
|
|
107
107
|
_tag: 'Established'
|
|
108
|
-
listenSchema: Schema.
|
|
108
|
+
listenSchema: Schema.Codec<any, any>
|
|
109
109
|
listenQueue: Queue.Queue<any>
|
|
110
110
|
ackMap: Map<string, Deferred.Deferred<void>>
|
|
111
111
|
combinedChannelId: string
|
|
@@ -131,6 +131,10 @@ export const makeProxyChannel = ({
|
|
|
131
131
|
const channelSpan = yield* Effect.currentSpan.pipe(Effect.orDie)
|
|
132
132
|
|
|
133
133
|
const connectedStateRef = yield* SubscriptionRef.make<ProxiedChannelStateEstablished | false>(false)
|
|
134
|
+
// Incoming packets can be processed as soon as the queue-draining fiber starts, so state
|
|
135
|
+
// referenced by established channels must exist before that processor is forked.
|
|
136
|
+
const listenQueue = yield* Queue.unbounded<any>()
|
|
137
|
+
const ackMap = new Map<string, Deferred.Deferred<void>>()
|
|
134
138
|
|
|
135
139
|
const waitForEstablished = Effect.gen(function* () {
|
|
136
140
|
const state = yield* SubscriptionRef.waitUntil(connectedStateRef, (state) => state !== false)
|
|
@@ -162,8 +166,9 @@ export const makeProxyChannel = ({
|
|
|
162
166
|
const getCombinedChannelId = (otherSideChannelIdCandidate: string) =>
|
|
163
167
|
[channelIdCandidate, otherSideChannelIdCandidate].toSorted().join('_')
|
|
164
168
|
|
|
165
|
-
const earlyPayloadBuffer = yield*
|
|
166
|
-
|
|
169
|
+
const earlyPayloadBuffer = yield* Effect.acquireRelease(
|
|
170
|
+
Queue.unbounded<typeof MeshSchema.ProxyChannelPayload.Type>(),
|
|
171
|
+
Queue.shutdown,
|
|
167
172
|
)
|
|
168
173
|
|
|
169
174
|
const processProxyPacket = ({ packet, respondToSender }: ProxyQueueItem) =>
|
|
@@ -267,7 +272,7 @@ export const makeProxyChannel = ({
|
|
|
267
272
|
const establishedState = channelStateRef.current
|
|
268
273
|
if (establishedState._tag === 'Established') {
|
|
269
274
|
//
|
|
270
|
-
const bufferedPackets = yield* Queue.
|
|
275
|
+
const bufferedPackets = yield* Queue.clear(earlyPayloadBuffer)
|
|
271
276
|
// yield* Effect.logDebug(
|
|
272
277
|
// `[${nodeName}] Draining early payload buffer (${bufferedPackets.length}) after ResponseSuccess`,
|
|
273
278
|
// )
|
|
@@ -278,10 +283,10 @@ export const makeProxyChannel = ({
|
|
|
278
283
|
)
|
|
279
284
|
continue
|
|
280
285
|
}
|
|
281
|
-
const decodedMessage = yield* Schema.
|
|
286
|
+
const decodedMessage = yield* Schema.decodeUnknownEffect(establishedState.listenSchema)(
|
|
282
287
|
bufferedPacket.payload,
|
|
283
288
|
)
|
|
284
|
-
yield*
|
|
289
|
+
yield* Queue.offer(establishedState.listenQueue, decodedMessage)
|
|
285
290
|
}
|
|
286
291
|
} else {
|
|
287
292
|
yield* Effect.logError(
|
|
@@ -327,8 +332,8 @@ export const makeProxyChannel = ({
|
|
|
327
332
|
yield* simSleep('onPayload', 'afterAckFork')
|
|
328
333
|
|
|
329
334
|
if (channelState._tag === 'Established') {
|
|
330
|
-
const decodedMessage = yield* Schema.
|
|
331
|
-
yield*
|
|
335
|
+
const decodedMessage = yield* Schema.decodeUnknownEffect(channelState.listenSchema)(packet.payload)
|
|
336
|
+
yield* Queue.offer(channelState.listenQueue, decodedMessage)
|
|
332
337
|
|
|
333
338
|
// Simulation point: delay after adding to listen queue
|
|
334
339
|
yield* simSleep('onPayload', 'afterListenQueueOffer')
|
|
@@ -370,19 +375,8 @@ export const makeProxyChannel = ({
|
|
|
370
375
|
}),
|
|
371
376
|
)
|
|
372
377
|
|
|
373
|
-
yield* Stream.fromQueue(queue).pipe(
|
|
374
|
-
Stream.tap(processProxyPacket),
|
|
375
|
-
Stream.runDrain,
|
|
376
|
-
Effect.tapCauseLogPretty,
|
|
377
|
-
Effect.forkScoped,
|
|
378
|
-
)
|
|
379
|
-
|
|
380
|
-
const listenQueue = yield* Queue.unbounded<any>()
|
|
381
|
-
|
|
382
378
|
yield* Effect.spanEvent(`Connecting`)
|
|
383
379
|
|
|
384
|
-
const ackMap = new Map<string, Deferred.Deferred<void>>()
|
|
385
|
-
|
|
386
380
|
// check if already established via incoming `ProxyChannelRequest` from other side
|
|
387
381
|
// which indicates we already have a edge to the target node
|
|
388
382
|
// const channelState = channelStateRef.current
|
|
@@ -401,6 +395,15 @@ export const makeProxyChannel = ({
|
|
|
401
395
|
Effect.forkScoped,
|
|
402
396
|
)
|
|
403
397
|
|
|
398
|
+
// Drain queued proxy packets only after the local side entered `Pending` and sent its
|
|
399
|
+
// request. Otherwise an already-buffered response can race initialization.
|
|
400
|
+
yield* Stream.fromQueue(queue).pipe(
|
|
401
|
+
Stream.tap(processProxyPacket),
|
|
402
|
+
Stream.runDrain,
|
|
403
|
+
Effect.tapCauseLogPretty,
|
|
404
|
+
Effect.forkScoped,
|
|
405
|
+
)
|
|
406
|
+
|
|
404
407
|
const { combinedChannelId: channelId } = yield* waitForEstablished
|
|
405
408
|
|
|
406
409
|
yield* Fiber.interrupt(retryOnNewEdgeFiber)
|
|
@@ -410,7 +413,7 @@ export const makeProxyChannel = ({
|
|
|
410
413
|
|
|
411
414
|
const send = (message: any) =>
|
|
412
415
|
Effect.gen(function* () {
|
|
413
|
-
const payload = yield* Schema.
|
|
416
|
+
const payload = yield* Schema.encodeUnknownEffect(schema.send)(message)
|
|
414
417
|
const sendFiberHandle = yield* FiberHandle.make<void, never>()
|
|
415
418
|
|
|
416
419
|
const sentDeferred = yield* Deferred.make<void>()
|
|
@@ -441,7 +444,7 @@ export const makeProxyChannel = ({
|
|
|
441
444
|
|
|
442
445
|
yield* sendPacket(packet)
|
|
443
446
|
|
|
444
|
-
yield* ack
|
|
447
|
+
yield* Deferred.await(ack)
|
|
445
448
|
yield* Deferred.succeed(sentDeferred, void 0)
|
|
446
449
|
|
|
447
450
|
debugInfo.pendingSends--
|
|
@@ -450,18 +453,18 @@ export const makeProxyChannel = ({
|
|
|
450
453
|
// TODO make this configurable
|
|
451
454
|
// Schedule.exponential(10): 10, 20, 40, 80, 160, 320, ...
|
|
452
455
|
yield* innerSend.pipe(Effect.timeout(100), Effect.retry(Schedule.exponential(10)), Effect.orDie)
|
|
453
|
-
}).pipe(Effect.
|
|
456
|
+
}).pipe(Effect.tapCause(Effect.logError))
|
|
454
457
|
|
|
455
|
-
const rerunOnNewChannelFiber = yield*
|
|
458
|
+
const rerunOnNewChannelFiber = yield* SubscriptionRef.changes(connectedStateRef).pipe(
|
|
456
459
|
Stream.filter((_) => _ === false),
|
|
457
460
|
Stream.tap(() => FiberHandle.run(sendFiberHandle, trySend)),
|
|
458
461
|
Stream.runDrain,
|
|
459
|
-
Effect.
|
|
462
|
+
Effect.forkChild,
|
|
460
463
|
)
|
|
461
464
|
|
|
462
465
|
yield* FiberHandle.run(sendFiberHandle, trySend)
|
|
463
466
|
|
|
464
|
-
yield* sentDeferred
|
|
467
|
+
yield* Deferred.await(sentDeferred)
|
|
465
468
|
|
|
466
469
|
yield* Fiber.interrupt(rerunOnNewChannelFiber)
|
|
467
470
|
}).pipe(
|
|
@@ -470,11 +473,11 @@ export const makeProxyChannel = ({
|
|
|
470
473
|
Effect.withParentSpan(channelSpan),
|
|
471
474
|
)
|
|
472
475
|
|
|
473
|
-
const listen = Stream.fromQueue(listenQueue).pipe(Stream.map(
|
|
476
|
+
const listen = Stream.fromQueue(listenQueue).pipe(Stream.map(Result.succeed))
|
|
474
477
|
|
|
475
|
-
const closedDeferred = yield* Deferred.make<void>()
|
|
478
|
+
const closedDeferred = yield* Effect.acquireRelease(Deferred.make<void>(), Deferred.done(Exit.void))
|
|
476
479
|
|
|
477
|
-
const
|
|
480
|
+
const services = yield* Effect.context()
|
|
478
481
|
|
|
479
482
|
const webChannel = {
|
|
480
483
|
[WebChannel.WebChannelSymbol]: WebChannel.WebChannelSymbol,
|
|
@@ -489,7 +492,7 @@ export const makeProxyChannel = ({
|
|
|
489
492
|
debug: {
|
|
490
493
|
ping: (message = 'ping') =>
|
|
491
494
|
send(WebChannel.DebugPingMessage.make({ message })).pipe(
|
|
492
|
-
Effect.provide(
|
|
495
|
+
Effect.provide(services),
|
|
493
496
|
Effect.tapCauseLogPretty,
|
|
494
497
|
Effect.runFork,
|
|
495
498
|
),
|