Skip to content

Commit 07f7057

Browse files
feat(appsync): js resolver support
Adds support for resolvers using the APPSYNC_JS runtime. Adds new props for specifying `Code` and `Runtime` on both `Resolver` and `Function` constructs. Adds test covering basic JS resolver usage.
1 parent 9b2573b commit 07f7057

21 files changed

+2130
-1
lines changed

packages/@aws-cdk/aws-appsync/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@ junit.xml
2323
!**/*.snapshot/**/asset.*/*.d.ts
2424

2525
!**/*.snapshot/**/asset.*/**
26+
!test/appsync-js-resolver.js
27+
!test/appsync-js-pipeline.js

packages/@aws-cdk/aws-appsync/lib/appsync-function.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { Resource, IResource, Lazy, Fn } from '@aws-cdk/core';
22
import { Construct } from 'constructs';
33
import { CfnFunctionConfiguration } from './appsync.generated';
4+
import { Code } from './code';
45
import { BaseDataSource } from './data-source';
56
import { IGraphqlApi } from './graphqlapi-base';
67
import { MappingTemplate } from './mapping-template';
8+
import { FunctionRuntime } from './runtime';
79

810
/**
911
* the base properties for AppSync Functions
@@ -31,6 +33,14 @@ export interface BaseAppsyncFunctionProps {
3133
* @default - no response mapping template
3234
*/
3335
readonly responseMappingTemplate?: MappingTemplate;
36+
/**
37+
* The functions runtime
38+
*/
39+
readonly runtime?: FunctionRuntime;
40+
/**
41+
* The function code
42+
*/
43+
readonly code?: Code;
3444
}
3545

3646
/**
@@ -128,11 +138,21 @@ export class AppsyncFunction extends Resource implements IAppsyncFunction {
128138

129139
public constructor(scope: Construct, id: string, props: AppsyncFunctionProps) {
130140
super(scope, id);
141+
142+
// If runtime is specified, code must also be
143+
if (props.runtime && !props.code) {
144+
throw new Error('Code is required when specifying a runtime');
145+
}
146+
147+
const code = props.code?.bind(this);
131148
this.function = new CfnFunctionConfiguration(this, 'Resource', {
132149
name: props.name,
133150
description: props.description,
134151
apiId: props.api.apiId,
135152
dataSourceName: props.dataSource.name,
153+
runtime: props.runtime?.toProperties(),
154+
codeS3Location: code?.s3Location,
155+
code: code?.inlineCode,
136156
functionVersion: '2018-05-29',
137157
requestMappingTemplate: props.requestMappingTemplate?.renderTemplate(),
138158
responseMappingTemplate: props.responseMappingTemplate?.renderTemplate(),
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import * as cdk from '@aws-cdk/core';
2+
import { Construct } from 'constructs';
3+
import * as s3_assets from '@aws-cdk/aws-s3-assets';
4+
5+
/**
6+
* Result of binding `Code` into a `Function`.
7+
*/
8+
export interface CodeConfig {
9+
/**
10+
* The location of the code in S3 (mutually exclusive with `inlineCode`.
11+
* @default - code is not an s3 location
12+
*/
13+
readonly s3Location?: string;
14+
15+
/**
16+
* Inline code (mutually exclusive with `s3Location`).
17+
* @default - code is not inline code
18+
*/
19+
readonly inlineCode?: string;
20+
}
21+
22+
export abstract class Code {
23+
/**
24+
* Loads the function code from a local disk path.
25+
*
26+
* @param a source code file.
27+
*/
28+
public static fromAsset(path: string, options?: s3_assets.AssetOptions): AssetCode {
29+
return new AssetCode(path, options);
30+
}
31+
32+
/**
33+
* Inline code for AppSync function
34+
* @returns `InlineCode` with inline code.
35+
* @param code The actual handler code (limited to 4KiB)
36+
*/
37+
public static fromInline(code: string): InlineCode {
38+
return new InlineCode(code);
39+
}
40+
41+
public abstract bind(scope: Construct): CodeConfig;
42+
}
43+
44+
export class AssetCode extends Code {
45+
public readonly isInline = false;
46+
private asset?: s3_assets.Asset;
47+
48+
/**
49+
* @param path The path to the asset file.
50+
*/
51+
constructor(public readonly path: string, private readonly options: s3_assets.AssetOptions = { }) {
52+
super();
53+
}
54+
55+
public bind(scope: Construct): CodeConfig {
56+
// If the same AssetCode is used multiple times, retain only the first instantiation.
57+
if (!this.asset) {
58+
this.asset = new s3_assets.Asset(scope, 'Code', {
59+
path: this.path,
60+
...this.options,
61+
});
62+
} else if (cdk.Stack.of(this.asset) !== cdk.Stack.of(scope)) {
63+
throw new Error(`Asset is already associated with another stack '${cdk.Stack.of(this.asset).stackName}'. ` +
64+
'Create a new Code instance for every stack.');
65+
}
66+
67+
return {
68+
s3Location: this.asset.s3ObjectUrl,
69+
};
70+
}
71+
}
72+
73+
/**
74+
* AppSync function code from an inline string.
75+
*/
76+
export class InlineCode extends Code {
77+
public readonly isInline = true;
78+
79+
constructor(private code: string) {
80+
super();
81+
82+
if (code.length === 0) {
83+
throw new Error('AppSync Inline code cannot be empty');
84+
}
85+
}
86+
87+
public bind(_scope: Construct): CodeConfig {
88+
return {
89+
inlineCode: this.code,
90+
};
91+
}
92+
}

