Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-appsync/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ junit.xml
!**/*.snapshot/**/asset.*/*.d.ts

!**/*.snapshot/**/asset.*/**
!test/appsync-js-resolver.js
!test/appsync-js-pipeline.js
36 changes: 36 additions & 0 deletions packages/@aws-cdk/aws-appsync/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -550,4 +550,40 @@ const pipelineResolver = new appsync.Resolver(this, 'pipeline', {
});
```

### JS Functions and Resolvers

JS Functions and resolvers are also supported. You can use a `.js` file within your CDK project, or specify your function code inline.

```ts
declare const api: appsync.GraphqlApi;

const myJsFunction = new appsync.AppsyncFunction(this, 'function', {
name: 'my_js_function',
api,
dataSource: api.addNoneDataSource('none'),
code: appsync.Code.fromAsset('directory/function_code.js'),
runtime: appsync.FunctionRuntime.JS_1_0_0,
});

new appsync.Resolver(this, 'PipelineResolver', {
api,
typeName: 'typeName',
fieldName: 'fieldName',
code: appsync.Code.fromInline(`
// The before step
export function request(...args) {
console.log(args);
return {}
}

// The after step
export function response(ctx) {
return ctx.prev.result
}
`),
runtime: appsync.FunctionRuntime.JS_1_0_0,
pipelineConfig: [myJsFunction],
});
```

Learn more about Pipeline Resolvers and AppSync Functions [here](https://docs.aws.amazon.com/appsync/latest/devguide/pipeline-resolvers.html).
24 changes: 24 additions & 0 deletions packages/@aws-cdk/aws-appsync/lib/appsync-function.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Resource, IResource, Lazy, Fn } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnFunctionConfiguration } from './appsync.generated';
import { Code } from './code';
import { BaseDataSource } from './data-source';
import { IGraphqlApi } from './graphqlapi-base';
import { MappingTemplate } from './mapping-template';
import { FunctionRuntime } from './runtime';

/**
* the base properties for AppSync Functions
Expand Down Expand Up @@ -31,6 +33,18 @@ export interface BaseAppsyncFunctionProps {
* @default - no response mapping template
*/
readonly responseMappingTemplate?: MappingTemplate;
/**
* The functions runtime
*
* @default - no function runtime, VTL maping templates used
*/
readonly runtime?: FunctionRuntime;
/**
* The function code
*
* @default - no code is used
*/
readonly code?: Code;
}

/**
Expand Down Expand Up @@ -128,11 +142,21 @@ export class AppsyncFunction extends Resource implements IAppsyncFunction {

public constructor(scope: Construct, id: string, props: AppsyncFunctionProps) {
super(scope, id);

// If runtime is specified, code must also be
if (props.runtime && !props.code) {
throw new Error('Code is required when specifying a runtime');
}

const code = props.code?.bind(this);
this.function = new CfnFunctionConfiguration(this, 'Resource', {
name: props.name,
description: props.description,
apiId: props.api.apiId,
dataSourceName: props.dataSource.name,
runtime: props.runtime?.toProperties(),
codeS3Location: code?.s3Location,
code: code?.inlineCode,
functionVersion: '2018-05-29',
requestMappingTemplate: props.requestMappingTemplate?.renderTemplate(),
responseMappingTemplate: props.responseMappingTemplate?.renderTemplate(),
Expand Down
98 changes: 98 additions & 0 deletions packages/@aws-cdk/aws-appsync/lib/code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import * as s3_assets from '@aws-cdk/aws-s3-assets';
import * as cdk from '@aws-cdk/core';
import { Construct } from 'constructs';

/**
* Result of binding `Code` into a `Function`.
*/
export interface CodeConfig {
/**
* The location of the code in S3 (mutually exclusive with `inlineCode`.
* @default - code is not an s3 location
*/
readonly s3Location?: string;

/**
* Inline code (mutually exclusive with `s3Location`).
* @default - code is not inline code
*/
readonly inlineCode?: string;
}

/**
* Represents source code for an AppSync Function or Resolver.
*/
export abstract class Code {
/**
* Loads the function code from a local disk path.
*
* @param path The path to the source code file.
*/
public static fromAsset(path: string, options?: s3_assets.AssetOptions): AssetCode {
return new AssetCode(path, options);
}

/**
* Inline code for AppSync function
* @returns `InlineCode` with inline code.
* @param code The actual handler code (limited to 4KiB)
*/
public static fromInline(code: string): InlineCode {
return new InlineCode(code);
}

/**
* Bind source code to an AppSync Function or resolver.
*/
public abstract bind(scope: Construct): CodeConfig;
}

/**
* Represents a local file with source code used for an AppSync Function or Resolver.
*/
export class AssetCode extends Code {
private asset?: s3_assets.Asset;

/**
* @param path The path to the asset file.
*/
constructor(public readonly path: string, private readonly options: s3_assets.AssetOptions = { }) {
super();
}

public bind(scope: Construct): CodeConfig {
// If the same AssetCode is used multiple times, retain only the first instantiation.
if (!this.asset) {
this.asset = new s3_assets.Asset(scope, 'Code', {
path: this.path,
...this.options,
});
} else if (cdk.Stack.of(this.asset) !== cdk.Stack.of(scope)) {
throw new Error(`Asset is already associated with another stack '${cdk.Stack.of(this.asset).stackName}'. ` +
'Create a new Code instance for every stack.');
}

return {
s3Location: this.asset.s3ObjectUrl,
};
}
}

