TypeError when trying to create GraphQL Directive - node.js

I'm using Apollo-Server/TypeScript and utilizing graphql-tool's makeExecutableSchema() to setup schema/directives.
I'm currently getting this error when trying to add a barebones simple GraphQL Directive:
TypeError: Class constructor SchemaDirectiveVisitor cannot be invoked without 'new' at new AuthDirective
(/home/node/app/src/api/directives/AuthDirective.ts:58:42)
Here is the setup for the schema:
import AuthDirective, { authTypeDefs } from "./directives/AuthDirective";
import { makeExecutableSchema } from "graphql-tools";
const schema = makeExecutableSchema({
resolvers: [...],
typeDefs: [...], // authTypeDefs is included here
schemaDirectives: {
auth: AuthDirective,
},
});
export default schema;
The AuthDirective file:
import { SchemaDirectiveVisitor } from "graphql-tools";
import { defaultFieldResolver } from "graphql";
export default class AuthDirective extends SchemaDirectiveVisitor {
public visitFieldDefinition(field) {
console.log("VISIT FIELD: ", field);
const { resolve = defaultFieldResolver } = field;
field.resolve = async function (...args) {
return resolve.apply(this, args);
};
}
}
export const authTypeDefs = `
enum AppRole {
USER
ADMIN
}
directive #auth(
requires: AppRole! = USER
) on FIELD_DEFINITION
`;
I've been following the documentation here. Everything seems to be in order but I could be overlooking something.
What's absurd though is that the error is saying something about line 58 in the AuthDirective file.
The file is only 23/24 lines long.

Fixed the issue. I've changed the way of implementing directives using graphql-tools directive resolvers (instead of the class-based SchemaDirectiveVisitor way). Documentation here

Related

Jest testEnvironment: expect is not defined

I'm using Jest 26.x and I defined a custom testEnvironment as so:
import type { EnvironmentContext } from '#jest/environment';
import { Config } from '#jest/types';
import JSDOMEnvironment from 'jest-environment-jsdom';
declare global {
function expectAndMore<ExpectedObject>(
result: ExpectedObject,
expectedObject: ExpectedObject,
): void;
}
class ApprovalTestEnvironment extends JSDOMEnvironment {
constructor(config: Config.ProjectConfig, context: EnvironmentContext) {
super(config, context);
}
async setup() {
await super.setup();
this.global.expectAndMore = ({ result, expectedObject }) => {
expect(result).toEqual(expectedObject);
};
}
async teardown() {
await super.teardown();
}
getVmContext() {
return super.getVmContext();
}
}
export default ApprovalTestEnvironment;
I then configured a unit test file with these comments at the header of my file so it would use the custom environment defined above:
/**
* #jest-environment ./src/approvals/approvalTestEnvironment.ts
*/
But whenever I'm trying to access the expectAndMoreFunction defined in my custom environment with globalThis.expectObjectForPostRequest(result, expected);, I get the following error:
ReferenceError: expect is not defined
Does anyone what causes this and how to fix it?
Note: I'm also using ts-jest 27.1.4.

How to create a custom health check for Prisma with #nestjs/terminus?

Since #nestjs/terminus doesn't provide a health check for Prisma, I'm trying to create it based on their Mongoose health check.
When I try:
import * as Prisma from 'prisma';
...
...
private getContextConnection(): any | null {
const {
getConnectionToken,
// eslint-disable-next-line #typescript-eslint/no-var-requires
} = require('prisma') as typeof Prisma;
try {
return this.moduleRef.get(getConnectionToken('DatabaseConnection') as string, {
strict: false,
});
} catch (err) {
return null;
}
}
...
...
const connection = options.connection || this.getContextConnection();
if (!connection) {
throw new ConnectionNotFoundError(
this.getStatus(key, isHealthy, {
message: 'Connection provider not found in application context',
}),
);
}
I always seem to get: "message": "Connection provider not found in application context".
There is a problem with the connection or I don't really understand how the health check actually works
This question helped me build a Prisma health check for NestJS.
Here's what I made:
import { Injectable } from "#nestjs/common";
import { HealthCheckError, HealthIndicator, HealthIndicatorResult } from "#nestjs/terminus";
import { PrismaService } from "./prisma.service";
#Injectable()
export class PrismaHealthIndicator extends HealthIndicator {
constructor(private readonly prismaService: PrismaService) {
super();
}
async isHealthy(key: string): Promise<HealthIndicatorResult> {
try {
await this.prismaService.$queryRaw`SELECT 1`;
return this.getStatus(key, true);
} catch (e) {
throw new HealthCheckError("Prisma check failed", e);
}
}
}
This injects a PrismaService exactly as it is shown in the NestJS docs. https://docs.nestjs.com/recipes/prisma#use-prisma-client-in-your-nestjs-services
You could alternatively replace prismaService with new PrismaClient().
A naive copy of the mongoose implementation isn't going to work because there are differences between the NestJSMongoose type/module and Prisma. In particular, getConnectionToken does not exist inside the Prisma package.
I can't comment on what the best way would be to extend terminus to support prisma. You might have to dig a bit into the terminus interface for that. However, a simple way to get a health check/ping in Prisma is to use the following query:
prisma.$queryRaw`SELECT 1`

