@moltzap/protocol 2026.401.0 → 2026.425.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/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/rpc-registry.d.ts +446 -0
- package/dist/rpc-registry.d.ts.map +1 -0
- package/dist/rpc-registry.js +67 -0
- package/dist/rpc-registry.js.map +1 -0
- package/dist/rpc.d.ts +42 -0
- package/dist/rpc.d.ts.map +1 -0
- package/dist/rpc.js +29 -0
- package/dist/rpc.js.map +1 -0
- package/dist/schema/apps.d.ts +86 -0
- package/dist/schema/apps.d.ts.map +1 -0
- package/dist/schema/apps.js +77 -0
- package/dist/schema/apps.js.map +1 -0
- package/dist/schema/contacts.d.ts +8 -14
- package/dist/schema/contacts.d.ts.map +1 -1
- package/dist/schema/contacts.js +9 -15
- package/dist/schema/contacts.js.map +1 -1
- package/dist/schema/conversations.d.ts +15 -8
- package/dist/schema/conversations.d.ts.map +1 -1
- package/dist/schema/conversations.js +18 -7
- package/dist/schema/conversations.js.map +1 -1
- package/dist/schema/delivery.d.ts +1 -4
- package/dist/schema/delivery.d.ts.map +1 -1
- package/dist/schema/delivery.js +2 -3
- package/dist/schema/delivery.js.map +1 -1
- package/dist/schema/errors.d.ts +13 -0
- package/dist/schema/errors.d.ts.map +1 -1
- package/dist/schema/errors.js +14 -0
- package/dist/schema/errors.js.map +1 -1
- package/dist/schema/events.d.ts +112 -85
- package/dist/schema/events.d.ts.map +1 -1
- package/dist/schema/events.js +84 -25
- package/dist/schema/events.js.map +1 -1
- package/dist/schema/identity.d.ts +26 -15
- package/dist/schema/identity.d.ts.map +1 -1
- package/dist/schema/identity.js +10 -14
- package/dist/schema/identity.js.map +1 -1
- package/dist/schema/index.d.ts +4 -2
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +4 -2
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/messages.d.ts +3 -7
- package/dist/schema/messages.d.ts.map +1 -1
- package/dist/schema/messages.js +4 -6
- package/dist/schema/messages.js.map +1 -1
- package/dist/schema/methods/apps.d.ts +123 -0
- package/dist/schema/methods/apps.d.ts.map +1 -0
- package/dist/schema/methods/apps.js +93 -0
- package/dist/schema/methods/apps.js.map +1 -0
- package/dist/schema/methods/auth.d.ts +110 -79
- package/dist/schema/methods/auth.d.ts.map +1 -1
- package/dist/schema/methods/auth.js +88 -91
- package/dist/schema/methods/auth.js.map +1 -1
- package/dist/schema/methods/contacts.d.ts +39 -66
- package/dist/schema/methods/contacts.d.ts.map +1 -1
- package/dist/schema/methods/contacts.js +35 -29
- package/dist/schema/methods/contacts.js.map +1 -1
- package/dist/schema/methods/conversations.d.ts +48 -52
- package/dist/schema/methods/conversations.d.ts.map +1 -1
- package/dist/schema/methods/conversations.js +89 -38
- package/dist/schema/methods/conversations.js.map +1 -1
- package/dist/schema/methods/invites.d.ts +1 -3
- package/dist/schema/methods/invites.d.ts.map +1 -1
- package/dist/schema/methods/invites.js +8 -1
- package/dist/schema/methods/invites.js.map +1 -1
- package/dist/schema/methods/messages.d.ts +12 -44
- package/dist/schema/methods/messages.d.ts.map +1 -1
- package/dist/schema/methods/messages.js +22 -28
- package/dist/schema/methods/messages.js.map +1 -1
- package/dist/schema/methods/presence.d.ts +7 -22
- package/dist/schema/methods/presence.d.ts.map +1 -1
- package/dist/schema/methods/presence.js +12 -6
- package/dist/schema/methods/presence.js.map +1 -1
- package/dist/schema/methods/push.d.ts +8 -6
- package/dist/schema/methods/push.d.ts.map +1 -1
- package/dist/schema/methods/push.js +20 -7
- package/dist/schema/methods/push.js.map +1 -1
- package/dist/schema/methods/system.d.ts +4 -0
- package/dist/schema/methods/system.d.ts.map +1 -0
- package/dist/schema/methods/system.js +11 -0
- package/dist/schema/methods/system.js.map +1 -0
- package/dist/schema/presence.d.ts +1 -12
- package/dist/schema/presence.d.ts.map +1 -1
- package/dist/schema/presence.js +2 -7
- package/dist/schema/presence.js.map +1 -1
- package/dist/schema/surfaces.d.ts +31 -25
- package/dist/schema/surfaces.d.ts.map +1 -1
- package/dist/schema/surfaces.js +40 -21
- package/dist/schema/surfaces.js.map +1 -1
- package/dist/testing/agent-registration.d.ts +49 -0
- package/dist/testing/agent-registration.d.ts.map +1 -0
- package/dist/testing/agent-registration.js +77 -0
- package/dist/testing/agent-registration.js.map +1 -0
- package/dist/testing/arbitraries/frames.d.ts +24 -0
- package/dist/testing/arbitraries/frames.d.ts.map +1 -0
- package/dist/testing/arbitraries/frames.js +36 -0
- package/dist/testing/arbitraries/frames.js.map +1 -0
- package/dist/testing/arbitraries/from-typebox.d.ts +37 -0
- package/dist/testing/arbitraries/from-typebox.d.ts.map +1 -0
- package/dist/testing/arbitraries/from-typebox.js +131 -0
- package/dist/testing/arbitraries/from-typebox.js.map +1 -0
- package/dist/testing/arbitraries/index.d.ts +4 -0
- package/dist/testing/arbitraries/index.d.ts.map +1 -0
- package/dist/testing/arbitraries/index.js +4 -0
- package/dist/testing/arbitraries/index.js.map +1 -0
- package/dist/testing/arbitraries/rpc.d.ts +66 -0
- package/dist/testing/arbitraries/rpc.d.ts.map +1 -0
- package/dist/testing/arbitraries/rpc.js +98 -0
- package/dist/testing/arbitraries/rpc.js.map +1 -0
- package/dist/testing/canonicalize.d.ts +38 -0
- package/dist/testing/canonicalize.d.ts.map +1 -0
- package/dist/testing/canonicalize.js +55 -0
- package/dist/testing/canonicalize.js.map +1 -0
- package/dist/testing/captures.d.ts +61 -0
- package/dist/testing/captures.d.ts.map +1 -0
- package/dist/testing/captures.js +99 -0
- package/dist/testing/captures.js.map +1 -0
- package/dist/testing/codec.d.ts +44 -0
- package/dist/testing/codec.d.ts.map +1 -0
- package/dist/testing/codec.js +170 -0
- package/dist/testing/codec.js.map +1 -0
- package/dist/testing/conformance/__divergence_proofs__/executable-proof-helpers.d.ts +6 -0
- package/dist/testing/conformance/__divergence_proofs__/executable-proof-helpers.d.ts.map +1 -0
- package/dist/testing/conformance/__divergence_proofs__/executable-proof-helpers.js +33 -0
- package/dist/testing/conformance/__divergence_proofs__/executable-proof-helpers.js.map +1 -0
- package/dist/testing/conformance/adversity.d.ts +36 -0
- package/dist/testing/conformance/adversity.d.ts.map +1 -0
- package/dist/testing/conformance/adversity.js +360 -0
- package/dist/testing/conformance/adversity.js.map +1 -0
- package/dist/testing/conformance/boundary.d.ts +56 -0
- package/dist/testing/conformance/boundary.d.ts.map +1 -0
- package/dist/testing/conformance/boundary.js +129 -0
- package/dist/testing/conformance/boundary.js.map +1 -0
- package/dist/testing/conformance/client/_fixtures.d.ts +71 -0
- package/dist/testing/conformance/client/_fixtures.d.ts.map +1 -0
- package/dist/testing/conformance/client/_fixtures.js +102 -0
- package/dist/testing/conformance/client/_fixtures.js.map +1 -0
- package/dist/testing/conformance/client/adversity.d.ts +39 -0
- package/dist/testing/conformance/client/adversity.d.ts.map +1 -0
- package/dist/testing/conformance/client/adversity.js +162 -0
- package/dist/testing/conformance/client/adversity.js.map +1 -0
- package/dist/testing/conformance/client/boundary.d.ts +12 -0
- package/dist/testing/conformance/client/boundary.d.ts.map +1 -0
- package/dist/testing/conformance/client/boundary.js +68 -0
- package/dist/testing/conformance/client/boundary.js.map +1 -0
- package/dist/testing/conformance/client/delivery.d.ts +38 -0
- package/dist/testing/conformance/client/delivery.d.ts.map +1 -0
- package/dist/testing/conformance/client/delivery.js +202 -0
- package/dist/testing/conformance/client/delivery.js.map +1 -0
- package/dist/testing/conformance/client/index.d.ts +16 -0
- package/dist/testing/conformance/client/index.d.ts.map +1 -0
- package/dist/testing/conformance/client/index.js +16 -0
- package/dist/testing/conformance/client/index.js.map +1 -0
- package/dist/testing/conformance/client/rpc-semantics.d.ts +26 -0
- package/dist/testing/conformance/client/rpc-semantics.d.ts.map +1 -0
- package/dist/testing/conformance/client/rpc-semantics.js +145 -0
- package/dist/testing/conformance/client/rpc-semantics.js.map +1 -0
- package/dist/testing/conformance/client/runner.d.ts +258 -0
- package/dist/testing/conformance/client/runner.d.ts.map +1 -0
- package/dist/testing/conformance/client/runner.js +228 -0
- package/dist/testing/conformance/client/runner.js.map +1 -0
- package/dist/testing/conformance/client/schema-conformance.d.ts +25 -0
- package/dist/testing/conformance/client/schema-conformance.d.ts.map +1 -0
- package/dist/testing/conformance/client/schema-conformance.js +123 -0
- package/dist/testing/conformance/client/schema-conformance.js.map +1 -0
- package/dist/testing/conformance/client/suite.d.ts +90 -0
- package/dist/testing/conformance/client/suite.d.ts.map +1 -0
- package/dist/testing/conformance/client/suite.js +209 -0
- package/dist/testing/conformance/client/suite.js.map +1 -0
- package/dist/testing/conformance/coverage-policy.d.ts +8 -0
- package/dist/testing/conformance/coverage-policy.d.ts.map +1 -0
- package/dist/testing/conformance/coverage-policy.js +10 -0
- package/dist/testing/conformance/coverage-policy.js.map +1 -0
- package/dist/testing/conformance/delivery.d.ts +40 -0
- package/dist/testing/conformance/delivery.d.ts.map +1 -0
- package/dist/testing/conformance/delivery.js +231 -0
- package/dist/testing/conformance/delivery.js.map +1 -0
- package/dist/testing/conformance/env.d.ts +3 -0
- package/dist/testing/conformance/env.d.ts.map +1 -0
- package/dist/testing/conformance/env.js +14 -0
- package/dist/testing/conformance/env.js.map +1 -0
- package/dist/testing/conformance/index.d.ts +10 -0
- package/dist/testing/conformance/index.d.ts.map +1 -0
- package/dist/testing/conformance/index.js +10 -0
- package/dist/testing/conformance/index.js.map +1 -0
- package/dist/testing/conformance/registry.d.ts +93 -0
- package/dist/testing/conformance/registry.d.ts.map +1 -0
- package/dist/testing/conformance/registry.js +62 -0
- package/dist/testing/conformance/registry.js.map +1 -0
- package/dist/testing/conformance/rpc-semantics.d.ts +67 -0
- package/dist/testing/conformance/rpc-semantics.d.ts.map +1 -0
- package/dist/testing/conformance/rpc-semantics.js +394 -0
- package/dist/testing/conformance/rpc-semantics.js.map +1 -0
- package/dist/testing/conformance/runner.d.ts +78 -0
- package/dist/testing/conformance/runner.d.ts.map +1 -0
- package/dist/testing/conformance/runner.js +65 -0
- package/dist/testing/conformance/runner.js.map +1 -0
- package/dist/testing/conformance/schema-conformance.d.ts +30 -0
- package/dist/testing/conformance/schema-conformance.d.ts.map +1 -0
- package/dist/testing/conformance/schema-conformance.js +229 -0
- package/dist/testing/conformance/schema-conformance.js.map +1 -0
- package/dist/testing/conformance/suite.d.ts +92 -0
- package/dist/testing/conformance/suite.d.ts.map +1 -0
- package/dist/testing/conformance/suite.js +233 -0
- package/dist/testing/conformance/suite.js.map +1 -0
- package/dist/testing/errors.d.ts +78 -0
- package/dist/testing/errors.d.ts.map +1 -0
- package/dist/testing/errors.js +34 -0
- package/dist/testing/errors.js.map +1 -0
- package/dist/testing/index.d.ts +25 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +37 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/models/dispatch.d.ts +61 -0
- package/dist/testing/models/dispatch.d.ts.map +1 -0
- package/dist/testing/models/dispatch.js +197 -0
- package/dist/testing/models/dispatch.js.map +1 -0
- package/dist/testing/models/index.d.ts +3 -0
- package/dist/testing/models/index.d.ts.map +1 -0
- package/dist/testing/models/index.js +3 -0
- package/dist/testing/models/index.js.map +1 -0
- package/dist/testing/models/state.d.ts +37 -0
- package/dist/testing/models/state.d.ts.map +1 -0
- package/dist/testing/models/state.js +14 -0
- package/dist/testing/models/state.js.map +1 -0
- package/dist/testing/test-client.d.ts +70 -0
- package/dist/testing/test-client.d.ts.map +1 -0
- package/dist/testing/test-client.js +266 -0
- package/dist/testing/test-client.js.map +1 -0
- package/dist/testing/test-server.d.ts +62 -0
- package/dist/testing/test-server.d.ts.map +1 -0
- package/dist/testing/test-server.js +134 -0
- package/dist/testing/test-server.js.map +1 -0
- package/dist/testing/toxics/client.d.ts +52 -0
- package/dist/testing/toxics/client.d.ts.map +1 -0
- package/dist/testing/toxics/client.js +120 -0
- package/dist/testing/toxics/client.js.map +1 -0
- package/dist/testing/toxics/defaults.d.ts +43 -0
- package/dist/testing/toxics/defaults.d.ts.map +1 -0
- package/dist/testing/toxics/defaults.js +9 -0
- package/dist/testing/toxics/defaults.js.map +1 -0
- package/dist/testing/toxics/index.d.ts +4 -0
- package/dist/testing/toxics/index.d.ts.map +1 -0
- package/dist/testing/toxics/index.js +4 -0
- package/dist/testing/toxics/index.js.map +1 -0
- package/dist/testing/toxics/profile.d.ts +69 -0
- package/dist/testing/toxics/profile.d.ts.map +1 -0
- package/dist/testing/toxics/profile.js +57 -0
- package/dist/testing/toxics/profile.js.map +1 -0
- package/dist/types.d.ts +7 -11
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -1
- package/dist/validators.d.ts +49 -174
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +77 -47
- package/dist/validators.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +18 -40
- package/dist/optional/contact-events.d.ts +0 -3
- package/dist/optional/contact-events.d.ts.map +0 -1
- package/dist/optional/contact-events.js +0 -5
- package/dist/optional/contact-events.js.map +0 -1
- package/dist/optional/contact-methods.d.ts +0 -5
- package/dist/optional/contact-methods.d.ts.map +0 -1
- package/dist/optional/contact-methods.js +0 -5
- package/dist/optional/contact-methods.js.map +0 -1
- package/dist/phone-hash.d.ts +0 -10
- package/dist/phone-hash.d.ts.map +0 -1
- package/dist/phone-hash.js +0 -17
- package/dist/phone-hash.js.map +0 -1
- package/dist/schema/methods/phone-contacts.d.ts +0 -26
- package/dist/schema/methods/phone-contacts.d.ts.map +0 -1
- package/dist/schema/methods/phone-contacts.js +0 -10
- package/dist/schema/methods/phone-contacts.js.map +0 -1
- package/dist/test-client.d.ts +0 -34
- package/dist/test-client.d.ts.map +0 -1
- package/dist/test-client.js +0 -176
- package/dist/test-client.js.map +0 -1
- package/dist/test-fixtures/phone-hashes.d.ts +0 -18
- package/dist/test-fixtures/phone-hashes.d.ts.map +0 -1
- package/dist/test-fixtures/phone-hashes.js +0 -24
- package/dist/test-fixtures/phone-hashes.js.map +0 -1
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TestClient — connects to a REAL MoltZap server URL and drives the wire.
|
|
3
|
+
*
|
|
4
|
+
* Per D1 (WS-only) and Invariant I1 (primitives never bypass the wire),
|
|
5
|
+
* every request is serialized and pushed through a real WebSocket transport
|
|
6
|
+
* — `@effect/platform/Socket.makeWebSocket` backed by
|
|
7
|
+
* `@effect/platform-node/NodeSocket.layerWebSocketConstructor` so the wire
|
|
8
|
+
* bytes match `packages/client`'s real production path.
|
|
9
|
+
*
|
|
10
|
+
* Satisfies AC2. Consumed by Tier A / B / C / D / E properties.
|
|
11
|
+
*/
|
|
12
|
+
import { Chunk, Context, Deferred, Duration, Effect, Exit, Ref, Scope, Stream, } from "effect";
|
|
13
|
+
import * as Socket from "@effect/platform/Socket";
|
|
14
|
+
import * as NodeSocket from "@effect/platform-node/NodeSocket";
|
|
15
|
+
import { PROTOCOL_VERSION } from "../version.js";
|
|
16
|
+
import { makeCaptureBuffer, recordFrame, recordMalformed, } from "./captures.js";
|
|
17
|
+
import { decodeFrame, encodeFrame, malformFrame, } from "./codec.js";
|
|
18
|
+
import { FrameSchemaError, RpcResponseError, RpcTimeoutError, TransportClosedError, TransportIoError, } from "./errors.js";
|
|
19
|
+
/** Context tag so property code can `Effect.serviceWith(TestClient, …)`. */
|
|
20
|
+
export const TestClient = Context.GenericTag("@moltzap/protocol/testing/TestClient");
|
|
21
|
+
let requestIdCounter = 0;
|
|
22
|
+
function nextRequestId() {
|
|
23
|
+
requestIdCounter += 1;
|
|
24
|
+
return `tc-${Date.now().toString(36)}-${requestIdCounter.toString(36)}`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Open a real WS connection to `config.serverUrl`, complete the `connect`
|
|
28
|
+
* handshake, and yield a `TestClient`. The surrounding `Scope` owns the
|
|
29
|
+
* socket; releasing it closes the WS and drains captures.
|
|
30
|
+
*/
|
|
31
|
+
export function makeTestClient(config) {
|
|
32
|
+
return Effect.gen(function* () {
|
|
33
|
+
const captures = yield* makeCaptureBuffer({
|
|
34
|
+
capacity: config.captureCapacity,
|
|
35
|
+
});
|
|
36
|
+
const pending = new Map();
|
|
37
|
+
const closeRef = yield* Ref.make({
|
|
38
|
+
closed: false,
|
|
39
|
+
code: 0,
|
|
40
|
+
reason: "",
|
|
41
|
+
});
|
|
42
|
+
const eventQueue = yield* Ref.make([]);
|
|
43
|
+
// Acquire the WS socket via @effect/platform. The Node WebSocket
|
|
44
|
+
// constructor layer is provided via `Effect.provide` at each use site
|
|
45
|
+
// so the test harness stays self-contained.
|
|
46
|
+
const socket = yield* Socket.makeWebSocket(config.serverUrl, {
|
|
47
|
+
openTimeout: Duration.millis(config.defaultTimeoutMs),
|
|
48
|
+
}).pipe(Effect.provide(NodeSocket.layerWebSocketConstructor), Effect.mapError((err) => new TransportIoError({ direction: "outbound", cause: err })));
|
|
49
|
+
const writer = yield* socket.writer.pipe(Effect.mapError((err) => new TransportIoError({ direction: "outbound", cause: err })));
|
|
50
|
+
const handleInbound = (raw) => Effect.gen(function* () {
|
|
51
|
+
const decoded = yield* Effect.either(decodeFrame(raw, "inbound"));
|
|
52
|
+
if (decoded._tag === "Left") {
|
|
53
|
+
yield* recordMalformed(captures, raw, "bit-flip");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const frame = decoded.right;
|
|
57
|
+
yield* recordFrame(captures, "inbound", raw, frame);
|
|
58
|
+
if (frame.type === "response") {
|
|
59
|
+
const def = pending.get(frame.id);
|
|
60
|
+
if (def !== undefined) {
|
|
61
|
+
pending.delete(frame.id);
|
|
62
|
+
if (frame.error !== undefined) {
|
|
63
|
+
yield* Deferred.fail(def, new RpcResponseError({
|
|
64
|
+
method: "",
|
|
65
|
+
requestId: frame.id,
|
|
66
|
+
code: frame.error.code,
|
|
67
|
+
message: frame.error.message,
|
|
68
|
+
data: frame.error.data,
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
yield* Deferred.succeed(def, frame);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (frame.type === "event") {
|
|
78
|
+
yield* Ref.update(eventQueue, (q) => [...q, frame]);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
// Fork the reader fiber into the ambient scope. `socket.runRaw` yields
|
|
82
|
+
// every received string/bytes chunk; teardown is on scope close.
|
|
83
|
+
yield* Effect.forkScoped(socket
|
|
84
|
+
.runRaw((data) => {
|
|
85
|
+
const raw = typeof data === "string"
|
|
86
|
+
? data
|
|
87
|
+
: new TextDecoder("utf-8").decode(data);
|
|
88
|
+
return handleInbound(raw);
|
|
89
|
+
})
|
|
90
|
+
.pipe(Effect.catchAllCause((cause) => Effect.gen(function* () {
|
|
91
|
+
const failure = cause.toString();
|
|
92
|
+
yield* Ref.set(closeRef, {
|
|
93
|
+
closed: true,
|
|
94
|
+
code: 1006,
|
|
95
|
+
reason: failure,
|
|
96
|
+
});
|
|
97
|
+
const closedErr = new TransportClosedError({
|
|
98
|
+
direction: "inbound",
|
|
99
|
+
code: 1006,
|
|
100
|
+
reason: failure,
|
|
101
|
+
});
|
|
102
|
+
for (const [id, def] of pending) {
|
|
103
|
+
pending.delete(id);
|
|
104
|
+
yield* Deferred.fail(def, closedErr);
|
|
105
|
+
}
|
|
106
|
+
}))));
|
|
107
|
+
const writeFrame = (raw) => Effect.gen(function* () {
|
|
108
|
+
const state = yield* Ref.get(closeRef);
|
|
109
|
+
if (state.closed) {
|
|
110
|
+
return yield* Effect.fail(new TransportClosedError({
|
|
111
|
+
direction: "outbound",
|
|
112
|
+
code: state.code,
|
|
113
|
+
reason: state.reason,
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
yield* writer(raw).pipe(Effect.mapError((err) => new TransportIoError({ direction: "outbound", cause: err })));
|
|
117
|
+
});
|
|
118
|
+
const sendRpc = (method, params, opts) => Effect.gen(function* () {
|
|
119
|
+
const id = nextRequestId();
|
|
120
|
+
const timeoutMs = opts?.timeoutMs ?? config.defaultTimeoutMs;
|
|
121
|
+
const request = {
|
|
122
|
+
type: "request",
|
|
123
|
+
jsonrpc: "2.0",
|
|
124
|
+
id,
|
|
125
|
+
method,
|
|
126
|
+
params,
|
|
127
|
+
};
|
|
128
|
+
const raw = encodeFrame(request);
|
|
129
|
+
const deferred = yield* Deferred.make();
|
|
130
|
+
pending.set(id, deferred);
|
|
131
|
+
yield* recordFrame(captures, "outbound", raw, request);
|
|
132
|
+
yield* writeFrame(raw);
|
|
133
|
+
const result = yield* Deferred.await(deferred).pipe(Effect.timeoutFail({
|
|
134
|
+
duration: Duration.millis(timeoutMs),
|
|
135
|
+
onTimeout: () => new RpcTimeoutError({ method, requestId: id, timeoutMs }),
|
|
136
|
+
}), Effect.ensuring(Effect.sync(() => {
|
|
137
|
+
pending.delete(id);
|
|
138
|
+
})));
|
|
139
|
+
if (result.type !== "response") {
|
|
140
|
+
return yield* Effect.fail(new FrameSchemaError({
|
|
141
|
+
direction: "inbound",
|
|
142
|
+
expected: "response",
|
|
143
|
+
raw: encodeFrame(result),
|
|
144
|
+
reason: "expected response frame for rpc reply",
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
return result.result;
|
|
148
|
+
});
|
|
149
|
+
const takeEvent = (eventName) => Ref.modify(eventQueue, (events) => {
|
|
150
|
+
const idx = events.findIndex((event) => event.event === eventName);
|
|
151
|
+
if (idx === -1)
|
|
152
|
+
return [null, events];
|
|
153
|
+
const event = events[idx];
|
|
154
|
+
return [event, [...events.slice(0, idx), ...events.slice(idx + 1)]];
|
|
155
|
+
});
|
|
156
|
+
const waitForEvent = (eventName, timeoutMs = 5000) => Effect.gen(function* () {
|
|
157
|
+
while (true) {
|
|
158
|
+
const event = yield* takeEvent(eventName);
|
|
159
|
+
if (event !== null)
|
|
160
|
+
return event;
|
|
161
|
+
const state = yield* Ref.get(closeRef);
|
|
162
|
+
if (state.closed) {
|
|
163
|
+
return yield* Effect.fail(new Error(`Connection closed while waiting for event: ${eventName}`));
|
|
164
|
+
}
|
|
165
|
+
yield* Effect.sleep(Duration.millis(10));
|
|
166
|
+
}
|
|
167
|
+
}).pipe(Effect.timeoutFail({
|
|
168
|
+
duration: Duration.millis(timeoutMs),
|
|
169
|
+
onTimeout: () => new Error(`Timeout waiting for event: ${eventName}`),
|
|
170
|
+
}));
|
|
171
|
+
/**
|
|
172
|
+
* Send malformed bytes and await the server's reaction. Registers the
|
|
173
|
+
* request id in `pending` so a typed `RpcResponseError` surfaces
|
|
174
|
+
* through the same path as valid RPCs; if the server drops the frame
|
|
175
|
+
* without responding, resolves `null` after the quiescence window.
|
|
176
|
+
*
|
|
177
|
+
* The distinction is observable: `null` means "drop" (the property
|
|
178
|
+
* should assert no state poisoning followed); a returned
|
|
179
|
+
* `RpcResponseError` means the server parsed enough to reply with a
|
|
180
|
+
* typed error. Either is acceptable per Tier A4's contract.
|
|
181
|
+
*/
|
|
182
|
+
const sendMalformed = (opts) => Effect.gen(function* () {
|
|
183
|
+
const id = nextRequestId();
|
|
184
|
+
const baseFrame = {
|
|
185
|
+
type: "request",
|
|
186
|
+
jsonrpc: "2.0",
|
|
187
|
+
id,
|
|
188
|
+
method: opts.baseMethod,
|
|
189
|
+
params: {},
|
|
190
|
+
};
|
|
191
|
+
const raw = malformFrame(baseFrame, opts.kind, opts.seed);
|
|
192
|
+
const deferred = yield* Deferred.make();
|
|
193
|
+
pending.set(id, deferred);
|
|
194
|
+
yield* recordMalformed(captures, raw, opts.kind);
|
|
195
|
+
yield* writeFrame(raw);
|
|
196
|
+
const waitMs = config.malformedQuiescenceMs ?? 500;
|
|
197
|
+
// Race the pending Deferred against a quiescence timeout. Clean up
|
|
198
|
+
// the pending entry on both legs so no slot leaks when the server
|
|
199
|
+
// drops silently.
|
|
200
|
+
const outcome = yield* Effect.raceFirst(Deferred.await(deferred).pipe(Effect.matchEffect({
|
|
201
|
+
onSuccess: () => Effect.succeed(null),
|
|
202
|
+
onFailure: (err) => err._tag === "TestingRpcResponseError"
|
|
203
|
+
? Effect.succeed(err)
|
|
204
|
+
: Effect.fail(err),
|
|
205
|
+
})), Effect.succeed(null).pipe(Effect.delay(Duration.millis(waitMs)))).pipe(Effect.ensuring(Effect.sync(() => {
|
|
206
|
+
pending.delete(id);
|
|
207
|
+
})));
|
|
208
|
+
return outcome;
|
|
209
|
+
});
|
|
210
|
+
// Event stream — repeatedly drain `eventQueue`, ending when the WS closes.
|
|
211
|
+
const events = Stream.unwrap(Effect.sync(() => {
|
|
212
|
+
const pullOne = Effect.gen(function* () {
|
|
213
|
+
while (true) {
|
|
214
|
+
const state = yield* Ref.get(closeRef);
|
|
215
|
+
if (state.closed) {
|
|
216
|
+
return yield* Effect.fail(new TransportClosedError({
|
|
217
|
+
direction: "inbound",
|
|
218
|
+
code: state.code,
|
|
219
|
+
reason: state.reason,
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
const q = yield* Ref.getAndSet(eventQueue, []);
|
|
223
|
+
if (q.length > 0)
|
|
224
|
+
return q;
|
|
225
|
+
yield* Effect.sleep(Duration.millis(10));
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
return Stream.repeatEffectChunk(pullOne.pipe(Effect.map((arr) => Chunk.fromIterable(arr))));
|
|
229
|
+
}));
|
|
230
|
+
const client = {
|
|
231
|
+
sendRpc,
|
|
232
|
+
sendMalformed,
|
|
233
|
+
events,
|
|
234
|
+
captures,
|
|
235
|
+
snapshot: captures.snapshot,
|
|
236
|
+
waitForEvent,
|
|
237
|
+
drainEvents: Ref.getAndSet(eventQueue, []),
|
|
238
|
+
};
|
|
239
|
+
// Auto-connect handshake (auth/connect). Matches packages/client's
|
|
240
|
+
// real shape — `agentKey` + `minProtocol` + `maxProtocol`. Tolerant
|
|
241
|
+
// of typed rejections so properties that explicitly drive
|
|
242
|
+
// unauthenticated traffic (e.g., authority-negative) can skip
|
|
243
|
+
// autoConnect without the acquire path faulting.
|
|
244
|
+
if (config.autoConnect !== false) {
|
|
245
|
+
const handshakeParams = {
|
|
246
|
+
agentKey: config.agentKey,
|
|
247
|
+
minProtocol: PROTOCOL_VERSION,
|
|
248
|
+
maxProtocol: PROTOCOL_VERSION,
|
|
249
|
+
};
|
|
250
|
+
const handshake = sendRpc("auth/connect", handshakeParams).pipe(Effect.catchTag("TestingRpcTimeoutError", () => Effect.void), Effect.catchTag("TestingFrameSchemaError", () => Effect.void), Effect.catchTag("TestingRpcResponseError", () => Effect.void));
|
|
251
|
+
yield* handshake;
|
|
252
|
+
}
|
|
253
|
+
return client;
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
export function makeCloseableTestClient(config) {
|
|
257
|
+
return Effect.gen(function* () {
|
|
258
|
+
const scope = yield* Scope.make();
|
|
259
|
+
const client = yield* Scope.extend(makeTestClient(config), scope);
|
|
260
|
+
return {
|
|
261
|
+
...client,
|
|
262
|
+
close: Scope.close(scope, Exit.void),
|
|
263
|
+
};
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
//# sourceMappingURL=test-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-client.js","sourceRoot":"","sources":["../../src/testing/test-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TestServer — accepts real client WebSocket connections and lets property
|
|
3
|
+
* code script arbitrary server-side traffic (valid events, malformed
|
|
4
|
+
* frames, delayed / out-of-order sequences).
|
|
5
|
+
*
|
|
6
|
+
* Per D1 (WS-only) and Invariant I1, TestServer binds a real
|
|
7
|
+
* `@effect/platform-node/NodeSocketServer.makeWebSocket` so the wire bytes
|
|
8
|
+
* match `packages/server`'s real production path. TestServer is *not* an
|
|
9
|
+
* in-process counterpart of TestClient; it exists to exercise real client
|
|
10
|
+
* code (`packages/client`, `openclaw-channel`, `nanoclaw-channel`, arena).
|
|
11
|
+
*
|
|
12
|
+
* Satisfies AC3. Consumed by Tier A (A2), Tier B (server-emitted event
|
|
13
|
+
* replay), and Tier E E2 (schema-exhaustive fuzz).
|
|
14
|
+
*/
|
|
15
|
+
import { Context, Effect, type Scope } from "effect";
|
|
16
|
+
import type { EventFrame, ResponseFrame } from "../schema/frames.js";
|
|
17
|
+
import { type CapturedFrame, type CaptureBuffer } from "./captures.js";
|
|
18
|
+
import { type MalformedFrameKind } from "./codec.js";
|
|
19
|
+
import { FrameSchemaError, TransportClosedError, TransportIoError } from "./errors.js";
|
|
20
|
+
export interface TestServerConfig {
|
|
21
|
+
/** If 0, bind to an ephemeral port. */
|
|
22
|
+
readonly port: number;
|
|
23
|
+
/** Host string bound by the HTTP server; default `"127.0.0.1"`. */
|
|
24
|
+
readonly host: string;
|
|
25
|
+
readonly captureCapacity: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A single live client connection accepted by TestServer. Identity is by
|
|
29
|
+
* `connectionId` (monotonic), not by any agent-level claim — TestServer is
|
|
30
|
+
* below the identity layer.
|
|
31
|
+
*/
|
|
32
|
+
export interface TestServerConnection {
|
|
33
|
+
readonly connectionId: string;
|
|
34
|
+
readonly remoteAddr: string;
|
|
35
|
+
readonly inbound: CaptureBuffer;
|
|
36
|
+
readonly emitEvent: (event: EventFrame) => Effect.Effect<void, TransportIoError | FrameSchemaError>;
|
|
37
|
+
readonly emitResponse: (response: ResponseFrame) => Effect.Effect<void, TransportIoError | FrameSchemaError>;
|
|
38
|
+
readonly emitMalformed: (opts: {
|
|
39
|
+
readonly baseEvent: EventFrame;
|
|
40
|
+
readonly kind: MalformedFrameKind;
|
|
41
|
+
readonly seed: number;
|
|
42
|
+
}) => Effect.Effect<void, TransportIoError>;
|
|
43
|
+
readonly close: (opts: {
|
|
44
|
+
readonly code: number;
|
|
45
|
+
readonly reason: string;
|
|
46
|
+
}) => Effect.Effect<void, TransportClosedError>;
|
|
47
|
+
}
|
|
48
|
+
export interface TestServer {
|
|
49
|
+
readonly wsUrl: string;
|
|
50
|
+
readonly accept: Effect.Effect<TestServerConnection, TransportIoError>;
|
|
51
|
+
readonly connections: Effect.Effect<ReadonlyArray<TestServerConnection>>;
|
|
52
|
+
readonly allInbound: CaptureBuffer;
|
|
53
|
+
readonly snapshot: Effect.Effect<ReadonlyArray<CapturedFrame>>;
|
|
54
|
+
}
|
|
55
|
+
export declare const TestServer: Context.Tag<TestServer, TestServer>;
|
|
56
|
+
/**
|
|
57
|
+
* Bind an `@effect/platform` WebSocket server. The surrounding `Scope` owns
|
|
58
|
+
* the listener; releasing it closes every open connection, drains captures,
|
|
59
|
+
* and awaits port release.
|
|
60
|
+
*/
|
|
61
|
+
export declare function makeTestServer(config: TestServerConfig): Effect.Effect<TestServer, TransportIoError, Scope.Scope>;
|
|
62
|
+
//# sourceMappingURL=test-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-server.d.ts","sourceRoot":"","sources":["../../src/testing/test-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAO,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAC;AAG1D,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAKL,KAAK,aAAa,EAClB,KAAK,aAAa,EACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAKL,KAAK,kBAAkB,EACxB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,CAClB,KAAK,EAAE,UAAU,KACd,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,GAAG,gBAAgB,CAAC,CAAC;IAC9D,QAAQ,CAAC,YAAY,EAAE,CACrB,QAAQ,EAAE,aAAa,KACpB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,GAAG,gBAAgB,CAAC,CAAC;IAC9D,QAAQ,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE;QAC7B,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;QAC/B,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;QAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;KACvB,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IAC5C,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE;QACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACzB,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAC;CACjD;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,oBAAoB,EAAE,gBAAgB,CAAC,CAAC;IACvE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC,CAAC;IACzE,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC;CAChE;AAED,eAAO,MAAM,UAAU,qCAEtB,CAAC;AA2EF;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,gBAAgB,GACvB,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,gBAAgB,EAAE,KAAK,CAAC,KAAK,CAAC,CAiG1D"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TestServer — accepts real client WebSocket connections and lets property
|
|
3
|
+
* code script arbitrary server-side traffic (valid events, malformed
|
|
4
|
+
* frames, delayed / out-of-order sequences).
|
|
5
|
+
*
|
|
6
|
+
* Per D1 (WS-only) and Invariant I1, TestServer binds a real
|
|
7
|
+
* `@effect/platform-node/NodeSocketServer.makeWebSocket` so the wire bytes
|
|
8
|
+
* match `packages/server`'s real production path. TestServer is *not* an
|
|
9
|
+
* in-process counterpart of TestClient; it exists to exercise real client
|
|
10
|
+
* code (`packages/client`, `openclaw-channel`, `nanoclaw-channel`, arena).
|
|
11
|
+
*
|
|
12
|
+
* Satisfies AC3. Consumed by Tier A (A2), Tier B (server-emitted event
|
|
13
|
+
* replay), and Tier E E2 (schema-exhaustive fuzz).
|
|
14
|
+
*/
|
|
15
|
+
import { Context, Effect, Ref } from "effect";
|
|
16
|
+
import * as NodeSocketServer from "@effect/platform-node/NodeSocketServer";
|
|
17
|
+
import * as Socket from "@effect/platform/Socket";
|
|
18
|
+
import { makeCaptureBuffer, mergeCaptures, recordFrame, recordMalformed, } from "./captures.js";
|
|
19
|
+
import { decodeFrame, encodeFrame, malformFrame, } from "./codec.js";
|
|
20
|
+
import { FrameSchemaError, TransportClosedError, TransportIoError, } from "./errors.js";
|
|
21
|
+
export const TestServer = Context.GenericTag("@moltzap/protocol/testing/TestServer");
|
|
22
|
+
let connectionCounter = 0;
|
|
23
|
+
/**
|
|
24
|
+
* Build a `TestServerConnection` for a freshly-accepted socket. `writer`
|
|
25
|
+
* is acquired by the caller; the per-connection receive loop is driven
|
|
26
|
+
* separately via `sock.runRaw`.
|
|
27
|
+
*/
|
|
28
|
+
function makeConnection(writer, captureCapacity, remoteAddr) {
|
|
29
|
+
return Effect.gen(function* () {
|
|
30
|
+
connectionCounter += 1;
|
|
31
|
+
const connectionId = `conn-${connectionCounter}`;
|
|
32
|
+
const inbound = yield* makeCaptureBuffer({ capacity: captureCapacity });
|
|
33
|
+
const emit = (frame) => Effect.gen(function* () {
|
|
34
|
+
const raw = encodeFrame(frame);
|
|
35
|
+
// Validate on the way out as well — Invariant I3.
|
|
36
|
+
const check = yield* Effect.either(decodeFrame(raw, "outbound"));
|
|
37
|
+
if (check._tag === "Left") {
|
|
38
|
+
return yield* Effect.fail(check.left);
|
|
39
|
+
}
|
|
40
|
+
yield* writer(raw).pipe(Effect.mapError((err) => new TransportIoError({ direction: "outbound", cause: err })));
|
|
41
|
+
yield* recordFrame(inbound, "outbound", raw, frame);
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
connectionId,
|
|
45
|
+
remoteAddr,
|
|
46
|
+
inbound,
|
|
47
|
+
emitEvent: (event) => emit(event),
|
|
48
|
+
emitResponse: (response) => emit(response),
|
|
49
|
+
emitMalformed: (opts) => Effect.gen(function* () {
|
|
50
|
+
const base = opts.baseEvent;
|
|
51
|
+
const raw = malformFrame(base, opts.kind, opts.seed);
|
|
52
|
+
yield* writer(raw).pipe(Effect.mapError((err) => new TransportIoError({ direction: "outbound", cause: err })));
|
|
53
|
+
yield* recordMalformed(inbound, raw, opts.kind);
|
|
54
|
+
}),
|
|
55
|
+
close: (opts) => writer(new Socket.CloseEvent(opts.code, opts.reason)).pipe(Effect.mapError((err) => new TransportClosedError({
|
|
56
|
+
direction: "outbound",
|
|
57
|
+
code: opts.code,
|
|
58
|
+
reason: `${opts.reason}: ${String(err)}`,
|
|
59
|
+
}))),
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Bind an `@effect/platform` WebSocket server. The surrounding `Scope` owns
|
|
65
|
+
* the listener; releasing it closes every open connection, drains captures,
|
|
66
|
+
* and awaits port release.
|
|
67
|
+
*/
|
|
68
|
+
export function makeTestServer(config) {
|
|
69
|
+
return Effect.gen(function* () {
|
|
70
|
+
const serverState = yield* Ref.make([]);
|
|
71
|
+
const acceptQueue = yield* Ref.make([]);
|
|
72
|
+
const server = yield* NodeSocketServer.makeWebSocket({
|
|
73
|
+
port: config.port,
|
|
74
|
+
host: config.host,
|
|
75
|
+
}).pipe(Effect.mapError((err) => new TransportIoError({ direction: "inbound", cause: err })));
|
|
76
|
+
// Fork the accept loop into the ambient scope; scope closure tears down
|
|
77
|
+
// the listener and every per-connection fiber.
|
|
78
|
+
yield* Effect.forkScoped(server
|
|
79
|
+
.run((sock) => Effect.gen(function* () {
|
|
80
|
+
const writer = yield* sock.writer;
|
|
81
|
+
const conn = yield* makeConnection(writer, config.captureCapacity, "");
|
|
82
|
+
yield* Ref.update(serverState, (s) => [...s, conn]);
|
|
83
|
+
yield* Ref.update(acceptQueue, (q) => [...q, conn]);
|
|
84
|
+
yield* sock.runRaw((data) => Effect.gen(function* () {
|
|
85
|
+
const raw = typeof data === "string"
|
|
86
|
+
? data
|
|
87
|
+
: new TextDecoder("utf-8").decode(data);
|
|
88
|
+
const decoded = yield* Effect.either(decodeFrame(raw, "inbound"));
|
|
89
|
+
if (decoded._tag === "Left") {
|
|
90
|
+
yield* recordMalformed(conn.inbound, raw, "bit-flip");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
yield* recordFrame(conn.inbound, "inbound", raw, decoded.right);
|
|
94
|
+
}));
|
|
95
|
+
}))
|
|
96
|
+
.pipe(Effect.ignore));
|
|
97
|
+
const addr = server.address;
|
|
98
|
+
if (addr._tag !== "TcpAddress") {
|
|
99
|
+
return yield* Effect.fail(new TransportIoError({
|
|
100
|
+
direction: "inbound",
|
|
101
|
+
cause: new Error(`expected TcpAddress, got ${addr._tag}`),
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
const wsUrl = `ws://${addr.hostname}:${addr.port}`;
|
|
105
|
+
const accept = Effect.gen(function* () {
|
|
106
|
+
while (true) {
|
|
107
|
+
const q = yield* Ref.get(acceptQueue);
|
|
108
|
+
if (q.length > 0) {
|
|
109
|
+
const [next, ...rest] = q;
|
|
110
|
+
yield* Ref.set(acceptQueue, rest);
|
|
111
|
+
if (next !== undefined)
|
|
112
|
+
return next;
|
|
113
|
+
}
|
|
114
|
+
yield* Effect.sleep("10 millis");
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
const allInbound = yield* mergeCaptures([]);
|
|
118
|
+
const snapshot = Effect.gen(function* () {
|
|
119
|
+
const conns = yield* Ref.get(serverState);
|
|
120
|
+
const snaps = yield* Effect.forEach(conns, (c) => c.inbound.snapshot, {
|
|
121
|
+
concurrency: "unbounded",
|
|
122
|
+
});
|
|
123
|
+
return snaps.flat();
|
|
124
|
+
});
|
|
125
|
+
return {
|
|
126
|
+
wsUrl,
|
|
127
|
+
accept,
|
|
128
|
+
connections: Ref.get(serverState),
|
|
129
|
+
allInbound,
|
|
130
|
+
snapshot,
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=test-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-server.js","sourceRoot":"","sources":["../../src/testing/test-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toxiproxy control-plane client.
|
|
3
|
+
*
|
|
4
|
+
* Per design doc §5 "Dependency choices", we talk to Toxiproxy's HTTP
|
|
5
|
+
* control API directly (no `toxiproxy-node-client` dep). The API surface
|
|
6
|
+
* we need is small — create proxy, add toxic, remove toxic, delete proxy —
|
|
7
|
+
* and a thin Effect wrapper keeps us from taking a 4-year-stale MIT
|
|
8
|
+
* package.
|
|
9
|
+
*
|
|
10
|
+
* Satisfies Goal 5 + AC8. Consumed by the Tier D runner.
|
|
11
|
+
*/
|
|
12
|
+
import { Effect, Scope } from "effect";
|
|
13
|
+
import { ToxicControlError } from "../errors.js";
|
|
14
|
+
import type { ToxicProfile } from "./profile.js";
|
|
15
|
+
export interface ToxiproxyConfig {
|
|
16
|
+
/** Control-plane URL, e.g. `http://localhost:8474`. */
|
|
17
|
+
readonly apiUrl: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A live toxic attachment. Scoped: acquiring adds the toxic to the proxy,
|
|
21
|
+
* releasing the scope removes it. Tier D properties acquire a
|
|
22
|
+
* `ToxicHandle` inside `Effect.scoped` so a crashed property still cleans
|
|
23
|
+
* up.
|
|
24
|
+
*/
|
|
25
|
+
export interface ToxicHandle {
|
|
26
|
+
readonly name: string;
|
|
27
|
+
readonly profile: ToxicProfile;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A live proxy that sits between TestClient and the real server (or
|
|
31
|
+
* between real client and TestServer). Acquiring the scope allocates an
|
|
32
|
+
* ephemeral port and registers the proxy; releasing deletes it.
|
|
33
|
+
*/
|
|
34
|
+
export interface Proxy {
|
|
35
|
+
/** Upstream (real server) address the proxy forwards to. */
|
|
36
|
+
readonly upstream: string;
|
|
37
|
+
/** Client-facing URL (`ws://127.0.0.1:<ephemeralPort>`). */
|
|
38
|
+
readonly listenUrl: string;
|
|
39
|
+
/** Attach a toxic inside a Scope; removed on release. */
|
|
40
|
+
readonly withToxic: (profile: ToxicProfile) => Effect.Effect<ToxicHandle, ToxicControlError, Scope.Scope>;
|
|
41
|
+
}
|
|
42
|
+
export interface ToxiproxyClient {
|
|
43
|
+
/** Create a scoped proxy; teardown on release. */
|
|
44
|
+
readonly proxy: (opts: {
|
|
45
|
+
readonly name: string;
|
|
46
|
+
readonly upstream: string;
|
|
47
|
+
}) => Effect.Effect<Proxy, ToxicControlError, Scope.Scope>;
|
|
48
|
+
/** Probe: control plane reachable. */
|
|
49
|
+
readonly ping: Effect.Effect<void, ToxicControlError>;
|
|
50
|
+
}
|
|
51
|
+
export declare function makeToxiproxyClient(config: ToxiproxyConfig): Effect.Effect<ToxiproxyClient, ToxicControlError>;
|
|
52
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/testing/toxics/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,MAAM,WAAW,eAAe;IAC9B,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;CAChC;AAED;;;;GAIG;AACH,MAAM,WAAW,KAAK;IACpB,4DAA4D;IAC5D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,yDAAyD;IACzD,QAAQ,CAAC,SAAS,EAAE,CAClB,OAAO,EAAE,YAAY,KAClB,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,iBAAiB,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;CACjE;AAED,MAAM,WAAW,eAAe;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE;QACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;KAC3B,KAAK,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,iBAAiB,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3D,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;CACvD;AAuFD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,eAAe,GACtB,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,iBAAiB,CAAC,CA8EnD"}
|