/**
* AppSync function code from an inline string.
*/
export class InlineCode extends Code {
constructor(private code: string) {
super();

if (code.length === 0) {
throw new Error('AppSync Inline code cannot be empty');
}
}

public bind(_scope: Construct): CodeConfig {
return {
inlineCode: this.code,
};
}
}
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-appsync/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ export * from './resolver';
export * from './schema';
export * from './graphqlapi';
export * from './graphqlapi-base';
export * from './code';
export * from './runtime';
24 changes: 24 additions & 0 deletions packages/@aws-cdk/aws-appsync/lib/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { IAppsyncFunction } from './appsync-function';
import { CfnResolver } from './appsync.generated';
import { CachingConfig } from './caching-config';
import { BASE_CACHING_KEYS } from './caching-key';
import { Code } from './code';
import { BaseDataSource } from './data-source';
import { IGraphqlApi } from './graphqlapi-base';
import { MappingTemplate } from './mapping-template';
import { FunctionRuntime } from './runtime';

/**
* Basic properties for an AppSync resolver
Expand Down Expand Up @@ -51,6 +53,19 @@ export interface BaseResolverProps {
* @default - No max batch size
*/
readonly maxBatchSize?: number;

/**
* The functions runtime
*
* @default - no function runtime, VTL maping templates used
*/
readonly runtime?: FunctionRuntime;
/**
* The function code
*
* @default - no code is used
*/
readonly code?: Code;
}

/**
Expand Down Expand Up @@ -93,6 +108,11 @@ export class Resolver extends Construct {
{ functions: props.pipelineConfig.map((func) => func.functionId) }
: undefined;

// If runtime is specified, code must also be
if (props.runtime && !props.code) {
throw new Error('Code is required when specifying a runtime');
}

if (pipelineConfig && props.dataSource) {
throw new Error(`Pipeline Resolver cannot have data source. Received: ${props.dataSource.name}`);
}
Expand All @@ -108,12 +128,16 @@ export class Resolver extends Construct {
}
}

const code = props.code?.bind(this);
this.resolver = new CfnResolver(this, 'Resource', {
apiId: props.api.apiId,
typeName: props.typeName,
fieldName: props.fieldName,
dataSourceName: props.dataSource ? props.dataSource.name : undefined,
kind: pipelineConfig ? 'PIPELINE' : 'UNIT',
runtime: props.runtime?.toProperties(),
codeS3Location: code?.s3Location,
code: code?.inlineCode,
pipelineConfig: pipelineConfig,
requestMappingTemplate: props.requestMappingTemplate ? props.requestMappingTemplate.renderTemplate() : undefined,
responseMappingTemplate: props.responseMappingTemplate ? props.responseMappingTemplate.renderTemplate() : undefined,
Expand Down
47 changes: 47 additions & 0 deletions packages/@aws-cdk/aws-appsync/lib/runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { CfnFunctionConfiguration } from './appsync.generated';

/**
* Appsync supported runtimes. Only JavaScript as of now
*/
export enum FunctionRuntimeFamily {
/**
* AppSync JavaScript runtime
*/
JS = 'APPSYNC_JS',
}

/**
* Utility class for specifying specific appsync runtime versions
*/
export class FunctionRuntime {
/**
* APPSYNC_JS v1.0.0 runtime
*/
public static readonly JS_1_0_0 = new FunctionRuntime(FunctionRuntimeFamily.JS, '1.0.0');

/**
* The name of the runtime
*/
public readonly name: string;

/**
* The runtime version
*/
public readonly version: string;

public constructor(family: FunctionRuntimeFamily, version: string) {
this.name = family;
this.version = version;
}

/**
* Convert to Cfn runtime configuration property format
*/
public toProperties(): CfnFunctionConfiguration.AppSyncRuntimeProperty {
return {
name: this.name,
runtimeVersion: this.version,
};
}
}

1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-appsync/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
"@aws-cdk/aws-logs": "0.0.0",
"@aws-cdk/aws-opensearchservice": "0.0.0",
"@aws-cdk/aws-rds": "0.0.0",
"@aws-cdk/assets": "0.0.0",
"@aws-cdk/aws-s3-assets": "0.0.0",
"@aws-cdk/aws-secretsmanager": "0.0.0",
"@aws-cdk/core": "0.0.0",
Expand Down
10 changes: 10 additions & 0 deletions packages/@aws-cdk/aws-appsync/test/appsync-js-pipeline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// The before step
export function request(...args) {
console.log(args);
return {}
}

// The after step
export function response(ctx) {
return ctx.prev.result
}
22 changes: 22 additions & 0 deletions packages/@aws-cdk/aws-appsync/test/appsync-js-resolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { util } from '@aws-appsync/utils'

export function request(ctx) {
const id = util.autoId()
const name = ctx.args.name;

ctx.args.input = {
id,
name,
}

return JSON.stringify({
version: '2018-05-29',
operation: 'PutItem',
key: { id: util.dynamodb.toDynamoDB(ctx.args.input.id) },
attributeValues: util.dynamodb.toMapValues(ctx.args.input),
})
}

export function response(ctx) {
return JSON.stringify(ctx.result)
}
10 changes: 10 additions & 0 deletions packages/@aws-cdk/aws-appsync/test/appsync.js-resolver.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type Test {
id: String!
name: String!
}
type Query {
getTests: [Test]!
}
type Mutation {
addTest(name: String!): Test
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "22.0.0",
"files": {
"21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": {
"source": {
"path": "JsResolverIntegTestDefaultTestDeployAssert57AD8D20.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
}
},
"dockerImages": {}
}
Loading