Error: metatype is not a constructor when using instance of own HTTPS Server class

Good evening, I am playing around with nest and want to achieve an own HTTPS-Server that can be instantiated everywhere in other projects. Right at the beginning I get the following error-message:
TypeError: metatype is not a constructor
… when I init the following HTTPS-Server:
import { Injectable } from '#nestjs/common';
import { NestFactory } from '#nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from '#nestjs/platform-fastify';
import * as fs from 'fs';
#Injectable()
export class HttpsServer {
constructor() {}
async bootstrap() {
const httpsOptions = {
key: fs.readFileSync('./certs/server.key'),
cert: fs.readFileSync('./certs/server.cert'),
};
const app = await NestFactory.create<NestFastifyApplication>(
new FastifyAdapter({ https: httpsOptions }),
);
await app.listen(443);
}
}
like this:
import { Logger } from '#nestjs/common';
import { HttpsServer } from 'server-lib';
const logger = new Logger();
const app = new HttpsServer();
app.bootstrap().then(() => {
logger.log('Bootstrap complete!');
}).catch((error) => {
logger.log('Bootstrap failed: ', error);
process.exit(1);
});
Thx for help...
All Nest applications needs to have a "RootModule" of some sort. With nest new applications this is a AppModule. This class holds the metadata for the server for how to tie everything together and run. What you could probably do is modify your HttpModule's constructor to do something like
export class HttpsServer {
constructor(private readonly rootModule: Type<any>) {} // Type comes from #nestjs/common
async bootstrap() {
const httpsOptions = {
key: fs.readFileSync('./certs/server.key'),
cert: fs.readFileSync('./certs/server.cert'),
};
const app = await NestFactory.create<NestFastifyApplication>(
this.rootModule,
new FastifyAdapter({ https: httpsOptions }),
);
await app.listen(443);
}
}
So now when you call new HttpServer() you pass in the root module and have everything else already set up. The NestFactory will instantiate the metadata from there properly, and you'll use the FastifyAdapter.
For more information, I suggest you follow the docs overview to get a feeling of how these classes fit together and why they're needed.
You probably have used an incorrect guard.
Check #UseGuards() and use the correct guard for that function.
If you're importing some files from index in the same folder such as all entities as
import * as Entities from './entities
make sure that you have that ./entities in the folder path and just plan '.'
It does not throws error but while building this is not recognized. use the full file path and it will most probably solve the issue.

Trouble migrating from graphql-import to just graphql-tools with ApolloServer, directives cease to work

