I have a simplified schema.graphql file with one type and one custom directive (at the field and object level). In Typescript, how can I programmatically get the type and type directive, and iterate over the fields and field directives?
type Test #myCustomDirective() {
id: String! #myCustomDirective()
}
This post says "This is not currently supported by GraphQL":
Is there any way to read GraphQL Directives on a Type with Query Introspection?
And the GitHub issue says this feature is being considered:
https://github.com/graphql/graphql-spec/issues/300
Then how is AWS AppSync doing this? See below.
// The following keeps custom directives:
parse(schema); // Return type: graphql.DocumentNode
// The following removes custom directives:
buildSchema(schema); // Return type: GraphQLSchema
AWS AppSync > Export Schema > Schema.json does include custom directives but is an AWS AppSync specific solution and is an expensive API operation.
aws appsync get-introspection-schema --api-id abc123 --format JSON --include-directives output.json
I tried GraphQL Code Generator Introspection plugin. But it removes custom directives from types and fields.
https://www.graphql-code-generator.com/plugins/introspection
I tried graphql/utilities but it also removes custom directives from types and fields.
graphql.introspectionFromSchema(graphqlSchema)
// or
graphql.graphqlSync({ schema: graphqlSchema, source: graphql.getIntrospectionQuery() }).data as unknown as graphql.IntrospectionQuery;
https://graphql.org/graphql-js/utilities/
This "GraphQL Tools" approach creates an iteratable schema but may not be the most efficient approach:
"The graphql-tools package allows you to create a GraphQL.js GraphQLSchema instance from GraphQL schema language using the function makeExecutableSchema"
https://www.graphql-tools.com/docs/generate-schema
import { promises as fs } from 'fs';
import { makeExecutableSchema } from '#graphql-tools/schema';
import gql from 'graphql-tag';
const schemaFile = await fs.readFile(
'./schema.graphql'
);
const executableSchema = makeExecutableSchema({
typeDefs: gql(schemaFile.toString())
});``
const output = executableSchema.getType('Test');
// output.astNode.directives sample output:
{
kind: 'Directive',
name: { kind: 'Name', value: 'myCustomDirective1' },
arguments: [ [Object] ]
},
{
kind: 'Directive',
name: { kind: 'Name', value: 'myCustomDirective' },
arguments: [ [Object] ]
}
// output.astNode['fields'] sample output:
[{
kind: 'FieldDefinition',
description: undefined,
name: { kind: 'Name', value: 'id' },
arguments: [],
type: { kind: 'NamedType', name: [Object] },
directives: [ [Object] ]
}]
Related
I'm using AWS CDK version 2.64.0 to configure AWS AppSync to setup my GraphQL API.
I created my graphql.schema with the following configuration:
type message {
id_message: String!
id_collection: String!
id_user: String!
created_at: String!
text: String!
}
type Query {
getMessages(idCollectionIdUser: String!): [ message! ]
}
And then I used the configuration above to create my GraphQL API using these commands:
const api = new appsync.GraphqlApi(this, 'GraphQLDatabase', {
name: 'my-graphql-database',
schema: appsync.SchemaFile.fromAsset(path.join(__dirname, 'schema.graphql')),
xrayEnabled: true,
});
I also created one dynamodb table (with one global secondary index) using the following commands:
const messageTable = new dynamodb.Table(this, 'MessageTable', {
partitionKey: {
name: 'id_message',
type: dynamodb.AttributeType.STRING,
},
tableName: 'message.dynamodb-table',
removalPolicy: RemovalPolicy.DESTROY,
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
});
messageTable.addGlobalSecondaryIndex({
indexName: 'id_collection-id_user-created_at-index',
partitionKey: {
name: 'id_collection-id_user',
type: dynamodb.AttributeType.STRING,
},
sortKey: {
name: 'created_at',
type: dynamodb.AttributeType.NUMBER,
},
projectionType: ProjectionType.ALL,
});
Then I added the dynamodb table as datasource for the graphql database:
const messageDataSource = api.addDynamoDbDataSource('messageDataSource', messageTable);
I'm now trying to create a resolver to get all the messages created by the user in a collection with the following code:
messageDataSource.createResolver('QueryGetMessagesResolver', {
typeName: 'Query',
fieldName: 'getMessages',
requestMappingTemplate: appsync.MappingTemplate.dynamoDbQuery(
appsync.KeyCondition.eq('id_collection-id_user', 'idCollectionIdUser'),
'id_collection-id_user-created_at-index',
false,
),
responseMappingTemplate: appsync.MappingTemplate.dynamoDbResultList(),
});
When I test my graph api, I always get this error:
ExpressionAttributeNames contains invalid key: Syntax error; key: "#id_collection-id_user"
What am I doing wrong?
I am trying to set a static cacheControl on some fields, as done here
From my understanding, I need to use a directive, so I used the following nest documentation to declare directives
So, I built a cacheControl directive, this is what my GraphQLModule.forRootAsync has in the buildSchemaOptions:
buildSchemaOptions: {
directives: [
new GraphQLDirective({
name: 'cacheControl',
locations: [
DirectiveLocation.FIELD_DEFINITION,
DirectiveLocation.OBJECT,
DirectiveLocation.INTERFACE,
DirectiveLocation.UNION
],
args: {
maxAge: { type: GraphQLInt },
scope: {
type: new GraphQLEnumType({
name: 'CacheControlScope',
values: {
PUBLIC: {
astNode: {
kind: 'EnumValueDefinition',
description: undefined,
name: {
kind: 'Name',
value: 'PUBLIC'
},
directives: []
}
},
PRIVATE: {
astNode: {
kind: 'EnumValueDefinition',
description: undefined,
name: {
kind: 'Name',
value: 'PRIVATE'
},
directives: []
}
}
}
})
},
inheritMaxAge: { type: GraphQLBoolean }
}
})
]
}
And it did create the directive in the schema:
directive #cacheControl(maxAge: Int, scope: CacheControlScope, inheritMaxAge: Boolean) on FIELD_DEFINITION | OBJECT | INTERFACE | UNION
enum CacheControlScope {
PUBLIC
PRIVATE
}
Now, I try to use it on my field declaration in my #ObjectType like so:
import { Directive, Field, Int, ObjectType } from '#nestjs/graphql'
#ObjectType('User')
export class User {
#Directive('#cacheControl(maxAge:60)')
#Field(() => Int)
id!: number
#Directive('#cacheControl(maxAge:60)')
#Field()
text!: string
}
But when doing some query to get all users, it does not seem to cache anything - and does not send a cache-control header and does the whole query each time.
I tried to do a transformer, but not sure how to implement caching for the resolvers.
What am I missing?
I am implementing a framework using Nestjs on Apollo Server using GraphQL and I would like to use some custom GraphQL scalars. I found this site, https://www.graphql-scalars.dev/docs/quick-start, which is helpful for importing custom scalars without actually implementing them as written on https://docs.nestjs.com/graphql/scalars#create-a-custom-scalar. To be specific, I would like to use BigInt, Time, and URL.
From the docs on the quick start page, I am uncertain where the code belongs at. Should I code this at app.module.ts?
// or import specific typeDefs only with ES6 Import
import { ScalarNameTypeDefinition } from 'graphql-scalars';
// or import specific typeDefs only with CommonJS
const { ScalarNameTypeDefinition } = require('graphql-scalars');
// or import all typeDefs once with ES6 Import
import { typeDefs as scalarTypeDefs } from 'graphql-scalars';
// or import all typeDefs once with CommonJS
const { typeDefs: scalarTypeDefs } = require('graphql-scalars');
const typeDefs = [
...scalarTypeDefs,
// other typeDefs
];
// or
const typeDefs = [
ScalarNameTypeDefinition,
// other typeDefs
];
my current GraphQLModule:
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
typePaths: ['./**/**/**/*.graphql'],
definitions: {
path: join(process.cwd(), 'src/graphql.ts'),
outputAs: 'class',
},
}),
How about the resolver map? Where should the code belong at? assets.resolver.ts? I also don't understand where this code belongs to?
In short, how to use graphql-scalars package in the Nestjs framework on Apollo Server? Is there any open-source GitHub repository to look into?
Have a look here NestJs Import a custom scalar
This is how my app.module.ts looks:
import { BigIntResolver, DateResolver, DateTimeResolver } from 'graphql-scalars';
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
typePaths: ['./**/*.graphql'],
definitions: {
path: join(process.cwd(), 'src/graphql/graphql-types.ts'),
customScalarTypeMapping: {
BigInt: 'bigint',
DateTime: 'Date',
},
},
resolvers: {
BigInt: BigIntResolver,
Date: DateResolver,
DateTime: DateTimeResolver,
},
playground: true,
debug: true,
}),
In my .graphql file I can then use those types:
scalar BigInt
scalar Date
scalar DateTime
input WorkperiodContent {
editedAt: DateTime
startDate: Date
endDate: Date
}
After I did this I could successfully run queries on the GraphQL Playground using those new scalars.
You don't even need to create your own custom scalar. You can just import the three that you need and you are good to go.
I have created the following schema which up until yesterday would generate Input using the #connection reference ID. I created a new API which is now creating an Input with the actual nested type as part of the Input for some of the types. E.g:
type Metadata
#model(subscriptions: null)
#versioned
#auth(
rules: [
]
) {
id: ID!
merchant: Merchant! #connection
}
type Merchant
#model(subscriptions: null)
#versioned
#auth(
rules: []
) {
id: ID!
metadata: Metadata! #connection
}
would create the Input types:
export type CreateMerchantInput = {
id?: string | null,
merchantMetadataId: string,
};
export type CreateMetadataInput = {
id?: string | null,
merchant: MerchantInput, <-- Contains this!!
metadataMerchantId: string,
};
The Appsync schema shows this too, so it's something on my machine that has compiled the main GraphQL schema and pushed it. Really scary that different machines can compile it differently!!
Does anyone have any ideas what I have changed or am missing or have specified in my schema or Amplify CLI config please?
I'm trying to get directives from a type.
Part of schema:
directive #translates(
type: String
) on OBJECT | FIELD_DEFINITION
type DocumentTranslated #translates(type: Document) {
...
}
Reading schema:
info.schema.getTypeMap()['DocumentTranslated'].astNode
Result:
{ kind: 'ObjectTypeDefinition',
description: undefined,
name:
{ kind: 'Name',
value: 'DocumentTranslated',
loc: { start: 8893, end: 8911 } },
interfaces: [],
directives: [],
fields:
I don't get, why directives are not present in AST? How can I read type directives?