Repository: sid88in/serverless-appsync-plugin
Pull request #534 (2022-11-29 15:34:00)

feat(resolvers): JavaScript resolvers
- Adds support for JS resolvers
- JS resolvers are now default
- PIPELINE resolvers are now defaults
- Removes support for default mapping templates and locations 
- Removes support for named data sources in resolver definition


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index be5f9f9..eebd90c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,11 +17,11 @@ jobs:
       matrix:
         node: [16]
     steps:
-      - uses: actions/setup-node@v1
+      - uses: actions/setup-node@v3
         with:
           node-version: ${{ matrix.node }}
       - name: Checkout code
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
       - name: Install dependencies
         run: npm install
       - name: Lint
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 2bdf26c..119ed5e 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -12,9 +12,9 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
       - name: Setup Node.js
-        uses: actions/setup-node@v2
+        uses: actions/setup-node@v3
         with:
           node-version: 16
       - name: Install dependencies
diff --git a/doc/general-config.md b/doc/general-config.md
index 7ce9892..494a311 100644
--- a/doc/general-config.md
+++ b/doc/general-config.md
@@ -48,12 +48,6 @@ appSync:
 - `caching`: See [Cacing](caching.md)
 - `waf`: See [Web Application Firefall](WAF.md)
 - `logging`: See [Logging](#Logging)
-- `defaultMappingTemplates`:
-  - `request`: Optional. A default request mapping template filename for all resolvers.
-  - `response`: Optional. A default response mapping template filename for all resolvers.
-- `mappingTemplatesLocation`:
-  - `resolvers`: The location where to find resolver mapping templates, relative to the service path. Defaults to `mapping-templates`.
-  - `pipelineFunctions`: The location where to find pipeline functions mapping templates. Defaults to the same value as `mappingTemplatesLocation.resolvers`.
 - `xrayEnabled`: Boolean. Enable or disable X-Ray tracing.
 - `tags`: A key-value pair for tagging this AppSync API
 
diff --git a/doc/pipeline-functions.md b/doc/pipeline-functions.md
index 39f1940..44e6893 100644
--- a/doc/pipeline-functions.md
+++ b/doc/pipeline-functions.md
@@ -4,28 +4,33 @@ When you use `PIPELINE` [resolvers](resolvers.md), you will also need to define
 
 It's a key-value pair object whose key is the name of the function and the value is its configuration.
 
-The definition can also be a string in which case it's the [dataSource](dataSources.md) name to use. The other attributes use the default values.
-
 ## Quick start
 
 ```yaml
 appSync:
   pipelineFunctions:
-    myFunction: myDataSource
-    myOtherFunction:
-      dataSource: myOtherDataSource
+    myFunction:
+      dataSource: myDataSource
+      code: myFunction.js
 ```
 
 ## Configutation
 
 - `dataSource`: The name of the dataSource to use.
 - `description`: An optional description for this pipeline function.
-- `request`: The request mapping template file name to use for this resolver, or `false` for [direct lambda](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html). Defaults to `{functionName}.request.vtl`.
-- `response`: The request mapping template file name to use for this resolver, or `false` for [direct lambda](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html). Defaults to `{functionName}.response.vtl`.
+- `code`: The path to the JS resolver handler file, relative to `serverless.yml`.
+- `request`: The path to the VTL request mapping template file, relative to `serverless.yml`.
+- `response`: The path to the VTL response mapping template file, relative to `serverless.yml`.
 - `maxBatchSize`: The maximum [batch size](https://aws.amazon.com/blogs/mobile/introducing-configurable-batching-size-for-aws-appsync-lambda-resolvers/) to use (only available for AWS Lambda DataAources)
 - `substitutions`: See [VTL template substitutions](substitutions.md)
 - `sync`: [See SyncConfig](syncConfig.md)
 
+## JavaScript vs VTL vs Direct Lambda
+
+When `code` is specified, the JavaScript runtime is used. When `request` and/or `response` are specified, the VTL runtime is used.
+
+To use [direct lambda](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html), don't specify anything (only works with Lambda function data sources).
+
 ## Inline DataSources
 
 Just like with `UNIT` resolvers, you can [define the dataSource inline](resolvers.md#inline-datasources) in pipeline functions.
diff --git a/doc/resolvers.md b/doc/resolvers.md
index a68f490..0651196 100644
--- a/doc/resolvers.md
+++ b/doc/resolvers.md
@@ -4,8 +4,6 @@ All the Resolvers in your AppSync API can be found in serverless.yml under the `
 
 Resolvers are defined using key-value pairs where the key can either be an arbitrary name for the resolver or the `type` and `field` in the schema it is attached to separated with a dot (`.`), and the value is the configuration of the resolver.
 
-The definition can also be a string in which case it's the [dataSource](dataSources.md) name to use. The other attributes use the default values.
-
 ## Quick start
 
 ```yaml
@@ -14,8 +12,6 @@ appSync:
     Query.user:
       dataSource: myDataSource
 
-    Query.users: myDataSource
-
     getPosts:
       type: Query
       field: getPosts
@@ -26,15 +22,34 @@ appSync:
 
 - `type`: The Type in the schema this resolver is attached to. Optional if specified in the configuration key.
 - `field`: The Field in the schema this resolver is attached to. Optional if specified in the configuration key.
-- `kind`: The kind of resolver. Can be `UNIT` or `PIPELINE` ([see below](#PIPELINE-resolvers)). Defaults to `UNIT`
+- `kind`: The kind of resolver. Can be `UNIT` or `PIPELINE` ([see below](#PIPELINE-resolvers)). Defaults to `PIPELINE`
 - `dataSource`: The name of the [dataSource](dataSources.md) this resolver uses.
 - `maxBatchSize`: The maximum [batch size](https://aws.amazon.com/blogs/mobile/introducing-configurable-batching-size-for-aws-appsync-lambda-resolvers/) to use (only available for AWS Lambda DataSources)
-- `request`: The request mapping template file name to use for this resolver, or `false` for [direct lambda](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html). Defaults to `{type}.{field}.request.vtl`.
-- `response`: The request mapping template file name to use for this resolver, or `false` for [direct lambda](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html). Defaults to `{type}.{field}.response.vtl`.
+- `code`: The path of the JavaScript resolver handler file, relative to `serverless.yml`. If not specified, a [minimalistic default](#javascript-vs-vtl) is used.
+- `request`: The path to the VTL request mapping template file, relative to `serverless.yml`.
+- `response`: The path to the VTL response mapping template file, relative to `serverless.yml`.
 - `substitutions`: See [VTL template substitutions](substitutions.md)
 - `caching`: [See below](#Caching)
 - `sync`: [See SyncConfig](syncConfig.md)
 
+## JavaScript vs VTL
+
+When `code` is specified, the JavaScript runtime is used. When `request` and/or `response` are specified, the VTL runtime is used.
+
+If neither are specified, by default, the resolver is a PIPELINE JavaScript resolver, and the following minimalistic resolver handler is used.
+
+```js
+export function request() {
+  return {};
+}
+
+export function response(ctx) {
+  return ctx.prev.result;
+}
+```
+
+To use [direct lambda](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html), set `kind` to `UNIT` and don't specify `request` and `response` (only works with Lambda function data sources).
+
 ## PIPELINE resolvers
 
 When `kind` is `PIPELINE`, you can specify the [pipeline function](pipeline-functions.md) names to use:
@@ -73,6 +88,19 @@ appSync:
             handler: 'functions/getUser.handler'
 ```
 
+## Inline function definitions
+
+If a [Pipeline function](pipeline-functions.md) is only used in a single resolver, you can also define it inline in the resolver configuration.
+
+```yaml
+appSync:
+  resolvers:
+    Query.user:
+      functions:
+        - dataSource: 'users'
+          code: 'getUser.js'
+```
+
 ## Caching
 
 ```yaml
diff --git a/doc/upgrading-from-v1.md b/doc/upgrading-from-v1.md
index 1f6e3a7..8f409ce 100644
--- a/doc/upgrading-from-v1.md
+++ b/doc/upgrading-from-v1.md
@@ -35,6 +35,33 @@ custom:
 
 Place your APIs into defferent stacks. Unfortunately, this WILL require **the replacement of the APIs**. You can probably use [custom domains](custom-domain.md) to workaround that, if that's an option.
 
+### Defaults to PIPELINE and JavaScript resolvers
+
+The new default runtime is JavaScript.
+
+The new default `KIND` for resolvers is `PIPELINE`. For several reasons:
+
+- The JavaScript runtime, is only supportsed with PIPELINE resolvers
+- It makes migrations easier later, if you need to add functions to your resolvers.
+
+> 💡 To simulate a UNIT resolver, use a PIPELINE with only one function.
+
+```yml
+resolvers:
+  Query.getPost:
+    functions:
+      - dataSource: posts
+        code: resolvers/getPost.js
+```
+
+### No more defaults for resolver handler paths.
+
+In `v1`, if you did not specify a path to your mapping templates, a default based on the type, field or function name was used. (e.g. `Query.getPost.request.vtl`).
+
+To avoid unexpected behaviours, you are now required to explicitely specify the path to your resolver handlers. i.e. use `code` for Pipeline JS resolvers or `request`/`response` for VTL.
+
+There is also no more `mappingTemplatesLocation` option. Paths must be relative to the `serverless.yml`. This aligns more with how Serverless Framework handles Lambda function handlers' paths.
+
 ### Graphiql "playground"
 
 The `graphql-playground` command which started a graphiql server pointing to the AppSync API has been removed.
diff --git a/package-lock.json b/package-lock.json
index 188c12b..df85e56 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9807,6 +9807,156 @@
         "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
       }
     },
+    "node_modules/npm/node_modules/@npmcli/run-script/node_modules/@npmcli/fs": {
+      "version": "1.1.1",
+      "dev": true,
+      "inBundle": true,
+      "license": "ISC",
+      "dependencies": {
+        "@gar/promisify": "^1.0.1",
+        "semver": "^7.3.5"
+      }
+    },
+    "node_modules/npm/node_modules/@npmcli/run-script/node_modules/brace-expansion": {
+      "version": "1.1.11",
+      "dev": true,
+      "inBundle": true,
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "node_modules/npm/node_modules/@npmcli/run-script/node_modules/cacache": {
+      "version": "15.3.0",
+      "dev": true,
+      "inBundle": true,
+      "license": "ISC",
+      "dependencies": {
+        "@npmcli/fs": "^1.0.0",
+        "@npmcli/move-file": "^1.0.1",
+        "chownr": "^2.0.0",
+        "fs-minipass": "^2.0.0",
+        "glob": "^7.1.4",
+        "infer-owner": "^1.0.4",
+        "lru-cache": "^6.0.0",
+        "minipass": "^3.1.1",
+        "minipass-collect": "^1.0.2",
+        "minipass-flush": "^1.0.5",
+        "minipass-pipeline": "^1.2.2",
+        "mkdirp": "^1.0.3",
+        "p-map": "^4.0.0",
+        "promise-inflight": "^1.0.1",
+        "rimraf": "^3.0.2",
+        "ssri": "^8.0.1",
+        "tar": "^6.0.2",
+        "unique-filename": "^1.1.1"
+      },
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/npm/node_modules/@npmcli/run-script/node_modules/glob": {
+      "version": "7.2.0",
+      "dev": true,
+      "inBundle": true,
+      "license": "ISC",
+      "dependencies": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.0.4",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      },
+      "engines": {
+        "node": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/npm/node_modules/@npmcli/run-script/node_modules/graceful-fs": {
+      "version": "4.2.9",
+      "dev": true,
+      "inBundle": true,
+      "license": "ISC"
+    },
+    "node_modules/npm/node_modules/@npmcli/run-script/node_modules/make-fetch-happen": {
+      "version": "9.1.0",
+      "dev": true,
+      "inBundle": true,
+      "license": "ISC",
+      "dependencies": {
+        "agentkeepalive": "^4.1.3",
+        "cacache": "^15.2.0",
+        "http-cache-semantics": "^4.1.0",
+        "http-proxy-agent": "^4.0.1",
+        "https-proxy-agent": "^5.0.0",
+        "is-lambda": "^1.0.1",
+        "lru-cache": "^6.0.0",
+        "minipass": "^3.1.3",
+        "minipass-collect": "^1.0.2",
+        "minipass-fetch": "^1.3.2",
+        "minipass-flush": "^1.0.5",
+        "minipass-pipeline": "^1.2.4",
+        "negotiator": "^0.6.2",
+        "promise-retry": "^2.0.1",
+        "socks-proxy-agent": "^6.0.0",
+        "ssri": "^8.0.0"
+      },
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/npm/node_modules/@npmcli/run-script/node_modules/minimatch": {
+      "version": "3.0.4",
+      "dev": true,
+      "inBundle": true,
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/npm/node_modules/@npmcli/run-script/node_modules/node-gyp": {
+      "version": "8.4.1",
+      "dev": true,
+      "inBundle": true,
+      "license": "MIT",
+      "dependencies": {
+        "env-paths": "^2.2.0",
+        "glob": "^7.1.4",
+        "graceful-fs": "^4.2.6",
+        "make-fetch-happen": "^9.1.0",
+        "nopt": "^5.0.0",
+        "npmlog": "^6.0.0",
+        "rimraf": "^3.0.2",
+        "semver": "^7.3.5",
+        "tar": "^6.1.2",
+        "which": "^2.0.2"
+      },
+      "bin": {
+        "node-gyp": "bin/node-gyp.js"
+      },
+      "engines": {
+        "node": ">= 10.12.0"
+      }
+    },
+    "node_modules/npm/node_modules/@npmcli/run-script/node_modules/ssri": {
+      "version": "8.0.1",
+      "dev": true,
+      "inBundle": true,
+      "license": "ISC",
+      "dependencies": {
+        "minipass": "^3.1.1"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
     "node_modules/npm/node_modules/@tootallnate/once": {
       "version": "2.0.0",
       "dev": true,
@@ -11592,6 +11742,29 @@
         "node": ">= 10"
       }
     },
+    "node_modules/npm/node_modules/socks-proxy-agent/node_modules/debug": {
+      "version": "4.3.4",
+      "dev": true,
+      "inBundle": true,
+      "license": "MIT",
+      "dependencies": {
+        "ms": "2.1.2"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/npm/node_modules/socks-proxy-agent/node_modules/ms": {
+      "version": "2.1.2",
+      "dev": true,
+      "inBundle": true,
+      "license": "MIT"
+    },
     "node_modules/npm/node_modules/spdx-correct": {
       "version": "3.1.1",
       "dev": true,
@@ -22981,6 +23154,125 @@
             "@npmcli/promise-spawn": "^3.0.0",
             "node-gyp": "^9.0.0",
             "read-package-json-fast": "^2.0.3"
+          },
+          "dependencies": {
+            "@npmcli/fs": {
+              "version": "1.1.1",
+              "bundled": true,
+              "dev": true,
+              "requires": {
+                "@gar/promisify": "^1.0.1",
+                "semver": "^7.3.5"
+              }
+            },
+            "brace-expansion": {
+              "version": "1.1.11",
+              "bundled": true,
+              "dev": true,
+              "requires": {
+                "balanced-match": "^1.0.0",
+                "concat-map": "0.0.1"
+              }
+            },
+            "cacache": {
+              "version": "15.3.0",
+              "bundled": true,
+              "dev": true,
+              "requires": {
+                "@npmcli/fs": "^1.0.0",
+                "@npmcli/move-file": "^1.0.1",
+                "chownr": "^2.0.0",
+                "fs-minipass": "^2.0.0",
+                "glob": "^7.1.4",
+                "infer-owner": "^1.0.4",
+                "lru-cache": "^6.0.0",
+                "minipass": "^3.1.1",
+                "minipass-collect": "^1.0.2",
+                "minipass-flush": "^1.0.5",
+                "minipass-pipeline": "^1.2.2",
+                "mkdirp": "^1.0.3",
+                "p-map": "^4.0.0",
+                "promise-inflight": "^1.0.1",
+                "rimraf": "^3.0.2",
+                "ssri": "^8.0.1",
+                "tar": "^6.0.2",
+                "unique-filename": "^1.1.1"
+              }
+            },
+            "glob": {
+              "version": "7.2.0",
+              "bundled": true,
+              "dev": true,
+              "requires": {
+                "fs.realpath": "^1.0.0",
+                "inflight": "^1.0.4",
+                "inherits": "2",
+                "minimatch": "^3.0.4",
+                "once": "^1.3.0",
+                "path-is-absolute": "^1.0.0"
+              }
+            },
+            "graceful-fs": {
+              "version": "4.2.9",
+              "bundled": true,
+              "dev": true
+            },
+            "make-fetch-happen": {
+              "version": "9.1.0",
+              "bundled": true,
+              "dev": true,
+              "requires": {
+                "agentkeepalive": "^4.1.3",
+                "cacache": "^15.2.0",
+                "http-cache-semantics": "^4.1.0",
+                "http-proxy-agent": "^4.0.1",
+                "https-proxy-agent": "^5.0.0",
+                "is-lambda": "^1.0.1",
+                "lru-cache": "^6.0.0",
+                "minipass": "^3.1.3",
+                "minipass-collect": "^1.0.2",
+                "minipass-fetch": "^1.3.2",
+                "minipass-flush": "^1.0.5",
+                "minipass-pipeline": "^1.2.4",
+                "negotiator": "^0.6.2",
+                "promise-retry": "^2.0.1",
+                "socks-proxy-agent": "^6.0.0",
+                "ssri": "^8.0.0"
+              }
+            },
+            "minimatch": {
+              "version": "3.0.4",
+              "bundled": true,
+              "dev": true,
+              "requires": {
+                "brace-expansion": "^1.1.7"
+              }
+            },
+            "node-gyp": {
+              "version": "8.4.1",
+              "bundled": true,
+              "dev": true,
+              "requires": {
+                "env-paths": "^2.2.0",
+                "glob": "^7.1.4",
+                "graceful-fs": "^4.2.6",
+                "make-fetch-happen": "^9.1.0",
+                "nopt": "^5.0.0",
+                "npmlog": "^6.0.0",
+                "rimraf": "^3.0.2",
+                "semver": "^7.3.5",
+                "tar": "^6.1.2",
+                "which": "^2.0.2"
+              }
+            },
+            "ssri": {
+              "version": "8.0.1",
+              "bundled": true,
+              "dev": true,
+              "requires": {
+                "minipass": "^3.1.1"
+              }
+            }
           }
         },
         "@tootallnate/once": {
@@ -24233,6 +24525,21 @@
             "agent-base": "^6.0.2",
             "debug": "^4.3.1",
             "socks": "^2.6.1"
+          },
+          "dependencies": {
+            "debug": {
+              "version": "4.3.4",
+              "bundled": true,
+              "dev": true,
+              "requires": {
+                "ms": "2.1.2"
+              }
+            },
+            "ms": {
+              "version": "2.1.2",
+              "bundled": true,
+              "dev": true
+            }
           }
         },
         "spdx-correct": {
diff --git a/src/getAppSyncConfig.ts b/src/getAppSyncConfig.ts
index 5ca80f4..bdfc2fa 100644
--- a/src/getAppSyncConfig.ts
+++ b/src/getAppSyncConfig.ts
@@ -16,20 +16,20 @@ type Replace<O extends object, O1 extends object> = O.Merge<
 
 export type DataSourceConfigInput = O.Omit<DataSourceConfig, 'name'>;
 
-export type ResolverConfigInput =
-  | O.Update<
-      O.Optional<ResolverConfig, 'type' | 'field'>,
-      'dataSource',
-      string | DataSourceConfigInput
-    >
-  | string;
-
-export type FunctionConfigInput =
-  | Replace<
-      { dataSource: string | DataSourceConfigInput },
-      O.Omit<PipelineFunctionConfig, 'name'>
-    >
-  | string;
+export type ResolverConfigInput = O.Update<
+  O.Update<
+    O.Optional<ResolverConfig, 'type' | 'field'>,
+    'dataSource',
+    string | DataSourceConfigInput
+  >,
+  'functions',
+  (string | FunctionConfigInput)[]
+>;
+
+export type FunctionConfigInput = Replace<
+  { dataSource: string | DataSourceConfigInput },
+  O.Omit<PipelineFunctionConfig, 'name'>
+>;
 
 export type AppSyncConfigInput = Replace<
   {
@@ -45,12 +45,7 @@ export type AppSyncConfigInput = Replace<
       | Record<string, DataSourceConfigInput>[]
       | Record<string, DataSourceConfigInput>;
   },
-  O.Optional<
-    AppSyncConfig,
-    | 'defaultMappingTemplates'
-    | 'mappingTemplatesLocation'
-    | 'additionalAuthentications'
-  >
+  O.Optional<AppSyncConfig, 'additionalAuthentications'>
 >;
 
 const flattenMaps = <T>(
@@ -65,21 +60,24 @@ const flattenMaps = <T>(
 
 export const isUnitResolver = (resolver: {
   kind?: 'UNIT' | 'PIPELINE';
-}): resolver is { kind?: 'UNIT' } => {
-  return resolver.kind === undefined || resolver.kind === 'UNIT';
+}): resolver is { kind: 'UNIT' } => {
+  return resolver.kind === 'UNIT';
+};
+
+export const isPipelineResolver = (resolver: {
+  kind?: 'UNIT' | 'PIPELINE';
+}): resolver is { kind: 'PIPELINE' } => {
+  return !resolver.kind || resolver.kind === 'PIPELINE';
+};
+
+const toResourceName = (name: string) => {
+  return name.replace(/[^a-z_]/i, '_');
 };
 
 export const getAppSyncConfig = (config: AppSyncConfigInput): AppSyncConfig => {
   const schema = Array.isArray(config.schema)
     ? config.schema
     : [config.schema || 'schema.graphql'];
-  const mappingTemplatesLocation = merge(
-    {
-      resolvers: 'mapping-templates',
-      pipelineFunctions: 'mapping-templates',
-    },
-    config.mappingTemplatesLocation,
-  );
 
   const dataSources: Record<string, DataSourceConfig> = {};
   const resolvers: Record<string, ResolverConfig> = {};
@@ -127,20 +125,30 @@ export const getAppSyncConfig = (config: AppSyncConfigInput): AppSyncConfig => {
           }
         : {
             kind: 'PIPELINE',
-            functions: resolver.functions,
+            functions: resolver.functions.map((f, index) => {
+              if (typeof f === 'string') {
+                return f;
+              }
+              const name = `${toResourceName(typeAndField)}_${index}`;
+              pipelineFunctions[name] = {
+                ...f,
+                name,
+                dataSource:
+                  typeof f.dataSource === 'string' ? f.dataSource : name,
+              };
+              if (typeof f.dataSource === 'object') {
+                dataSources[name] = {
+                  ...f.dataSource,
+                  name,
+                };
+              }
+              return name;
+            }),
           }),
     };
   });
 
   forEach(flattenMaps(config.pipelineFunctions), (func, name) => {
-    if (typeof func === 'string') {
-      pipelineFunctions[name] = {
-        name,
-        dataSource: func,
-      };
-      return;
-    }
-
     if (typeof func.dataSource === 'object') {
       dataSources[name] = {
         ...func.dataSource,
@@ -181,7 +189,6 @@ export const getAppSyncConfig = (config: AppSyncConfigInput): AppSyncConfig => {
     apiKeys,
     schema,
     dataSources,
-    mappingTemplatesLocation,
     resolvers,
     pipelineFunctions,
   };
diff --git a/src/resources/PipelineFunction.ts b/src/resources/PipelineFunction.ts
index f0d5a4a..89be01b 100644
--- a/src/resources/PipelineFunction.ts
+++ b/src/resources/PipelineFunction.ts
@@ -8,12 +8,13 @@ import { Api } from './Api';
 import path from 'path';
 import { MappingTemplate } from './MappingTemplate';
 import { SyncConfig } from './SyncConfig';
+import fs from 'fs';
 
 export class PipelineFunction {
   constructor(private api: Api, private config: PipelineFunctionConfig) {}
 
   compile(): CfnResources {
-    const { dataSource } = this.config;
+    const { dataSource, code } = this.config;
     if (!this.api.hasDataSource(dataSource)) {
       throw new this.api.plugin.serverless.classes.Error(
         `Pipeline Function '${this.config.name}' references unknown DataSource '${dataSource}'`,
@@ -36,14 +37,22 @@ export class PipelineFunction {
       MaxBatchSize: this.config.maxBatchSize,
     };
 
-    const requestMappingTemplates = this.resolveMappingTemplate('request');
-    if (requestMappingTemplates) {
-      Properties.RequestMappingTemplate = requestMappingTemplates;
-    }
+    if (code) {
+      Properties.Code = this.resolveJsCode(code);
+      Properties.Runtime = {
+        Name: 'APPSYNC_JS',
+        RuntimeVersion: '1.0.0',
+      };
+    } else {
+      const requestMappingTemplates = this.resolveMappingTemplate('request');
+      if (requestMappingTemplates) {
+        Properties.RequestMappingTemplate = requestMappingTemplates;
+      }
 
-    const responseMappingTemplate = this.resolveMappingTemplate('response');
-    if (responseMappingTemplate) {
-      Properties.ResponseMappingTemplate = responseMappingTemplate;
+      const responseMappingTemplate = this.resolveMappingTemplate('response');
+      if (responseMappingTemplate) {
+        Properties.ResponseMappingTemplate = responseMappingTemplate;
+      }
     }
 
     if (this.config.sync) {
@@ -59,19 +68,30 @@ export class PipelineFunction {
     };
   }
 
+  resolveJsCode = (filePath: string): string => {
+    const codePath = path.join(
+      this.api.plugin.serverless.config.servicePath,
+      filePath,
+    );
+
+    if (!fs.existsSync(codePath)) {
+      throw new this.api.plugin.serverless.classes.Error(
+        `The resolver handler file '${codePath}' does not exist`,
+      );
+    }
+
+    return fs.readFileSync(codePath, 'utf8');
+  };
+
   resolveMappingTemplate(
     type: 'request' | 'response',
   ): string | IntrinsicFunction | undefined {
-    const templateName =
-      type in this.config
-        ? this.config[type]
-        : this.api.config.defaultMappingTemplates?.[type];
+    const templateName = this.config[type];
 
-    if (templateName !== false) {
+    if (templateName) {
       const templatePath = path.join(
         this.api.plugin.serverless.config.servicePath,
-        this.api.config.mappingTemplatesLocation.pipelineFunctions,
-        templateName || `${this.config.name}.${type}.vtl`,
+        templateName,
       );
       const template = new MappingTemplate(this.api, {
         path: templatePath,
diff --git a/src/resources/Resolver.ts b/src/resources/Resolver.ts
index 4019430..e8c7710 100644
--- a/src/resources/Resolver.ts
+++ b/src/resources/Resolver.ts
@@ -8,6 +8,18 @@ import { Api } from './Api';
 import path from 'path';
 import { MappingTemplate } from './MappingTemplate';
 import { SyncConfig } from './SyncConfig';
+import fs from 'fs';
+
+// A decent default for pipeline JS resolvers
+const DEFAULT_JS_RESOLVERS = `
+export function request() {
+  return {};
+}
+
+export function response(ctx) {
+  return ctx.prev.result;
+}
+`;
 
 export class Resolver {
   constructor(private api: Api, private config: ResolverConfig) {}
@@ -19,14 +31,22 @@ export class Resolver {
       FieldName: this.config.field,
     };
 
-    const requestMappingTemplates = this.resolveMappingTemplate('request');
-    if (requestMappingTemplates) {
-      Properties.RequestMappingTemplate = requestMappingTemplates;
-    }
+    const isJsResolver = !(
+      this.config.kind === 'UNIT' ||
+      'request' in this.config ||
+      'response' in this.config
+    );
 
-    const responseMappingTemplate = this.resolveMappingTemplate('response');
-    if (responseMappingTemplate) {
-      Properties.ResponseMappingTemplate = responseMappingTemplate;
+    if (!isJsResolver) {
+      const requestMappingTemplates = this.resolveMappingTemplate('request');
+      if (requestMappingTemplates) {
+        Properties.RequestMappingTemplate = requestMappingTemplates;
+      }
+
+      const responseMappingTemplate = this.resolveMappingTemplate('response');
+      if (responseMappingTemplate) {
+        Properties.ResponseMappingTemplate = responseMappingTemplate;
+      }
     }
 
     if (this.config.caching) {
@@ -48,7 +68,35 @@ export class Resolver {
       Properties.SyncConfig = asyncConfig.compile();
     }
 
-    if (this.config.kind === 'PIPELINE') {
+    if (this.config.kind === 'UNIT') {
+      const { dataSource } = this.config;
+      if (!this.api.hasDataSource(dataSource)) {
+        throw new this.api.plugin.serverless.classes.Error(
+          `Resolver '${this.config.type}.${this.config.field}' references unknown DataSource '${dataSource}'`,
+        );
+      }
+
+      const logicalIdDataSource =
+        this.api.naming.getDataSourceLogicalId(dataSource);
+      Properties = {
+        ...Properties,
+        Kind: 'UNIT',
+        DataSourceName: { 'Fn::GetAtt': [logicalIdDataSource, 'Name'] },
+        MaxBatchSize: this.config.maxBatchSize,
+      };
+    } else {
+      if (isJsResolver) {
+        if (this.config.code) {
+          Properties.Code = this.resolveJsCode(this.config.code);
+        } else if (!this.config.code) {
+          Properties.Code = DEFAULT_JS_RESOLVERS;
+        }
+        Properties.Runtime = {
+          Name: 'APPSYNC_JS',
+          RuntimeVersion: '1.0.0',
+        };
+      }
+
       Properties = {
         ...Properties,
         Kind: 'PIPELINE',
@@ -66,22 +114,6 @@ export class Resolver {
           }),
         },
       };
-    } else {
-      const { dataSource } = this.config;
-      if (!this.api.hasDataSource(dataSource)) {
-        throw new this.api.plugin.serverless.classes.Error(
-          `Resolver '${this.config.type}.${this.config.field}' references unknown DataSource '${dataSource}'`,
-        );
-      }
-
-      const logicalIdDataSource =
-        this.api.naming.getDataSourceLogicalId(dataSource);
-      Properties = {
-        ...Properties,
-        Kind: 'UNIT',
-        DataSourceName: { 'Fn::GetAtt': [logicalIdDataSource, 'Name'] },
-        MaxBatchSize: this.config.maxBatchSize,
-      };
     }
 
     const logicalIdResolver = this.api.naming.getResolverLogicalId(
@@ -99,19 +131,30 @@ export class Resolver {
     };
   }
 
+  resolveJsCode = (filePath: string): string => {
+    const codePath = path.join(
+      this.api.plugin.serverless.config.servicePath,
+      filePath,
+    );
+
+    if (!fs.existsSync(codePath)) {
+      throw new this.api.plugin.serverless.classes.Error(
+        `The resolver handler file '${codePath}' does not exist`,
+      );
+    }
+
+    return fs.readFileSync(codePath, 'utf8');
+  };
+
   resolveMappingTemplate(
     type: 'request' | 'response',
   ): string | IntrinsicFunction | undefined {
-    const templateName =
-      type in this.config
-        ? this.config[type]
-        : this.api.config.defaultMappingTemplates?.[type];
+    const templateName = this.config[type];
 
-    if (templateName !== false) {
+    if (templateName) {
       const templatePath = path.join(
         this.api.plugin.serverless.config.servicePath,
-        this.api.config.mappingTemplatesLocation.resolvers,
-        templateName || `${this.config.type}.${this.config.field}.${type}.vtl`,
+        templateName,
       );
       const template = new MappingTemplate(this.api, {
         path: templatePath,
diff --git a/src/types/cloudFormation.ts b/src/types/cloudFormation.ts
index 41de830..6db069a 100644
--- a/src/types/cloudFormation.ts
+++ b/src/types/cloudFormation.ts
@@ -78,6 +78,11 @@ export type CfnDataSource = {
   };
 };
 
+export type CfnAppSyncRuntime = {
+  Name: 'APPSYNC_JS';
+  RuntimeVersion: '1.0.0';
+};
+
 export type CfnResolver = {
   RequestMappingTemplate?: string;
   ResponseMappingTemplate?: string;
@@ -89,6 +94,8 @@ export type CfnResolver = {
     DataSourceName?: string | IntrinsicFunction;
     Description?: string;
     FunctionVersion?: string;
+    Code?: string | IntrinsicFunction;
+    Runtime?: CfnAppSyncRuntime;
     RequestMappingTemplate?: string | IntrinsicFunction;
     ResponseMappingTemplate?: string | IntrinsicFunction;
     PipelineConfig?: {
@@ -117,6 +124,8 @@ export type CfnFunctionResolver = {
     DataSourceName: string | IntrinsicFunction;
     Description?: string;
     FunctionVersion?: string;
+    Code?: string | IntrinsicFunction;
+    Runtime?: CfnAppSyncRuntime;
     RequestMappingTemplate?: string | IntrinsicFunction;
     ResponseMappingTemplate?: string | IntrinsicFunction;
     SyncConfig?: {
diff --git a/src/types/plugin.ts b/src/types/plugin.ts
index 2865430..8f6138b 100644
--- a/src/types/plugin.ts
+++ b/src/types/plugin.ts
@@ -7,14 +7,6 @@ export type AppSyncConfig = {
   additionalAuthentications: Auth[];
   domain?: DomainConfig;
   apiKeys?: Record<string, ApiKeyConfig>;
-  defaultMappingTemplates?: {
-    request?: string | false;
-    response?: string | false;
-  };
-  mappingTemplatesLocation: {
-    resolvers: string;
-    pipelineFunctions: string;
-  };
   dataSources: Record<string, DataSourceConfig>;
   resolvers: Record<string, ResolverConfig>;
   pipelineFunctions: Record<string, PipelineFunctionConfig>;
@@ -170,13 +162,14 @@ export type BaseResolverConfig = {
 export type ResolverConfig = UnitResolverConfig | PipelineResolverConfig;
 
 export type UnitResolverConfig = BaseResolverConfig & {
-  kind?: 'UNIT';
+  kind: 'UNIT';
   dataSource: string;
   maxBatchSize?: number;
 };
 
 export type PipelineResolverConfig = BaseResolverConfig & {
-  kind: 'PIPELINE';
+  kind?: 'PIPELINE';
+  code?: string;
   functions: string[];
 };
 
@@ -186,8 +179,9 @@ export type PipelineFunctionConfig = {
   name: string;
   dataSource: string;
   description?: string;
-  request?: string | false;
-  response?: string | false;
+  code?: string;
+  request?: string;
+  response?: string;
   maxBatchSize?: number;
   substitutions?: Substitutions;
   sync?: SyncConfig;
diff --git a/src/validation.ts b/src/validation.ts
index ec12c87..7c12bb6 100644
--- a/src/validation.ts
+++ b/src/validation.ts
@@ -246,10 +246,6 @@ export const appSyncSchema = {
       },
       required: ['name', 'statement'],
     },
-    mappingTemplate: {
-      oneOf: [{ type: 'string' }, { type: 'boolean', const: false }],
-      errorMessage: 'must be a string or false',
-    },
     substitutions: {
       type: 'object',
       additionalProperties: {
@@ -277,20 +273,28 @@ export const appSyncSchema = {
         type: { type: 'string' },
         field: { type: 'string' },
         maxBatchSize: { type: 'number', minimum: 1, maximum: 2000 },
-        dataSource: { $ref: '#/definitions/dataSource' },
-        functions: { type: 'array', items: { type: 'string' } },
-        request: { $ref: '#/definitions/mappingTemplate' },
-        response: { $ref: '#/definitions/mappingTemplate' },
+        code: { type: 'string' },
+        request: { type: 'string' },
+        response: { type: 'string' },
         sync: { $ref: '#/definitions/syncConfig' },
         substitutions: { $ref: '#/definitions/substitutions' },
         caching: { $ref: '#/definitions/resolverCachingConfig' },
       },
-      if: { properties: { kind: { const: 'PIPELINE' } }, required: ['kind'] },
+      if: { properties: { kind: { const: 'UNIT' } }, required: ['kind'] },
       then: {
-        required: ['functions'],
+        properties: {
+          dataSource: { $ref: '#/definitions/dataSource' },
+        },
+        required: ['dataSource'],
       },
       else: {
-        required: ['dataSource'],
+        properties: {
+          functions: {
+            type: 'array',
+            items: { $ref: '#/definitions/pipelineFunction' },
+          },
+        },
+        required: ['functions'],
       },
       required: [],
     },
@@ -299,12 +303,7 @@ export const appSyncSchema = {
       patternProperties: {
         // Type.field keys, type and field are not required
         '^[_A-Za-z][_0-9A-Za-z]*\\.[_A-Za-z][_0-9A-Za-z]*$': {
-          if: { type: 'object' },
-          then: { $ref: '#/definitions/resolverConfig' },
-          else: {
-            type: 'string',
-            errorMessage: 'must be a string or an object',
-          },
+          $ref: '#/definitions/resolverConfig',
         },
       },
       additionalProperties: {
@@ -328,14 +327,22 @@ export const appSyncSchema = {
       properties: {
         dataSource: { $ref: '#/definitions/dataSource' },
         description: { type: 'string' },
-        request: { $ref: '#/definitions/mappingTemplate' },
-        response: { $ref: '#/definitions/mappingTemplate' },
+        request: { type: 'string' },
+        response: { type: 'string' },
         sync: { $ref: '#/definitions/syncConfig' },
         maxBatchSize: { type: 'number', minimum: 1, maximum: 2000 },
         substitutions: { $ref: '#/definitions/substitutions' },
       },
       required: ['dataSource'],
     },
+    pipelineFunction: {
+      if: { type: 'object' },
+      then: { $ref: '#/definitions/pipelineFunctionConfig' },
+      else: {
+        type: 'string',
+        errorMessage: 'must be a string or function definition',
+      },
+    },
     pipelineFunctionConfigMap: {
       type: 'object',
       additionalProperties: {
@@ -755,24 +762,6 @@ export const appSyncSchema = {
       },
       required: ['level'],
     },
-    mappingTemplatesLocation: {
-      type: 'object',
-      properties: {
-        resolvers: { type: 'string' },
-        pipelineFunctions: { type: 'string' },
-      },
-    },
-    defaultMappingTemplates: {
-      type: 'object',
-      properties: {
-        request: {
-          oneOf: [{ type: 'string' }, { type: 'boolean' }],
-        },
-        response: {
-          oneOf: [{ type: 'string' }, { type: 'boolean' }],
-        },
-      },
-    },
     dataSources: {
       oneOf: [
         {


Test patch:
diff --git a/src/__tests__/__snapshots__/getAppSyncConfig.test.ts.snap b/src/__tests__/__snapshots__/getAppSyncConfig.test.ts.snap
index ef605fa..df8c86f 100644
--- a/src/__tests__/__snapshots__/getAppSyncConfig.test.ts.snap
+++ b/src/__tests__/__snapshots__/getAppSyncConfig.test.ts.snap
@@ -24,6 +24,13 @@ Object {
 exports[`DataSources should merge dataSources embedded into resolvers and pipelineFunctions 1`] = `
 Object {
   "dataSources": Object {
+    "Mutation_createUser_0": Object {
+      "config": Object {
+        "functionName": "createUser",
+      },
+      "name": "Mutation_createUser_0",
+      "type": "AWS_LAMBDA",
+    },
     "Query_getUser": Object {
       "config": Object {
         "functionName": "getUser",
@@ -61,7 +68,29 @@ Object {
       "type": "NONE",
     },
   },
+  "pipelineFunctions": Object {
+    "Mutation_createUser_0": Object {
+      "dataSource": "Mutation_createUser_0",
+      "name": "Mutation_createUser_0",
+    },
+    "function1": Object {
+      "dataSource": "function1",
+      "name": "function1",
+    },
+    "function2": Object {
+      "dataSource": "function2",
+      "name": "function2",
+    },
+  },
   "resolvers": Object {
+    "Mutation.createUser": Object {
+      "field": "createUser",
+      "functions": Array [
+        "Mutation_createUser_0",
+      ],
+      "kind": "PIPELINE",
+      "type": "Mutation",
+    },
     "Query.getUser": Object {
       "dataSource": "Query_getUser",
       "field": "getUser",
@@ -96,21 +125,39 @@ Object {
     "dataSource": "users",
     "name": "function4",
   },
-  "function5": Object {
+}
+`;
+
+exports[`Pipeline Functions should merge inline function definitions 1`] = `
+Object {
+  "Mutation_createUser_0": Object {
+    "code": "function1.js",
+    "dataSource": "users",
+    "name": "Mutation_createUser_0",
+  },
+  "Mutation_createUser_1": Object {
+    "code": "function2.js",
     "dataSource": "users",
-    "name": "function5",
+    "name": "Mutation_createUser_1",
+  },
+  "Mutation_updateUser_0": Object {
+    "code": "updateUser.js",
+    "dataSource": "Mutation_updateUser_0",
+    "name": "Mutation_updateUser_0",
+  },
+  "function1": Object {
+    "dataSource": "users",
+    "name": "function1",
+  },
+  "function2": Object {
+    "dataSource": "users",
+    "name": "function2",
   },
 }
 `;
 
 exports[`Resolvers should merge resolvers arrays 1`] = `
 Object {
-  "Query.getComments": Object {
-    "dataSource": "comments",
-    "field": "getComments",
-    "kind": "UNIT",
-    "type": "Query",
-  },
   "Query.getPost": Object {
     "dataSource": "posts",
     "field": "getPost",
@@ -158,12 +205,6 @@ Object {
 
 exports[`Resolvers should resolve resolver type and fields 1`] = `
 Object {
-  "Query.getPosts": Object {
-    "dataSource": "posts",
-    "field": "getPosts",
-    "kind": "UNIT",
-    "type": "Query",
-  },
   "Query.getUser": Object {
     "dataSource": "users",
     "field": "getUser",
@@ -206,10 +247,6 @@ Object {
     "type": "API_KEY",
   },
   "dataSources": Object {},
-  "mappingTemplatesLocation": Object {
-    "pipelineFunctions": "mapping-templates",
-    "resolvers": "mapping-templates",
-  },
   "name": "My Api",
   "pipelineFunctions": Object {},
   "resolvers": Object {},
diff --git a/src/__tests__/__snapshots__/resolvers.test.ts.snap b/src/__tests__/__snapshots__/resolvers.test.ts.snap
deleted file mode 100644
index 7ff432d..0000000
--- a/src/__tests__/__snapshots__/resolvers.test.ts.snap
+++ /dev/null
@@ -1,180 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Resolvers Pipeline Function should generate Resources with sync configuration 1`] = `
-Object {
-  "GraphQlFunctionConfigurationmyFunction": Object {
-    "Properties": Object {
-      "ApiId": Object {
-        "Fn::GetAtt": Array [
-          "GraphQlApi",
-          "ApiId",
-        ],
-      },
-      "DataSourceName": Object {
-        "Fn::GetAtt": Array [
-          "GraphQlDsmyLambdaFunction",
-          "Name",
-        ],
-      },
-      "Description": undefined,
-      "FunctionVersion": "2018-05-29",
-      "MaxBatchSize": undefined,
-      "Name": "myFunction",
-      "RequestMappingTemplate": "Content of path/to/mappingTemplates/myFunction.request.vtl",
-      "ResponseMappingTemplate": "Content of path/to/mappingTemplates/myFunction.response.vtl",
-      "SyncConfig": Object {
-        "ConflictDetection": "VERSION",
-        "ConflictHandler": "LAMBDA",
-        "LambdaConflictHandlerConfig": Object {
-          "LambdaConflictHandlerArn": Object {
-            "Fn::GetAtt": Array [
-              "MyFunctionUnderscoreSyncLambdaFunction",
-              "Arn",
-            ],
-          },
-        },
-      },
-    },
-    "Type": "AWS::AppSync::FunctionConfiguration",
-  },
-}
-`;
-
-exports[`Resolvers Pipeline Function should generate Resources with sync configuration 2`] = `
-Object {
-  "myFunction_Sync": Object {
-    "handler": "index.handler",
-  },
-}
-`;
-
-exports[`Resolvers Pipeline Resovlers should generate Resources with default mapping templates 1`] = `
-Object {
-  "GraphQlResolverQueryuser": Object {
-    "DependsOn": Array [
-      "GraphQlSchema",
-    ],
-    "Properties": Object {
-      "ApiId": Object {
-        "Fn::GetAtt": Array [
-          "GraphQlApi",
-          "ApiId",
-        ],
-      },
-      "FieldName": "user",
-      "Kind": "PIPELINE",
-      "PipelineConfig": Object {
-        "Functions": Array [
-          Object {
-            "Fn::GetAtt": Array [
-              "GraphQlFunctionConfigurationfunction1",
-              "FunctionId",
-            ],
-          },
-          Object {
-            "Fn::GetAtt": Array [
-              "GraphQlFunctionConfigurationfunction2",
-              "FunctionId",
-            ],
-          },
-        ],
-      },
-      "RequestMappingTemplate": "Content of path/to/mappingTemplates/Query.user.request.vtl",
-      "ResponseMappingTemplate": "Content of path/to/mappingTemplates/Query.user.response.vtl",
-      "TypeName": "Query",
-    },
-    "Type": "AWS::AppSync::Resolver",
-  },
-}
-`;
-
-exports[`Resolvers Pipeline Resovlers should generate Resources with specific mapping templates 1`] = `
-Object {
-  "GraphQlResolverQueryuser": Object {
-    "DependsOn": Array [
-      "GraphQlSchema",
-    ],
-    "Properties": Object {
-      "ApiId": Object {
-        "Fn::GetAtt": Array [
-          "GraphQlApi",
-          "ApiId",
-        ],
-      },
-      "FieldName": "user",
-      "Kind": "PIPELINE",
-      "PipelineConfig": Object {
-        "Functions": Array [
-          Object {
-            "Fn::GetAtt": Array [
-              "GraphQlFunctionConfigurationfunction1",
-              "FunctionId",
-            ],
-          },
-          Object {
-            "Fn::GetAtt": Array [
-              "GraphQlFunctionConfigurationfunction2",
-              "FunctionId",
-            ],
-          },
-        ],
-      },
-      "RequestMappingTemplate": "Content of path/to/mappingTemplates/specific.request.tpl",
-      "ResponseMappingTemplate": "Content of path/to/mappingTemplates/specific.response.tpl",
-      "TypeName": "Query",
-    },
-    "Type": "AWS::AppSync::Resolver",
-  },
-}
-`;
-
-exports[`Resolvers Unit Resolvers should generate Resources with sync configuration 1`] = `
-Object {
-  "GraphQlResolverQueryuser": Object {
-    "DependsOn": Array [
-      "GraphQlSchema",
-    ],
-    "Properties": Object {
-      "ApiId": Object {
-        "Fn::GetAtt": Array [
-          "GraphQlApi",
-          "ApiId",
-        ],
-      },
-      "DataSourceName": Object {
-        "Fn::GetAtt": Array [
-          "GraphQlDsmyLambdaFunction",
-          "Name",
-        ],
-      },
-      "FieldName": "user",
-      "Kind": "UNIT",
-      "MaxBatchSize": undefined,
-      "RequestMappingTemplate": "Content of path/to/mappingTemplates/Query.user.request.vtl",
-      "ResponseMappingTemplate": "Content of path/to/mappingTemplates/Query.user.response.vtl",
-      "SyncConfig": Object {
-        "ConflictDetection": "VERSION",
-        "ConflictHandler": "LAMBDA",
-        "LambdaConflictHandlerConfig": Object {
-          "LambdaConflictHandlerArn": Object {
-            "Fn::GetAtt": Array [
-              "QueryUnderscoreuserUnderscoreSyncLambdaFunction",
-              "Arn",
-            ],
-          },
-        },
-      },
-      "TypeName": "Query",
-    },
-    "Type": "AWS::AppSync::Resolver",
-  },
-}
-`;
-
-exports[`Resolvers Unit Resolvers should generate Resources with sync configuration 2`] = `
-Object {
-  "Query_user_Sync": Object {
-    "handler": "index.handler",
-  },
-}
-`;
diff --git a/src/__tests__/fixtures/appsync/serverless.yml b/src/__tests__/fixtures/appsync/serverless.yml
index 92c0667..69e8c47 100644
--- a/src/__tests__/fixtures/appsync/serverless.yml
+++ b/src/__tests__/fixtures/appsync/serverless.yml
@@ -14,9 +14,11 @@ appSync:
 
   resolvers:
     Query.user:
+      kind: UNIT
       dataSource: data
 
     Mutation.createUser:
+      kind: UNIT
       request: false
       response: false
       dataSource:
diff --git a/src/__tests__/getAppSyncConfig.test.ts b/src/__tests__/getAppSyncConfig.test.ts
index 2059316..d68400f 100644
--- a/src/__tests__/getAppSyncConfig.test.ts
+++ b/src/__tests__/getAppSyncConfig.test.ts
@@ -121,6 +121,7 @@ describe('DataSources', () => {
           },
         },
         getUsers: {
+          kind: 'UNIT',
           type: 'Query',
           field: 'getUsers',
           dataSource: {
@@ -130,6 +131,19 @@ describe('DataSources', () => {
             },
           },
         },
+        'Mutation.createUser': {
+          kind: 'PIPELINE',
+          functions: [
+            {
+              dataSource: {
+                type: 'AWS_LAMBDA',
+                config: {
+                  functionName: 'createUser',
+                },
+              },
+            },
+          ],
+        },
       },
       pipelineFunctions: {
         function1: {
@@ -150,7 +164,9 @@ describe('DataSources', () => {
         },
       },
     });
-    expect(pick(config, ['dataSources', 'resolvers'])).toMatchSnapshot();
+    expect(
+      pick(config, ['dataSources', 'resolvers', 'pipelineFunctions']),
+    ).toMatchSnapshot();
   });
 });
 
@@ -160,14 +176,15 @@ describe('Resolvers', () => {
       ...basicConfig,
       resolvers: {
         'Query.getUser': {
+          kind: 'UNIT',
           dataSource: 'users',
         },
         getUsersResolver: {
           type: 'Query',
           field: 'getUsers',
+          kind: 'UNIT',
           dataSource: 'users',
         },
-        'Query.getPosts': 'posts',
       },
     });
     expect(config.resolvers).toMatchSnapshot();
@@ -179,9 +196,11 @@ describe('Resolvers', () => {
       resolvers: [
         {
           'Query.getUser': {
+            kind: 'UNIT',
             dataSource: 'users',
           },
           getUsersResolver: {
+            kind: 'UNIT',
             type: 'Query',
             field: 'getUsers',
             dataSource: 'users',
@@ -193,11 +212,12 @@ describe('Resolvers', () => {
         },
         {
           'Query.getPost': {
+            kind: 'UNIT',
             dataSource: 'posts',
           },
-          'Query.getComments': 'comments',
           getPostsResolver: {
             type: 'Query',
+            kind: 'UNIT',
             field: 'getPosts',
             dataSource: 'posts',
           },
@@ -235,10 +255,53 @@ describe('Pipeline Functions', () => {
           function4: {
             dataSource: 'users',
           },
-          function5: 'users',
         },
       ],
     });
     expect(config.pipelineFunctions).toMatchSnapshot();
   });
+
+  it('should merge inline function definitions', async () => {
+    const config = getAppSyncConfig({
+      ...basicConfig,
+      resolvers: {
+        'Mutation.createUser': {
+          kind: 'PIPELINE',
+          functions: [
+            {
+              dataSource: 'users',
+              code: 'function1.js',
+            },
+            {
+              dataSource: 'users',
+              code: 'function2.js',
+            },
+          ],
+        },
+        'Mutation.updateUser': {
+          kind: 'PIPELINE',
+          functions: [
+            {
+              code: 'updateUser.js',
+              dataSource: {
+                type: 'AWS_LAMBDA',
+                config: {
+                  functionName: 'updateUser',
+                },
+              },
+            },
+          ],
+        },
+      },
+      pipelineFunctions: {
+        function1: {
+          dataSource: 'users',
+        },
+        function2: {
+          dataSource: 'users',
+        },
+      },
+    });
+    expect(config.pipelineFunctions).toMatchSnapshot();
+  });
 });
diff --git a/src/__tests__/given.ts b/src/__tests__/given.ts
index eff8f08..63ab67c 100644
--- a/src/__tests__/given.ts
+++ b/src/__tests__/given.ts
@@ -34,10 +34,6 @@ export const appSyncConfig = (partial?: Partial<AppSyncConfig>) => {
       type: 'API_KEY',
     },
     additionalAuthentications: [],
-    mappingTemplatesLocation: {
-      resolvers: 'path/to/mappingTemplates',
-      pipelineFunctions: 'path/to/mappingTemplates',
-    },
     resolvers: {},
     pipelineFunctions: {},
     dataSources: {},
diff --git a/src/__tests__/resolvers.test.ts b/src/__tests__/resolvers.test.ts
index c593ede..28ea5fe 100644
--- a/src/__tests__/resolvers.test.ts
+++ b/src/__tests__/resolvers.test.ts
@@ -22,7 +22,7 @@ describe('Resolvers', () => {
   });
 
   describe('Unit Resolvers', () => {
-    it('should generate Resources with default mapping templates', () => {
+    it('should generate Resources with VTL mapping templates', () => {
       const api = new Api(
         given.appSyncConfig({
           dataSources: {
@@ -41,6 +41,8 @@ describe('Resolvers', () => {
           kind: 'UNIT',
           type: 'Query',
           field: 'user',
+          request: 'path/to/mappingTemplates/Query.user.request.vtl',
+          response: 'path/to/mappingTemplates/Query.user.response.vtl',
         }),
       ).toMatchInlineSnapshot(`
         Object {
@@ -74,14 +76,14 @@ describe('Resolvers', () => {
       `);
     });
 
-    it('should generate Resources with maxBatchSize', () => {
+    it('should generate Resources with direct Lambda', () => {
       const api = new Api(
         given.appSyncConfig({
           dataSources: {
-            myFunction: {
-              name: 'myFunction',
+            myLambdaFunction: {
+              name: 'myLambdaFunction',
               type: 'AWS_LAMBDA',
-              config: { functionName: 'myFunction' },
+              config: { functionArn: 'arn:lambda:' },
             },
           },
         }),
@@ -89,11 +91,10 @@ describe('Resolvers', () => {
       );
       expect(
         api.compileResolver({
-          dataSource: 'myFunction',
+          dataSource: 'myLambdaFunction',
           kind: 'UNIT',
           type: 'Query',
           field: 'user',
-          maxBatchSize: 200,
         }),
       ).toMatchInlineSnapshot(`
         Object {
@@ -110,15 +111,13 @@ describe('Resolvers', () => {
               },
               "DataSourceName": Object {
                 "Fn::GetAtt": Array [
-                  "GraphQlDsmyFunction",
+                  "GraphQlDsmyLambdaFunction",
                   "Name",
                 ],
               },
               "FieldName": "user",
               "Kind": "UNIT",
-              "MaxBatchSize": 200,
-              "RequestMappingTemplate": "Content of path/to/mappingTemplates/Query.user.request.vtl",
-              "ResponseMappingTemplate": "Content of path/to/mappingTemplates/Query.user.response.vtl",
+              "MaxBatchSize": undefined,
               "TypeName": "Query",
             },
             "Type": "AWS::AppSync::Resolver",
@@ -127,14 +126,14 @@ describe('Resolvers', () => {
       `);
     });
 
-    it('should generate Resources with default specific templates', () => {
+    it('should generate Resources with maxBatchSize', () => {
       const api = new Api(
         given.appSyncConfig({
           dataSources: {
-            myTable: {
-              name: 'myTable',
-              type: 'AMAZON_DYNAMODB',
-              config: { tableName: 'data' },
+            myFunction: {
+              name: 'myFunction',
+              type: 'AWS_LAMBDA',
+              config: { functionName: 'myFunction' },
             },
           },
         }),
@@ -142,12 +141,11 @@ describe('Resolvers', () => {
       );
       expect(
         api.compileResolver({
-          dataSource: 'myTable',
+          dataSource: 'myFunction',
           kind: 'UNIT',
           type: 'Query',
           field: 'user',
-          request: 'specific.request.tpl',
-          response: 'specific.response.tpl',
+          maxBatchSize: 200,
         }),
       ).toMatchInlineSnapshot(`
         Object {
@@ -164,15 +162,13 @@ describe('Resolvers', () => {
               },
               "DataSourceName": Object {
                 "Fn::GetAtt": Array [
-                  "GraphQlDsmyTable",
+                  "GraphQlDsmyFunction",
                   "Name",
                 ],
               },
               "FieldName": "user",
               "Kind": "UNIT",
-              "MaxBatchSize": undefined,
-              "RequestMappingTemplate": "Content of path/to/mappingTemplates/specific.request.tpl",
-              "ResponseMappingTemplate": "Content of path/to/mappingTemplates/specific.response.tpl",
+              "MaxBatchSize": 200,
               "TypeName": "Query",
             },
             "Type": "AWS::AppSync::Resolver",
@@ -181,7 +177,7 @@ describe('Resolvers', () => {
       `);
     });
 
-    it('should generate Resources with direct Lambda templates', () => {
+    it('should generate Resources with sync configuration', () => {
       const api = new Api(
         given.appSyncConfig({
           dataSources: {
@@ -200,8 +196,13 @@ describe('Resolvers', () => {
           kind: 'UNIT',
           type: 'Query',
           field: 'user',
-          request: false,
-          response: false,
+          sync: {
+            conflictDetection: 'VERSION',
+            conflictHandler: 'LAMBDA',
+            function: {
+              handler: 'index.handler',
+            },
+          },
         }),
       ).toMatchInlineSnapshot(`
         Object {
@@ -225,43 +226,31 @@ describe('Resolvers', () => {
               "FieldName": "user",
               "Kind": "UNIT",
               "MaxBatchSize": undefined,
+              "SyncConfig": Object {
+                "ConflictDetection": "VERSION",
+                "ConflictHandler": "LAMBDA",
+                "LambdaConflictHandlerConfig": Object {
+                  "LambdaConflictHandlerArn": Object {
+                    "Fn::GetAtt": Array [
+                      "QueryUnderscoreuserUnderscoreSyncLambdaFunction",
+                      "Arn",
+                    ],
+                  },
+                },
+              },
               "TypeName": "Query",
             },
             "Type": "AWS::AppSync::Resolver",
           },
         }
       `);
-    });
-
-    it('should generate Resources with sync configuration', () => {
-      const api = new Api(
-        given.appSyncConfig({
-          dataSources: {
-            myLambdaFunction: {
-              name: 'myLambdaFunction',
-              type: 'AWS_LAMBDA',
-              config: { functionArn: 'arn:lambda:' },
-            },
-          },
-        }),
-        plugin,
-      );
-      expect(
-        api.compileResolver({
-          dataSource: 'myLambdaFunction',
-          kind: 'UNIT',
-          type: 'Query',
-          field: 'user',
-          sync: {
-            conflictDetection: 'VERSION',
-            conflictHandler: 'LAMBDA',
-            function: {
-              handler: 'index.handler',
-            },
+      expect(api.functions).toMatchInlineSnapshot(`
+        Object {
+          "Query_user_Sync": Object {
+            "handler": "index.handler",
           },
-        }),
-      ).toMatchSnapshot();
-      expect(api.functions).toMatchSnapshot();
+        }
+      `);
     });
 
     it('should fail when referencing unknown data source', () => {
@@ -285,7 +274,8 @@ describe('Resolvers', () => {
   });
 
   describe('Pipeline Resovlers', () => {
-    it('should generate Resources with default mapping templates', () => {
+    it('should generate JS Resources with default empty resolver', () => {
+      mockEists.mockReturnValue(false);
       const api = new Api(
         given.appSyncConfig({
           dataSources: {
@@ -296,12 +286,8 @@ describe('Resolvers', () => {
             },
           },
           pipelineFunctions: {
-            function1: {
-              name: 'function1',
-              dataSource: 'myTable',
-            },
-            function2: {
-              name: 'function2',
+            getUser: {
+              name: 'getUser',
               dataSource: 'myTable',
             },
           },
@@ -310,15 +296,57 @@ describe('Resolvers', () => {
       );
       expect(
         api.compileResolver({
-          kind: 'PIPELINE',
           type: 'Query',
           field: 'user',
-          functions: ['function1', 'function2'],
+          functions: ['getUser'],
         }),
-      ).toMatchSnapshot();
+      ).toMatchInlineSnapshot(`
+        Object {
+          "GraphQlResolverQueryuser": Object {
+            "DependsOn": Array [
+              "GraphQlSchema",
+            ],
+            "Properties": Object {
+              "ApiId": Object {
+                "Fn::GetAtt": Array [
+                  "GraphQlApi",
+                  "ApiId",
+                ],
+              },
+              "Code": "
+        export function request() {
+          return {};
+        }
+
+        export function response(ctx) {
+          return ctx.prev.result;
+        }
+        ",
+              "FieldName": "user",
+              "Kind": "PIPELINE",
+              "PipelineConfig": Object {
+                "Functions": Array [
+                  Object {
+                    "Fn::GetAtt": Array [
+                      "GraphQlFunctionConfigurationgetUser",
+                      "FunctionId",
+                    ],
+                  },
+                ],
+              },
+              "Runtime": Object {
+                "Name": "APPSYNC_JS",
+                "RuntimeVersion": "1.0.0",
+              },
+              "TypeName": "Query",
+            },
+            "Type": "AWS::AppSync::Resolver",
+          },
+        }
+      `);
     });
 
-    it('should generate Resources with specific mapping templates', () => {
+    it('should generate Resources with VTL mapping templates', () => {
       const api = new Api(
         given.appSyncConfig({
           dataSources: {
@@ -346,11 +374,113 @@ describe('Resolvers', () => {
           kind: 'PIPELINE',
           type: 'Query',
           field: 'user',
+          request: 'Query.user.request.vtl',
+          response: 'Query.user.response.vtl',
           functions: ['function1', 'function2'],
-          request: 'specific.request.tpl',
-          response: 'specific.response.tpl',
         }),
-      ).toMatchSnapshot();
+      ).toMatchInlineSnapshot(`
+        Object {
+          "GraphQlResolverQueryuser": Object {
+            "DependsOn": Array [
+              "GraphQlSchema",
+            ],
+            "Properties": Object {
+              "ApiId": Object {
+                "Fn::GetAtt": Array [
+                  "GraphQlApi",
+                  "ApiId",
+                ],
+              },
+              "FieldName": "user",
+              "Kind": "PIPELINE",
+              "PipelineConfig": Object {
+                "Functions": Array [
+                  Object {
+                    "Fn::GetAtt": Array [
+                      "GraphQlFunctionConfigurationfunction1",
+                      "FunctionId",
+                    ],
+                  },
+                  Object {
+                    "Fn::GetAtt": Array [
+                      "GraphQlFunctionConfigurationfunction2",
+                      "FunctionId",
+                    ],
+                  },
+                ],
+              },
+              "RequestMappingTemplate": "Content of Query.user.request.vtl",
+              "ResponseMappingTemplate": "Content of Query.user.response.vtl",
+              "TypeName": "Query",
+            },
+            "Type": "AWS::AppSync::Resolver",
+          },
+        }
+      `);
+    });
+
+    it('should generate JS Resources with specific code', () => {
+      const api = new Api(
+        given.appSyncConfig({
+          dataSources: {
+            myTable: {
+              name: 'myTable',
+              type: 'AMAZON_DYNAMODB',
+              config: { tableName: 'data' },
+            },
+          },
+          pipelineFunctions: {
+            getUser: {
+              name: 'getUser',
+              dataSource: 'myTable',
+            },
+          },
+        }),
+        plugin,
+      );
+      expect(
+        api.compileResolver({
+          type: 'Query',
+          field: 'user',
+          functions: ['getUser'],
+          code: 'resolvers/getUserFunction.js',
+        }),
+      ).toMatchInlineSnapshot(`
+        Object {
+          "GraphQlResolverQueryuser": Object {
+            "DependsOn": Array [
+              "GraphQlSchema",
+            ],
+            "Properties": Object {
+              "ApiId": Object {
+                "Fn::GetAtt": Array [
+                  "GraphQlApi",
+                  "ApiId",
+                ],
+              },
+              "Code": "Content of resolvers/getUserFunction.js",
+              "FieldName": "user",
+              "Kind": "PIPELINE",
+              "PipelineConfig": Object {
+                "Functions": Array [
+                  Object {
+                    "Fn::GetAtt": Array [
+                      "GraphQlFunctionConfigurationgetUser",
+                      "FunctionId",
+                    ],
+                  },
+                ],
+              },
+              "Runtime": Object {
+                "Name": "APPSYNC_JS",
+                "RuntimeVersion": "1.0.0",
+              },
+              "TypeName": "Query",
+            },
+            "Type": "AWS::AppSync::Resolver",
+          },
+        }
+      `);
     });
 
     it('should fail when referencing unknown pipeline function', () => {
@@ -386,7 +516,7 @@ describe('Resolvers', () => {
   });
 
   describe('Pipeline Function', () => {
-    it('should generate Pipeline Function Resources with default mapping templates', () => {
+    it('should generate Pipeline Function Resources with JS code', () => {
       const api = new Api(
         given.appSyncConfig({
           dataSources: {
@@ -404,6 +534,7 @@ describe('Resolvers', () => {
           name: 'function1',
           dataSource: 'myTable',
           description: 'Function1 Pipeline Resolver',
+          code: 'funciton1.js',
         }),
       ).toMatchInlineSnapshot(`
         Object {
@@ -415,6 +546,7 @@ describe('Resolvers', () => {
                   "ApiId",
                 ],
               },
+              "Code": "Content of funciton1.js",
               "DataSourceName": Object {
                 "Fn::GetAtt": Array [
                   "GraphQlDsmyTable",
@@ -425,8 +557,10 @@ describe('Resolvers', () => {
               "FunctionVersion": "2018-05-29",
               "MaxBatchSize": undefined,
               "Name": "function1",
-              "RequestMappingTemplate": "Content of path/to/mappingTemplates/function1.request.vtl",
-              "ResponseMappingTemplate": "Content of path/to/mappingTemplates/function1.response.vtl",
+              "Runtime": Object {
+                "Name": "APPSYNC_JS",
+                "RuntimeVersion": "1.0.0",
+              },
             },
             "Type": "AWS::AppSync::FunctionConfiguration",
           },
@@ -434,14 +568,14 @@ describe('Resolvers', () => {
       `);
     });
 
-    it('should generate Resources with sync configuration', () => {
+    it('should generate Pipeline Function Resources with VTL mapping tempaltes', () => {
       const api = new Api(
         given.appSyncConfig({
           dataSources: {
-            myLambdaFunction: {
-              name: 'myLambdaFunction',
-              type: 'AWS_LAMBDA',
-              config: { functionArn: 'arn:lambda:' },
+            myTable: {
+              name: 'myTable',
+              type: 'AMAZON_DYNAMODB',
+              config: { tableName: 'data' },
             },
           },
         }),
@@ -449,28 +583,49 @@ describe('Resolvers', () => {
       );
       expect(
         api.compilePipelineFunctionResource({
-          dataSource: 'myLambdaFunction',
-          name: 'myFunction',
-          sync: {
-            conflictDetection: 'VERSION',
-            conflictHandler: 'LAMBDA',
-            function: {
-              handler: 'index.handler',
+          name: 'function1',
+          dataSource: 'myTable',
+          description: 'Function1 Pipeline Resolver',
+          request: 'path/to/mappingTemplates/function1.request.vtl',
+          response: 'path/to/mappingTemplates/function1.response.vtl',
+        }),
+      ).toMatchInlineSnapshot(`
+        Object {
+          "GraphQlFunctionConfigurationfunction1": Object {
+            "Properties": Object {
+              "ApiId": Object {
+                "Fn::GetAtt": Array [
+                  "GraphQlApi",
+                  "ApiId",
+                ],
+              },
+              "DataSourceName": Object {
+                "Fn::GetAtt": Array [
+                  "GraphQlDsmyTable",
+                  "Name",
+                ],
+              },
+              "Description": "Function1 Pipeline Resolver",
+              "FunctionVersion": "2018-05-29",
+              "MaxBatchSize": undefined,
+              "Name": "function1",
+              "RequestMappingTemplate": "Content of path/to/mappingTemplates/function1.request.vtl",
+              "ResponseMappingTemplate": "Content of path/to/mappingTemplates/function1.response.vtl",
             },
+            "Type": "AWS::AppSync::FunctionConfiguration",
           },
-        }),
-      ).toMatchSnapshot();
-      expect(api.functions).toMatchSnapshot();
+        }
+      `);
     });
 
-    it('should generate Pipeline Function Resources with maxBatchSize', () => {
+    it('should generate Pipeline Function Resources with direct Lambda', () => {
       const api = new Api(
         given.appSyncConfig({
           dataSources: {
-            myFunction: {
-              name: 'myFunction',
+            myLambdaFunction: {
+              name: 'myLambdaFunction',
               type: 'AWS_LAMBDA',
-              config: { functionName: 'myFunction' },
+              config: { functionArn: 'arn:lambda:' },
             },
           },
         }),
@@ -479,9 +634,8 @@ describe('Resolvers', () => {
       expect(
         api.compilePipelineFunctionResource({
           name: 'function1',
-          dataSource: 'myFunction',
+          dataSource: 'myLambdaFunction',
           description: 'Function1 Pipeline Resolver',
-          maxBatchSize: 200,
         }),
       ).toMatchInlineSnapshot(`
         Object {
@@ -495,16 +649,14 @@ describe('Resolvers', () => {
               },
               "DataSourceName": Object {
                 "Fn::GetAtt": Array [
-                  "GraphQlDsmyFunction",
+                  "GraphQlDsmyLambdaFunction",
                   "Name",
                 ],
               },
               "Description": "Function1 Pipeline Resolver",
               "FunctionVersion": "2018-05-29",
-              "MaxBatchSize": 200,
+              "MaxBatchSize": undefined,
               "Name": "function1",
-              "RequestMappingTemplate": "Content of path/to/mappingTemplates/function1.request.vtl",
-              "ResponseMappingTemplate": "Content of path/to/mappingTemplates/function1.response.vtl",
             },
             "Type": "AWS::AppSync::FunctionConfiguration",
           },
@@ -512,14 +664,14 @@ describe('Resolvers', () => {
       `);
     });
 
-    it('should generate Pipeline Function Resources with specific mapping tempaltes', () => {
+    it('should generate Resources with sync configuration', () => {
       const api = new Api(
         given.appSyncConfig({
           dataSources: {
-            myTable: {
-              name: 'myTable',
-              type: 'AMAZON_DYNAMODB',
-              config: { tableName: 'data' },
+            myLambdaFunction: {
+              name: 'myLambdaFunction',
+              type: 'AWS_LAMBDA',
+              config: { functionArn: 'arn:lambda:' },
             },
           },
         }),
@@ -527,15 +679,21 @@ describe('Resolvers', () => {
       );
       expect(
         api.compilePipelineFunctionResource({
-          name: 'function1',
-          dataSource: 'myTable',
-          description: 'Function1 Pipeline Resolver',
-          request: 'specific.request.tpl',
-          response: 'specific.response.tpl',
+          dataSource: 'myLambdaFunction',
+          name: 'myFunction',
+          request: 'myFunction.request.vtl',
+          response: 'myFunction.response.vtl',
+          sync: {
+            conflictDetection: 'VERSION',
+            conflictHandler: 'LAMBDA',
+            function: {
+              handler: 'index.handler',
+            },
+          },
         }),
       ).toMatchInlineSnapshot(`
         Object {
-          "GraphQlFunctionConfigurationfunction1": Object {
+          "GraphQlFunctionConfigurationmyFunction": Object {
             "Properties": Object {
               "ApiId": Object {
                 "Fn::GetAtt": Array [
@@ -545,16 +703,28 @@ describe('Resolvers', () => {
               },
               "DataSourceName": Object {
                 "Fn::GetAtt": Array [
-                  "GraphQlDsmyTable",
+                  "GraphQlDsmyLambdaFunction",
                   "Name",
                 ],
               },
-              "Description": "Function1 Pipeline Resolver",
+              "Description": undefined,
               "FunctionVersion": "2018-05-29",
               "MaxBatchSize": undefined,
-              "Name": "function1",
-              "RequestMappingTemplate": "Content of path/to/mappingTemplates/specific.request.tpl",
-              "ResponseMappingTemplate": "Content of path/to/mappingTemplates/specific.response.tpl",
+              "Name": "myFunction",
+              "RequestMappingTemplate": "Content of myFunction.request.vtl",
+              "ResponseMappingTemplate": "Content of myFunction.response.vtl",
+              "SyncConfig": Object {
+                "ConflictDetection": "VERSION",
+                "ConflictHandler": "LAMBDA",
+                "LambdaConflictHandlerConfig": Object {
+                  "LambdaConflictHandlerArn": Object {
+                    "Fn::GetAtt": Array [
+                      "MyFunctionUnderscoreSyncLambdaFunction",
+                      "Arn",
+                    ],
+                  },
+                },
+              },
             },
             "Type": "AWS::AppSync::FunctionConfiguration",
           },
@@ -562,14 +732,14 @@ describe('Resolvers', () => {
       `);
     });
 
-    it('should generate Pipeline Function Resources with direct Lambda mapping tempaltes', () => {
+    it('should generate Pipeline Function Resources with maxBatchSize', () => {
       const api = new Api(
         given.appSyncConfig({
           dataSources: {
-            myLambdaFunction: {
-              name: 'myLambdaFunction',
+            myFunction: {
+              name: 'myFunction',
               type: 'AWS_LAMBDA',
-              config: { functionArn: 'arn:lambda:' },
+              config: { functionName: 'myFunction' },
             },
           },
         }),
@@ -578,10 +748,11 @@ describe('Resolvers', () => {
       expect(
         api.compilePipelineFunctionResource({
           name: 'function1',
-          dataSource: 'myLambdaFunction',
+          dataSource: 'myFunction',
+          request: 'function1.request.vtl',
+          response: 'function1.response.vtl',
           description: 'Function1 Pipeline Resolver',
-          request: false,
-          response: false,
+          maxBatchSize: 200,
         }),
       ).toMatchInlineSnapshot(`
         Object {
@@ -595,14 +766,16 @@ describe('Resolvers', () => {
               },
               "DataSourceName": Object {
                 "Fn::GetAtt": Array [
-                  "GraphQlDsmyLambdaFunction",
+                  "GraphQlDsmyFunction",
                   "Name",
                 ],
               },
               "Description": "Function1 Pipeline Resolver",
               "FunctionVersion": "2018-05-29",
-              "MaxBatchSize": undefined,
+              "MaxBatchSize": 200,
               "Name": "function1",
+              "RequestMappingTemplate": "Content of function1.request.vtl",
+              "ResponseMappingTemplate": "Content of function1.response.vtl",
             },
             "Type": "AWS::AppSync::FunctionConfiguration",
           },
@@ -622,8 +795,6 @@ describe('Resolvers', () => {
           name: 'function1',
           dataSource: 'myLambdaFunction',
           description: 'Function1 Pipeline Resolver',
-          request: false,
-          response: false,
         });
       }).toThrowErrorMatchingInlineSnapshot(
         `"Pipeline Function 'function1' references unknown DataSource 'myLambdaFunction'"`,
@@ -681,8 +852,6 @@ describe('Resolvers', () => {
               "FieldName": "user",
               "Kind": "UNIT",
               "MaxBatchSize": undefined,
-              "RequestMappingTemplate": "Content of path/to/mappingTemplates/Query.user.request.vtl",
-              "ResponseMappingTemplate": "Content of path/to/mappingTemplates/Query.user.response.vtl",
               "TypeName": "Query",
             },
             "Type": "AWS::AppSync::Resolver",
@@ -747,8 +916,6 @@ describe('Resolvers', () => {
               "FieldName": "user",
               "Kind": "UNIT",
               "MaxBatchSize": undefined,
-              "RequestMappingTemplate": "Content of path/to/mappingTemplates/Query.user.request.vtl",
-              "ResponseMappingTemplate": "Content of path/to/mappingTemplates/Query.user.response.vtl",
               "TypeName": "Query",
             },
             "Type": "AWS::AppSync::Resolver",
diff --git a/src/__tests__/validation/__snapshots__/pipelineFunctions.test.ts.snap b/src/__tests__/validation/__snapshots__/pipelineFunctions.test.ts.snap
index 5a9c6ec..fe57756 100644
--- a/src/__tests__/validation/__snapshots__/pipelineFunctions.test.ts.snap
+++ b/src/__tests__/validation/__snapshots__/pipelineFunctions.test.ts.snap
@@ -3,8 +3,8 @@
 exports[`Basic Invalid should validate: Invalid 1`] = `
 "/pipelineFunctions/function1/dataSource: must be a string or data source definition
 /pipelineFunctions/function1/description: must be string
-/pipelineFunctions/function1/request: must be a string or false
-/pipelineFunctions/function1/response: must be a string or false
+/pipelineFunctions/function1/request: must be string
+/pipelineFunctions/function1/response: must be string
 /pipelineFunctions/function1/maxBatchSize: must be <= 2000
 /pipelineFunctions: contains invalid pipeline function definitions"
 `;
diff --git a/src/__tests__/validation/__snapshots__/resolvers.test.ts.snap b/src/__tests__/validation/__snapshots__/resolvers.test.ts.snap
index 71e3578..21d9774 100644
--- a/src/__tests__/validation/__snapshots__/resolvers.test.ts.snap
+++ b/src/__tests__/validation/__snapshots__/resolvers.test.ts.snap
@@ -1,23 +1,23 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`Basic Invalid should validate: Invalid 1`] = `
-"/resolvers/myResolver/kind: must be \\"UNIT\\" or \\"PIPELINE\\"
+"/resolvers/myResolver/functions: must be array
+/resolvers/myResolver/kind: must be \\"UNIT\\" or \\"PIPELINE\\"
 /resolvers/myResolver/type: must be string
 /resolvers/myResolver/field: must be string
 /resolvers/myResolver/maxBatchSize: must be <= 2000
-/resolvers/myResolver/dataSource: must be a string or data source definition
-/resolvers/myResolver/request: must be a string or false
-/resolvers/myResolver/response: must be a string or false
+/resolvers/myResolver/request: must be string
+/resolvers/myResolver/response: must be string
 /resolvers: contains invalid resolver definitions"
 `;
 
-exports[`Basic Invalid should validate: Invalid embedded datasource 1`] = `
-"/resolvers/Query.getUser/dataSource/config: must specify functionName, functionArn or function (all exclusives)
+exports[`Basic Invalid should validate: Invalid datasource 1`] = `
+"/resolvers/Query.getUser: must be object
 /resolvers: contains invalid resolver definitions"
 `;
 
-exports[`Basic Invalid should validate: Invalid inline datasource 1`] = `
-"/resolvers/Query.getUser: must be a string or an object
+exports[`Basic Invalid should validate: Invalid embedded datasource 1`] = `
+"/resolvers/Query.getUser/dataSource/config: must specify functionName, functionArn or function (all exclusives)
 /resolvers: contains invalid resolver definitions"
 `;
 
diff --git a/src/__tests__/validation/resolvers.test.ts b/src/__tests__/validation/resolvers.test.ts
index 6ef41da..4a9f6e3 100644
--- a/src/__tests__/validation/resolvers.test.ts
+++ b/src/__tests__/validation/resolvers.test.ts
@@ -14,12 +14,18 @@ describe('Basic', () => {
             },
             'Query.getPost': {
               kind: 'PIPELINE',
-              functions: ['function1', 'function2'],
+              functions: [
+                'function1',
+                {
+                  dataSource: 'function2',
+                  code: 'function2.js',
+                },
+              ],
             },
             'Query.getBlog': {
+              kind: 'UNIT',
               dataSource: 'myDs',
             },
-            'Query.getComment': 'myDs',
             getUsers: {
               type: 'Query',
               field: 'getUsers',
@@ -35,15 +41,27 @@ describe('Basic', () => {
             getPosts: {
               type: 'Query',
               field: 'getPosts',
-              kind: 'PIPELINE',
-              functions: ['function1', 'function2'],
+              functions: [
+                'function1',
+                {
+                  dataSource: {
+                    type: 'AWS_LAMBDA',
+                    config: {
+                      functionName: 'function3',
+                    },
+                  },
+                  code: 'function2.js',
+                },
+              ],
             },
             getBlogs: {
+              kind: 'UNIT',
               type: 'Query',
               field: 'getUsers',
               dataSource: 'myDs',
             },
             getComments: {
+              kind: 'UNIT',
               type: 'Query',
               field: 'getComments',
               dataSource: {
@@ -71,9 +89,9 @@ describe('Basic', () => {
                 functions: ['function1', 'function2'],
               },
               'Query.getBlog': {
+                kind: 'UNIT',
                 dataSource: 'myDs',
               },
-              'Query.getComment': 'myDs',
             },
             {
               getUsers: {
@@ -93,6 +111,7 @@ describe('Basic', () => {
                 functions: ['function1', 'function2'],
               },
               'Query.getComment': {
+                kind: 'UNIT',
                 dataSource: {
                   type: 'AWS_LAMBDA',
                   name: 'getComment',
@@ -122,7 +141,7 @@ describe('Basic', () => {
           resolvers: {
             myResolver: {
               kind: 'FOO',
-              dataSource: 999,
+              functions: 999,
               type: 123,
               field: 456,
               request: 123,
@@ -172,10 +191,10 @@ describe('Basic', () => {
         },
       },
       {
-        name: 'Invalid inline datasource',
+        name: 'Invalid datasource',
         config: {
           resolvers: {
-            'Query.getUser': 1234,
+            'Query.getUser': 'foo',
           },
         },
       },
@@ -184,6 +203,7 @@ describe('Basic', () => {
         config: {
           resolvers: {
             'Query.getUser': {
+              kind: 'UNIT',
               dataSource: {
                 type: 'AWS_LAMBDA',
                 config: {},