My plight began as a simple desire to expand my graphql schema from a single .graphql file to multiple files so i can better organize the schema and so it wouldn;t grow to one huge file out of control.
My original layout was very straight forward and i had a working schema in a schema.graphql file. I would be able to parse it into a string using importSchema('server/schema.graphql') from the graphql-import library, which is now deprecated https://github.com/ardatan/graphql-import
They mention that it has been merged into graphql-tools in the newest version and provide a migration tutorial here https://www.graphql-tools.com/docs/migration-from-import The tutorial seems very straight forward since their first example pretty much illustrate exactly what my code looks like (except i dont use es6 import but old fashoined require):
import { importSchema } from 'graphql-import';
import { makeExecutableSchema } from 'graphql-tools';
const typeDefs = importSchema(join(__dirname, 'schema.graphql'));
const resolvers = {
Query: {...}
};
const schema = makeExecutableSchema({ typeDefs, resolvers });
And then they say to modify it, simply make these changes
import { loadSchemaSync } from '#graphql-tools/load';
import { GraphQLFileLoader } from '#graphql-tools/graphql-file-loader';
import { addResolversToSchema } from '#graphql-tools/schema';
const schema = loadSchemaSync(join(__dirname, 'schema.graphql'), { loaders: [new GraphQLFileLoader()] });
const resolvers = { Query: {...} };
const schemaWithResolvers = addResolversToSchema({
schema,
resolvers,
});
I made those changes but the vital difference is that they no longer use makeExecutableSchema() in their example, which is pretty important for me since i need to include the directives. What do i do now with the schema? How do i declare the directives? their documentation for directives still uses makeExecutableSchema but i cant use it anymore since the new loadSchemaSync function returns an object instead of a string literal which i would need to pass to typeDefs in makeExecutableSchema
I am using apollo-server, so it seemed a possible workaround was to just declare the directives in the apollo-server constructor and just pass in this new schemaWithResolvers as a schema as such
const server = new ApolloServer({
schema, //this includes now the returned value of using addResolversToSchema()
schemaDirectives : {
auth:AuthDirective,
authRole: AuthRoleDirective
}
context : ({req}) => //dostuff,
});
This allows my server to run, and i can perform queries and mutations, however, my directives are no longer working, and i no longer have authentication on protected queries.
I would like a way to import my .graphql file and parse it into a string so i can use it inside typeDefs as i used to with importSchema() or a way to declase my directies without using makeExecutableSchema() so that they continue working again!
I have gone up and down the documentation and seen other libraries and so far i keep coming up short, any tips or guidance is greatly appreciated
makeExecutableSchema is still part of graphql-tools and you can continue to use it as shown here in the docs. The issue with the example shown in the docs is that it's not equivalent to what you were doing before. You should use loadTypedefsSync instead:
import { loadTypedefsSync } from '#graphql-tools/load';
import { GraphQLFileLoader } from '#graphql-tools/graphql-file-loader';
import { addResolversToSchema } from '#graphql-tools/schema';
const sources = loadTypedefsSync(join(__dirname, 'schema.graphql'), { loaders: [new GraphQLFileLoader()] });
const documentNodes = sources.map(source => source.document);
const resolvers = { Query: {...} };
const schema = makeExecutableSchema({ typeDefs, resolvers });
Alternatively, if you go the loadSchema route, you should be able to apply the directives to your schema after loading it:
import { SchemaDirectiveVisitor } from "#graphql-tools/utils";
import { loadSchemaSync } from '#graphql-tools/load';
import { GraphQLFileLoader } from '#graphql-tools/graphql-file-loader';
import { addResolversToSchema } from '#graphql-tools/schema';
const schema = loadSchemaSync(join(__dirname, 'schema.graphql'), { loaders: [new GraphQLFileLoader()] });
const resolvers = { Query: {...} };
const schemaWithResolvers = addResolversToSchema({
schema,
resolvers,
});
SchemaDirectiveVisitor.visitSchemaDirectives(schemaWithResolvers, schemaDirectives);
I tried this way but I couldn't solve the problem. A unique solution that managed to take the following approach:
const { ApolloServer, makeExecutableSchema, gql} = require('apollo-server-express')
const { loadTypedefsSync } = require('#graphql-tools/load')
const { GraphQLFileLoader } = require('#graphql-tools/graphql-file-loader')
const path = require('path')
const sources = loadTypedefsSync(
path.resolve(__dirname, '../schema/root.graphql'),
{ loaders: [new GraphQLFileLoader()] }
)
const typeDefs = sources.map(source => source.document)
const schema = makeExecutableSchema({
typeDefs: gql`${typeDefs[0]}`,
resolvers,
})
I had the same issue I loaded the Schema via .graphql and I want to add the graphql-constraint-directive. My Solution was to load the schema with loadSchemaSync and then to use the wrapSchema to use the transform functions, you need also to add the directives into one of your .graphql files:
import { addResolversToSchema, wrapSchema } from 'graphql-tools';
import { GraphQLSchema } from 'graphql';
import resolvers from './resolver';
schema = loadSchemaSync('./**/*.graphql', {
loaders: [new GraphQLFileLoader()],
});
const schemaWithResolver = addResolversToSchema({
schema,
resolvers
});
const { constraintDirective } = require('graphql-constraint-directive')
const schemaConstrain = wrapSchema({
schema: schemaWithResolver,
transforms: [constraintDirective()]
})
Documentation to Schema Wrapping

Need to find the error with connecting subscription with schema stitching

