awscdk-appsync-decorators
v0.0.6
Published
Decorators for creating appsync apis using aws-cdk
Readme
AWS CDK AppSync Decorators
Provides decorators for creating appsync apis using aws-cdk and awscdk-appsync-utils.
More information can be found here.
Code First Schema
A SchemaBinder is used to generate a CodeFirstSchema.
const binder = new SchemaBinder();
const api = new GraphqlApi(this, 'Api', {
name: 'Demo',
definition: Definition.fromSchema(binder.schema),
});
binder.addQuery(Query);
binder.addMutation(Mutation);
binder.addSubscription(Subscription);
binder.bindSchema();Types
The foundations of a schema are it's type definitions.
Scalar
The built-in scalar types can be accessed via the Scalar enum (e.g. Scalar.ID, Scalar.AWS_DATE).
Interface
The @InterfaceType() decorator is attached to a class that defines the interface shape.
@InterfaceType()
class Node {
id = Scalar.STRING;
}GraphQL
interface Node {
id: String
}Object
The @ObjectType() decorator is attached to a class that defines the object shape.
If an object implements any interfaces, then the interface class types should be provided to the decorator.
@InterfaceType()
class Node {
id = Scalar.STRING;
}
@ObjectType(Node)
class FilmNode {
filmName = Scalar.STRING;
}GraphQL
interface Node {
id: String
}
type FilmNode implements Node {
filmName: String
}Enum
The @EnumType() decorator is attached to a class that defines the enum values.
The property names are used as the enum values and the property values are ignored (so can be anything to keep TypeScript happy).
@EnumType()
class Episode {
NEWHOPE = Scalar.STRING;
EMPIRE = Scalar.STRING;
JEDI = Scalar.STRING;
}GraphQL
enum Episode {
NEWHOPE
EMPIRE
JEDI
}Input
The @InputType() decorator is attached to a class that defines the input shape.
@InputType()
class Review {
stars = Scalar.INT;
commentary = Scalar.STRING;
}GraphQL
input Review {
stars: Int
commentary: String
}Union
The @UnionType() decorator is attached to a class that defines the union.
All object class types should be provided to the decorator.
@ObjectType()
class Human {
name = Scalar.STRING;
}
@ObjectType()
class Droid {
name = Scalar.STRING;
}
@ObjectType()
class Starship {
name = Scalar.STRING;
}
@UnionType(Human, Droid, Starship)
class Search {}GraphQL
union Search = Human | Droid | StarshipArgs
The @Args() decorator is attached to a property and provides a class type that defines the argument names and types.
class FilmArgs {
after = Scalar.STRING;
first = Scalar.INT;
before = Scalar.STRING;
last = Scalar.INT;
}
class Query {
@Args(FilmArgs)
allFilms = FilmConnection;
}GraphQL
type Query {
allFilms(after: String, first: Int, before: String, last: Int): FilmConnection
}Modifiers
Modifiers can be attached to properties to define when a value is a list or non-null.
@List()@Required()@RequiredList()
If the @RequiredList() decorator is attached, then the @List() decorator is not needed.
A property is also considered a list when it's return type is an array.
class FilmArgs {
@Required()
id = Scalar.STRING;
}
@ObjectType()
class Film {
@Required()
id = Scalar.STRING;
@Required()
filmName = Scalar.STRING;
@List()
reviews = Review; // can also be Review[] as an alternative
}
class Query {
@Args(FilmArgs)
@Required()
film = Film;
}GraphQL
type Film {
id: String!
filmName: String!
reviews: [Review]
}
type Query {
film(id: String!): Film!
}Resolvers
Resolvers are defined by extending either JsResolver or VtlResolver and then using the @Resolver() decorator to attach them to properties.
Under the hood, when defining the resolver class, a data source definition is required. As a result, when calling bindSchema(), the relevant data source definitions should be included to match the name provided by the resolver.
class FilmResolver extends JsResolver {
dataSource = 'Demo';
code = Code.fromInline('...code...');
}
class AllFilmsResolver extends VtlResolver {
dataSource = 'Demo';
requestMappingTemplate = MappingTemplate.fromString('...template...');
responseMappingTemplate = MappingTemplate.fromString('...template...');
}
class Query {
@Resolver(FilmResolver)
film = Film;
@Resolver(AllFilmsResolver)
allFilms = Film[];
}
// Bind the schema
const dataSource = api.addNoneDataSource('NoneDataSource');
binder.bindSchema({
dataSources: {
'Demo': dataSource,
},
});If a resolver pipeline is needed, then an array of function names can be provided to the decorator. Note that a data source should not be provided for a pipeline resolver.
As with the data sources, the named function definitions should be included when calling bindSchema().
class AddFilmResolver extends JsResolver {
code = Code.fromInline('...code...');
}
class Mutation {
@Resolver(AddFilmResolver, 'Index', 'Db')
addFilm = Film;
}
// Bind the schema
const indexFunction = new appsync.AppsyncFunction(this, 'IndexFunction');
const dbFunction = new appsync.AppsyncFunction(this, 'DbFunction');
binder.bindSchema({
functions: {
Index: indexFunction,
Db: dbFunction,
},
});Directives
Directives can be attached to both classes and properties.
@ApiKey()@Cognito(groups)@Custom(statement)@Iam()@Lambda()@Oidc()@Subscribe(mutations)
Caching
When a resolver is attached to a property, then a caching configuration can also be attached using the @Cache() decorator. This accepts a time to live (TTL) value in seconds and an optional array of keys.
class Query {
@Args(FilmArgs)
@Resolver(FilmResolver)
@Cache(30)
film = Film;
}