Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 50 additions & 2 deletions packages/plugins/graphql/src/sdk/plugin.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { describe, it, expect } from "@effect/vitest";
import { Effect } from "effect";
import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
import { Effect, Layer } from "effect";
import {
HttpClient,
HttpClientRequest,
HttpClientResponse,
HttpServerRequest,
HttpServerResponse,
} from "effect/unstable/http";

import {
AuthTemplateSlug,
Expand Down Expand Up @@ -318,6 +324,48 @@ describe("graphqlPlugin real protocol server", () => {
}),
);

it.effect("uses the executor HttpClient layer for connection-time introspection", () =>
Effect.gen(function* () {
const seen: string[] = [];
const httpClientLayer = Layer.succeed(HttpClient.HttpClient)(
HttpClient.make((request: HttpClientRequest.HttpClientRequest) => {
seen.push(request.url);
return Effect.succeed(
HttpClientResponse.fromWeb(
request,
new Response(JSON.stringify({ data: introspectionResult }), {
status: 200,
headers: { "content-type": "application/json" },
}),
),
);
}),
);
const config = makeTestConfig({
plugins: [memoryCredentialsPlugin(), graphqlPlugin()] as const,
});
const executor = yield* createExecutor({ ...config, httpClientLayer });

yield* executor.graphql.addIntegration({
endpoint: "https://internal.example/graphql",
slug: "guarded_graph",
name: "Guarded Graph",
});
yield* createOrgConnection(executor, {
integration: "guarded_graph",
name: "default",
template: "none",
value: "unused",
});

const tools = yield* executor.tools.list();
expect(seen).toEqual(["https://internal.example/graphql"]);
expect(tools.map((tool) => String(tool.name))).toEqual(
expect.arrayContaining(["query.hello", "mutation.setGreeting"]),
);
}),
);

it.effect("invokes a live query through an apiKey header template", () =>
Effect.gen(function* () {
const server = yield* serveGreetingServer;
Expand Down
12 changes: 4 additions & 8 deletions packages/plugins/graphql/src/sdk/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Effect, Match, Option, Schema } from "effect";
import type { Layer } from "effect";
import { FetchHttpClient, HttpClient } from "effect/unstable/http";
import { HttpClient } from "effect/unstable/http";

import {
authToolFailure,
Expand Down Expand Up @@ -893,11 +893,13 @@ export const graphqlPlugin = definePlugin((options?: GraphqlPluginOptions) => {
template,
storage,
getValues,
httpClientLayer,
}: {
readonly config: IntegrationConfig;
readonly template: AuthTemplateSlug | null;
readonly storage: GraphqlStore;
readonly getValues: () => Effect.Effect<Record<string, string | null>, unknown>;
readonly httpClientLayer: Layer.Layer<HttpClient.HttpClient>;
}) =>
Effect.gen(function* () {
const decoded = yield* decodeGraphqlIntegrationConfig(config).pipe(Effect.option);
Expand All @@ -917,7 +919,7 @@ export const graphqlPlugin = definePlugin((options?: GraphqlPluginOptions) => {
introspectionJson,
values,
template,
options?.httpClientLayer ?? httpClientLayerFallback,
options?.httpClientLayer ?? httpClientLayer,
).pipe(Effect.option);
if (Option.isNone(introspection)) return { tools: [] };
const extracted = yield* extract(introspection.value).pipe(Effect.option);
Expand Down Expand Up @@ -1117,9 +1119,3 @@ export const graphqlPlugin = definePlugin((options?: GraphqlPluginOptions) => {
// HTTP transport (routes/handlers/extensionService) is layered on by the
// api-aware factory in `@executor-js/plugin-graphql/api`.
});

// The fallback HTTP layer for `resolveTools`. The hook input carries no `ctx`,
// so when no explicit layer is passed to the plugin we use the same default the
// executor wires into `ctx.httpClientLayer` (`FetchHttpClient.layer`). Hosts/
// tests that need a custom transport pass `options.httpClientLayer`.
const httpClientLayerFallback: Layer.Layer<HttpClient.HttpClient> = FetchHttpClient.layer;
Loading