I am using apollo-server-express for graphql back-end. I am going to process only mutations there, but I want to redirect query and subscription on hasura by means of schema stitching with introspection. Queries through apollo-server to hasura are working fine and returning the expected data.
But subscriptions are not working and I am getting this error: " Expected Iterable, but did not find one for field subscription_root.users".
And besides, server hasura is receiving events:
But apollo-server resents the answer from hasura. It is not the first day I suffer with this and I can not understand what the problem is.
In the editor hasura subscriptions work.
Link to full code
If you need any additional info, I will gladly provide it to you.
import {
introspectSchema,
makeExecutableSchema,
makeRemoteExecutableSchema,
mergeSchemas,
transformSchema,
FilterRootFields
} from 'graphql-tools';
import { HttpLink } from 'apollo-link-http';
import nodeFetch from 'node-fetch';
import { resolvers } from './resolvers';
import { hasRoleResolver } from './directives';
import { typeDefs } from './types';
import { WebSocketLink } from 'apollo-link-ws';
import { split } from 'apollo-link';
import { getMainDefinition } from 'apollo-utilities';
import { SubscriptionClient } from 'subscriptions-transport-ws';
import * as ws from 'ws';
import { OperationTypeNode } from 'graphql';
interface IDefinitionsParams {
operation?: OperationTypeNode,
kind: 'OperationDefinition' | 'FragmentDefinition'
}
const wsurl = 'ws://graphql-engine:8080/v1alpha1/graphql';
const getWsClient = function (wsurl: string) {
const client = new SubscriptionClient(wsurl, {
reconnect: true,
lazy: true
}, ws);
return client;
};
const wsLink = new WebSocketLink(getWsClient(wsurl));
const createRemoteSchema = async () => {
const httpLink = new HttpLink({
uri: 'http://graphql-engine:8080/v1alpha1/graphql',
fetch: (nodeFetch as any)
});
const link = split(
({ query }) => {
const { kind, operation }: IDefinitionsParams = getMainDefinition(query);
console.log('kind = ', kind, 'operation = ', operation);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httpLink,
);
const remoteSchema = await introspectSchema(link);
const remoteExecutableSchema = makeRemoteExecutableSchema({
link,
schema: remoteSchema
});
const renamedSchema = transformSchema(
remoteExecutableSchema,
[
new FilterRootFields((operation, fieldName) => {
return (operation === 'Mutation') ? false : true; // && fieldName === 'password'
})
]
);
return renamedSchema;
};
export const createNewSchema = async () => {
const hasuraExecutableSchema = await createRemoteSchema();
const apolloSchema = makeExecutableSchema({
typeDefs,
resolvers,
directiveResolvers: {
hasRole: hasRoleResolver
}
});
return mergeSchemas({
schemas: [
hasuraExecutableSchema,
apolloSchema
]
});
};
Fixed by installing graphql-tools 4th version. It tutns out the editor did not even notice that I do not have this dependency and simply took the version of node_modules, which was installed by some other package. Problem was with version 3.x. Pull request is where the bug was fixed.
I had the same problem, different cause and solution.
My subscription was working well, until I introduced the 'resolve' key in
my subscription resolver:
Here is the 'Subscription' part of My resolver:
Subscription: {
mySubName: {
resolve: (payload) => {
console.log('In mySubName resolver, payload:',payload)
return payload;
},
subscribe:() => pubSub.asyncIterator(['requestsIncomplete']),
// )
},
The console.log proved the resolve() function was being called with a well structured payload (shaped the same as my Schema definiton - specifically the an object with a key named after the graphQL Subscriber, pointing to an array (array is an iterable):
In mySubName resolver, payload: { mySubName:
[ { id: 41,
...,
},
{...},
{...}
...
...
]
Even though I was returning that same unadulterated object, it caused the error expected Iterable, but did not find one for field "Subscription.mySubName"
When I commented out that resolve function all together, the subscription worked, which is further evidence that my payload was well structured, with the right key pointing to an iterable.
I must be mis-using the resolve field. From https://www.apollographql.com/docs/graphql-subscriptions/subscriptions-to-schema/
When using subscribe field, it's also possible to manipulate the event
payload before running it through the GraphQL execution engine.
Add resolve method near your subscribe and change the payload as you wish
so I am not sure how to properly use that function, specifically don't know what shape object to return from it, but using it as above breaks the subscription in the same manner you describe in your question.
I was already using graphql-tools 4.0.0, I upgraded to 4.0.8 but it made no difference.

Resources