packages/@aws-cdk/aws-appsync/lib/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ export * from './resolver';
1010
export * from './schema';
1111
export * from './graphqlapi';
1212
export * from './graphqlapi-base';
13+
export * from './code';
14+
export * from './runtime';

packages/@aws-cdk/aws-appsync/lib/resolver.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { Token } from '@aws-cdk/core';
22
import { Construct } from 'constructs';
3-
import { IAppsyncFunction } from './appsync-function';
3+
import { IAppsyncFunction, FunctionRuntime } from './appsync-function';
44
import { CfnResolver } from './appsync.generated';
55
import { CachingConfig } from './caching-config';
66
import { BASE_CACHING_KEYS } from './caching-key';
77
import { BaseDataSource } from './data-source';
88
import { IGraphqlApi } from './graphqlapi-base';
99
import { MappingTemplate } from './mapping-template';
10+
import { Code } from './code';
1011

1112
/**
1213
* Basic properties for an AppSync resolver
@@ -51,6 +52,15 @@ export interface BaseResolverProps {
5152
* @default - No max batch size
5253
*/
5354
readonly maxBatchSize?: number;
55+
56+
/**
57+
* The functions runtime
58+
*/
59+
readonly runtime?: FunctionRuntime;
60+
/**
61+
* The function code
62+
*/
63+
readonly code?: Code;
5464
}
5565

5666
/**
@@ -93,6 +103,11 @@ export class Resolver extends Construct {
93103
{ functions: props.pipelineConfig.map((func) => func.functionId) }
94104
: undefined;
95105

106+
// If runtime is specified, code must also be
107+
if (props.runtime && !props.code) {
108+
throw new Error('Code is required when specifying a runtime');
109+
}
110+
96111
if (pipelineConfig && props.dataSource) {
97112
throw new Error(`Pipeline Resolver cannot have data source. Received: ${props.dataSource.name}`);
98113
}
@@ -108,12 +123,16 @@ export class Resolver extends Construct {
108123
}
109124
}
110125

126+
const code = props.code?.bind(this);
111127
this.resolver = new CfnResolver(this, 'Resource', {
112128
apiId: props.api.apiId,
113129
typeName: props.typeName,
114130
fieldName: props.fieldName,
115131
dataSourceName: props.dataSource ? props.dataSource.name : undefined,
116132
kind: pipelineConfig ? 'PIPELINE' : 'UNIT',
133+
runtime: props.runtime?.toProperties(),
134+
codeS3Location: code?.s3Location,
135+
code: code?.inlineCode,
117136
pipelineConfig: pipelineConfig,
118137
requestMappingTemplate: props.requestMappingTemplate ? props.requestMappingTemplate.renderTemplate() : undefined,
119138
responseMappingTemplate: props.responseMappingTemplate ? props.responseMappingTemplate.renderTemplate() : undefined,
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Appsync supported runtimes. Only JavaScript as of now
3+
*/
4+
export enum FunctionRuntimeFamily {
5+
JS = 'APPSYNC_JS',
6+
}
7+
8+
/**
9+
* Utility class for specifying specific appsync runtime versions
10+
*/
11+
export class FunctionRuntime {
12+
public static readonly JS_1_0_0 = new FunctionRuntime(FunctionRuntimeFamily.JS, '1.0.0');
13+
14+
/**
15+
* The name of the runtime
16+
*/
17+
public readonly name: string;
18+
19+
/**
20+
* The runtime version
21+
*/
22+
public readonly version: string;
23+
24+
public constructor(family: FunctionRuntimeFamily, version: string) {
25+
this.name = family;
26+
this.version = version;
27+
}
28+
29+
/**
30+
* Convert to Cfn runtime configuration property format
31+
*/
32+
public toProperties() {
33+
return {
34+
name: this.name,
35+
runtimeVersion: this.version,
36+
};
37+
}
38+
}
39+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// The before step
2+
export function request(...args) {
3+
console.log(args);
4+
return {}
5+
}
6+
7+
// The after step
8+
export function response(ctx) {
9+
return ctx.prev.result
10+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { util } from '@aws-appsync/utils'
2+
3+
export function request(ctx) {
4+
const id = util.autoId()
5+
const name = ctx.args.name;
6+
7+
ctx.args.input = {
8+
id,
9+
name,
10+
}
11+
12+
return JSON.stringify({
13+
version: '2018-05-29',
14+
operation: 'PutItem',
15+
key: { id: util.dynamodb.toDynamoDB(ctx.args.input.id) },
16+
attributeValues: util.dynamodb.toMapValues(ctx.args.input),
17+
})
18+
}
19+
20+
export function response(ctx) {
21+
return JSON.stringify(ctx.result)
22+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
type Test {
2+
id: String!
3+
name: String!
4+
}
5+
type Query {
6+
getTests: [Test]!
7+
}
8+
type Mutation {
9+
addTest(name: String!): Test
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"version": "22.0.0",
3+
"files": {
4+
"21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": {
5+
"source": {
6+
"path": "JsResolverIntegTestDefaultTestDeployAssert57AD8D20.template.json",
7+
"packaging": "file"
8+
},
9+
"destinations": {
10+
"current_account-current_region": {
11+
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12+
"objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json",
13+
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
14+
}
15+
}
16+
}
17+
},
18+
"dockerImages": {}
19+
}

0 commit comments

Comments
 (0)