diff --git a/doc/general-config.md b/doc/general-config.md index e91972d3..eb9400ae 100644 --- a/doc/general-config.md +++ b/doc/general-config.md @@ -51,6 +51,7 @@ appSync: - `xrayEnabled`: Boolean. Enable or disable X-Ray tracing. - `visibility`: Optional. `GLOBAL` or `PRIVATE`. **Changing this value requires the replacement of the API.** - `tags`: A key-value pair for tagging this AppSync API +- `esbuild`: Custom esbuild options, or `false` See [Esbuild](#Esbuild) ## Schema @@ -186,3 +187,22 @@ appSync: - `excludeVerboseContent`: Boolean, Optional. Exclude or not verbose content (headers, response headers, context, and evaluated mapping templates), regardless of field logging level. Defaults to `false`. - `retentionInDays`: Optional. Number of days to retain the logs. Defaults to [`provider.logRetentionInDays`](https://www.serverless.com/framework/docs/providers/aws/guide/serverless.yml#general-function-settings). - `roleArn`: Optional. The role ARN to use for AppSync to write into CloudWatch. If not specified, a new role is created by default. + +## Esbuild + +By default, this plugin uses esbuild in order to bundle Javascript resolvers. TypeScript files are also transpiled into compatible JavaScript. This option allows you to pass custom options that must be passed to the esbuild command. + +⚠️ Use these options carefully. Some options are not compatible with AWS AppSync. For more details about using esbuild with AppSync, see the [official guidelines](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-reference-overview-js.html#additional-utilities) + +Set this option to `false` to disable esbuild completely. You code will be sent as-is to AppSync. + +Example: + +Override the target and disable sourcemap. + +```yml +appSync: + esbuild: + target: 'es2020', + sourcemap: false +``` diff --git a/doc/resolvers.md b/doc/resolvers.md index 2c56782f..cfd5f777 100644 --- a/doc/resolvers.md +++ b/doc/resolvers.md @@ -64,6 +64,76 @@ appSync: code: getUser.js ``` +## Bundling + +AppSync requires resolvers to be bundled in one single file. By default, this plugin bundles your code with [esbuild](https://esbuild.github.io/), using the given path as the entry point. + +This means that you can import external libraries and utilities. e.g. + +```js +import { Context, util } from '@aws-appsync/utils'; +import { generateUpdateExpressions, updateItem } from '../lib/helpers'; + +export function request(ctx) { + const { id, ...post } = ctx.args.post; + + const item = updateItem(post); + + return { + operation: 'UpdateItem', + key: { + id: util.dynamodb.toDynamoDB(id), + }, + update: generateUpdateExpressions(item), + condition: { + expression: 'attribute_exists(#id)', + expressionNames: { + '#id': 'id', + }, + }, + }; +} + +export function response(ctx: Context) { + return ctx.result; +} +``` + +For more information, also see the [esbuild option](./general-config.md#Esbuild). + +## TypeScript support + +You can write JS resolver in TypeScript. Resolver files with the `.ts` extension are automatically transpiled and bundled using esbuild. + +```yaml +resolvers: + Query.user: + kind: UNIT + dataSource: 'users' + code: 'getUser.ts' +``` + +```ts +// getUser.ts +import { Context, util } from '@aws-appsync/utils'; + +export function request(ctx: Context) { + const { + args: { id }, + } = ctx; + return { + operation: 'GetItem', + key: util.dynamodb.toMapValues({ id }), + }; +} + +export function response(ctx: Context) { + return ctx.result; +} +``` + +For more information, also see the [esbuild option](./general-config.md#Esbuild). + ## PIPELINE resolvers When `kind` is `PIPELINE`, you can specify the [pipeline function](pipeline-functions.md) names to use: diff --git a/package-lock.json b/package-lock.json index 621ee40c..4cc95f92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "ajv-merge-patch": "^5.0.1", "aws-sdk": "^2.1265.0", "chalk": "^4.1.2", + "esbuild": "^0.17.11", "globby": "^11.1.0", "graphql": "^16.6.0", "lodash": "^4.17.21", @@ -683,6 +684,336 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.11.tgz", + "integrity": "sha512-CdyX6sRVh1NzFCsf5vw3kULwlAhfy9wVt8SZlrhQ7eL2qBjGbFhRBWkkAzuZm9IIEOCKJw4DXA6R85g+qc8RDw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.11.tgz", + "integrity": "sha512-QnK4d/zhVTuV4/pRM4HUjcsbl43POALU2zvBynmrrqZt9LPcLA3x1fTZPBg2RRguBQnJcnU059yKr+bydkntjg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.11.tgz", + "integrity": "sha512-3PL3HKtsDIXGQcSCKtWD/dy+mgc4p2Tvo2qKgKHj9Yf+eniwFnuoQ0OUhlSfAEpKAFzF9N21Nwgnap6zy3L3MQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.11.tgz", + "integrity": "sha512-pJ950bNKgzhkGNO3Z9TeHzIFtEyC2GDQL3wxkMApDEghYx5Qers84UTNc1bAxWbRkuJOgmOha5V0WUeh8G+YGw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.11.tgz", + "integrity": "sha512-iB0dQkIHXyczK3BZtzw1tqegf0F0Ab5texX2TvMQjiJIWXAfM4FQl7D909YfXWnB92OQz4ivBYQ2RlxBJrMJOw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.11.tgz", + "integrity": "sha512-7EFzUADmI1jCHeDRGKgbnF5sDIceZsQGapoO6dmw7r/ZBEKX7CCDnIz8m9yEclzr7mFsd+DyasHzpjfJnmBB1Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.11.tgz", + "integrity": "sha512-iPgenptC8i8pdvkHQvXJFzc1eVMR7W2lBPrTE6GbhR54sLcF42mk3zBOjKPOodezzuAz/KSu8CPyFSjcBMkE9g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.11.tgz", + "integrity": "sha512-M9iK/d4lgZH0U5M1R2p2gqhPV/7JPJcRz+8O8GBKVgqndTzydQ7B2XGDbxtbvFkvIs53uXTobOhv+RyaqhUiMg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.11.tgz", + "integrity": "sha512-Qxth3gsWWGKz2/qG2d5DsW/57SeA2AmpSMhdg9TSB5Svn2KDob3qxfQSkdnWjSd42kqoxIPy3EJFs+6w1+6Qjg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.11.tgz", + "integrity": "sha512-dB1nGaVWtUlb/rRDHmuDQhfqazWE0LMro/AIbT2lWM3CDMHJNpLckH+gCddQyhhcLac2OYw69ikUMO34JLt3wA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.11.tgz", + "integrity": "sha512-aCWlq70Q7Nc9WDnormntGS1ar6ZFvUpqr8gXtO+HRejRYPweAFQN615PcgaSJkZjhHp61+MNLhzyVALSF2/Q0g==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.11.tgz", + "integrity": "sha512-cGeGNdQxqY8qJwlYH1BP6rjIIiEcrM05H7k3tR7WxOLmD1ZxRMd6/QIOWMb8mD2s2YJFNRuNQ+wjMhgEL2oCEw==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.11.tgz", + "integrity": "sha512-BdlziJQPW/bNe0E8eYsHB40mYOluS+jULPCjlWiHzDgr+ZBRXPtgMV1nkLEGdpjrwgmtkZHEGEPaKdS/8faLDA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.11.tgz", + "integrity": "sha512-MDLwQbtF+83oJCI1Cixn68Et/ME6gelmhssPebC40RdJaect+IM+l7o/CuG0ZlDs6tZTEIoxUe53H3GmMn8oMA==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.11.tgz", + "integrity": "sha512-4N5EMESvws0Ozr2J94VoUD8HIRi7X0uvUv4c0wpTHZyZY9qpaaN7THjosdiW56irQ4qnJ6Lsc+i+5zGWnyqWqQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.11.tgz", + "integrity": "sha512-rM/v8UlluxpytFSmVdbCe1yyKQd/e+FmIJE2oPJvbBo+D0XVWi1y/NQ4iTNx+436WmDHQBjVLrbnAQLQ6U7wlw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.11.tgz", + "integrity": "sha512-4WaAhuz5f91h3/g43VBGdto1Q+X7VEZfpcWGtOFXnggEuLvjV+cP6DyLRU15IjiU9fKLLk41OoJfBFN5DhPvag==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.11.tgz", + "integrity": "sha512-UBj135Nx4FpnvtE+C8TWGp98oUgBcmNmdYgl5ToKc0mBHxVVqVE7FUS5/ELMImOp205qDAittL6Ezhasc2Ev/w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.11.tgz", + "integrity": "sha512-1/gxTifDC9aXbV2xOfCbOceh5AlIidUrPsMpivgzo8P8zUtczlq1ncFpeN1ZyQJ9lVs2hILy1PG5KPp+w8QPPg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.11.tgz", + "integrity": "sha512-vtSfyx5yRdpiOW9yp6Ax0zyNOv9HjOAw8WaZg3dF5djEHKKm3UnoohftVvIJtRh0Ec7Hso0RIdTqZvPXJ7FdvQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.11.tgz", + "integrity": "sha512-GFPSLEGQr4wHFTiIUJQrnJKZhZjjq4Sphf+mM76nQR6WkQn73vm7IsacmBRPkALfpOCHsopSvLgqdd4iUW2mYw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.11.tgz", + "integrity": "sha512-N9vXqLP3eRL8BqSy8yn4Y98cZI2pZ8fyuHx6lKjiG2WABpT2l01TXdzq5Ma2ZUBzfB7tx5dXVhge8X9u0S70ZQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", @@ -4645,6 +4976,42 @@ "es6-symbol": "^3.1.1" } }, + "node_modules/esbuild": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.11.tgz", + "integrity": "sha512-pAMImyokbWDtnA/ufPxjQg0fYo2DDuzAlqwnDvbXqHLphe+m80eF++perYKVm8LeTuj2zUuFXC+xgSVxyoHUdg==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.11", + "@esbuild/android-arm64": "0.17.11", + "@esbuild/android-x64": "0.17.11", + "@esbuild/darwin-arm64": "0.17.11", + "@esbuild/darwin-x64": "0.17.11", + "@esbuild/freebsd-arm64": "0.17.11", + "@esbuild/freebsd-x64": "0.17.11", + "@esbuild/linux-arm": "0.17.11", + "@esbuild/linux-arm64": "0.17.11", + "@esbuild/linux-ia32": "0.17.11", + "@esbuild/linux-loong64": "0.17.11", + "@esbuild/linux-mips64el": "0.17.11", + "@esbuild/linux-ppc64": "0.17.11", + "@esbuild/linux-riscv64": "0.17.11", + "@esbuild/linux-s390x": "0.17.11", + "@esbuild/linux-x64": "0.17.11", + "@esbuild/netbsd-x64": "0.17.11", + "@esbuild/openbsd-x64": "0.17.11", + "@esbuild/sunos-x64": "0.17.11", + "@esbuild/win32-arm64": "0.17.11", + "@esbuild/win32-ia32": "0.17.11", + "@esbuild/win32-x64": "0.17.11" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -15918,6 +16285,138 @@ } } }, + "@esbuild/android-arm": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.11.tgz", + "integrity": "sha512-CdyX6sRVh1NzFCsf5vw3kULwlAhfy9wVt8SZlrhQ7eL2qBjGbFhRBWkkAzuZm9IIEOCKJw4DXA6R85g+qc8RDw==", + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.11.tgz", + "integrity": "sha512-QnK4d/zhVTuV4/pRM4HUjcsbl43POALU2zvBynmrrqZt9LPcLA3x1fTZPBg2RRguBQnJcnU059yKr+bydkntjg==", + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.11.tgz", + "integrity": "sha512-3PL3HKtsDIXGQcSCKtWD/dy+mgc4p2Tvo2qKgKHj9Yf+eniwFnuoQ0OUhlSfAEpKAFzF9N21Nwgnap6zy3L3MQ==", + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.11.tgz", + "integrity": "sha512-pJ950bNKgzhkGNO3Z9TeHzIFtEyC2GDQL3wxkMApDEghYx5Qers84UTNc1bAxWbRkuJOgmOha5V0WUeh8G+YGw==", + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.11.tgz", + "integrity": "sha512-iB0dQkIHXyczK3BZtzw1tqegf0F0Ab5texX2TvMQjiJIWXAfM4FQl7D909YfXWnB92OQz4ivBYQ2RlxBJrMJOw==", + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.11.tgz", + "integrity": "sha512-7EFzUADmI1jCHeDRGKgbnF5sDIceZsQGapoO6dmw7r/ZBEKX7CCDnIz8m9yEclzr7mFsd+DyasHzpjfJnmBB1Q==", + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.11.tgz", + "integrity": "sha512-iPgenptC8i8pdvkHQvXJFzc1eVMR7W2lBPrTE6GbhR54sLcF42mk3zBOjKPOodezzuAz/KSu8CPyFSjcBMkE9g==", + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.11.tgz", + "integrity": "sha512-M9iK/d4lgZH0U5M1R2p2gqhPV/7JPJcRz+8O8GBKVgqndTzydQ7B2XGDbxtbvFkvIs53uXTobOhv+RyaqhUiMg==", + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.11.tgz", + "integrity": "sha512-Qxth3gsWWGKz2/qG2d5DsW/57SeA2AmpSMhdg9TSB5Svn2KDob3qxfQSkdnWjSd42kqoxIPy3EJFs+6w1+6Qjg==", + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.11.tgz", + "integrity": "sha512-dB1nGaVWtUlb/rRDHmuDQhfqazWE0LMro/AIbT2lWM3CDMHJNpLckH+gCddQyhhcLac2OYw69ikUMO34JLt3wA==", + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.11.tgz", + "integrity": "sha512-aCWlq70Q7Nc9WDnormntGS1ar6ZFvUpqr8gXtO+HRejRYPweAFQN615PcgaSJkZjhHp61+MNLhzyVALSF2/Q0g==", + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.11.tgz", + "integrity": "sha512-cGeGNdQxqY8qJwlYH1BP6rjIIiEcrM05H7k3tR7WxOLmD1ZxRMd6/QIOWMb8mD2s2YJFNRuNQ+wjMhgEL2oCEw==", + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.11.tgz", + "integrity": "sha512-BdlziJQPW/bNe0E8eYsHB40mYOluS+jULPCjlWiHzDgr+ZBRXPtgMV1nkLEGdpjrwgmtkZHEGEPaKdS/8faLDA==", + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.11.tgz", + "integrity": "sha512-MDLwQbtF+83oJCI1Cixn68Et/ME6gelmhssPebC40RdJaect+IM+l7o/CuG0ZlDs6tZTEIoxUe53H3GmMn8oMA==", + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.11.tgz", + "integrity": "sha512-4N5EMESvws0Ozr2J94VoUD8HIRi7X0uvUv4c0wpTHZyZY9qpaaN7THjosdiW56irQ4qnJ6Lsc+i+5zGWnyqWqQ==", + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.11.tgz", + "integrity": "sha512-rM/v8UlluxpytFSmVdbCe1yyKQd/e+FmIJE2oPJvbBo+D0XVWi1y/NQ4iTNx+436WmDHQBjVLrbnAQLQ6U7wlw==", + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.11.tgz", + "integrity": "sha512-4WaAhuz5f91h3/g43VBGdto1Q+X7VEZfpcWGtOFXnggEuLvjV+cP6DyLRU15IjiU9fKLLk41OoJfBFN5DhPvag==", + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.11.tgz", + "integrity": "sha512-UBj135Nx4FpnvtE+C8TWGp98oUgBcmNmdYgl5ToKc0mBHxVVqVE7FUS5/ELMImOp205qDAittL6Ezhasc2Ev/w==", + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.11.tgz", + "integrity": "sha512-1/gxTifDC9aXbV2xOfCbOceh5AlIidUrPsMpivgzo8P8zUtczlq1ncFpeN1ZyQJ9lVs2hILy1PG5KPp+w8QPPg==", + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.11.tgz", + "integrity": "sha512-vtSfyx5yRdpiOW9yp6Ax0zyNOv9HjOAw8WaZg3dF5djEHKKm3UnoohftVvIJtRh0Ec7Hso0RIdTqZvPXJ7FdvQ==", + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.11.tgz", + "integrity": "sha512-GFPSLEGQr4wHFTiIUJQrnJKZhZjjq4Sphf+mM76nQR6WkQn73vm7IsacmBRPkALfpOCHsopSvLgqdd4iUW2mYw==", + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.11.tgz", + "integrity": "sha512-N9vXqLP3eRL8BqSy8yn4Y98cZI2pZ8fyuHx6lKjiG2WABpT2l01TXdzq5Ma2ZUBzfB7tx5dXVhge8X9u0S70ZQ==", + "optional": true + }, "@eslint/eslintrc": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", @@ -19045,6 +19544,35 @@ "es6-symbol": "^3.1.1" } }, + "esbuild": { + "version": "0.17.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.11.tgz", + "integrity": "sha512-pAMImyokbWDtnA/ufPxjQg0fYo2DDuzAlqwnDvbXqHLphe+m80eF++perYKVm8LeTuj2zUuFXC+xgSVxyoHUdg==", + "requires": { + "@esbuild/android-arm": "0.17.11", + "@esbuild/android-arm64": "0.17.11", + "@esbuild/android-x64": "0.17.11", + "@esbuild/darwin-arm64": "0.17.11", + "@esbuild/darwin-x64": "0.17.11", + "@esbuild/freebsd-arm64": "0.17.11", + "@esbuild/freebsd-x64": "0.17.11", + "@esbuild/linux-arm": "0.17.11", + "@esbuild/linux-arm64": "0.17.11", + "@esbuild/linux-ia32": "0.17.11", + "@esbuild/linux-loong64": "0.17.11", + "@esbuild/linux-mips64el": "0.17.11", + "@esbuild/linux-ppc64": "0.17.11", + "@esbuild/linux-riscv64": "0.17.11", + "@esbuild/linux-s390x": "0.17.11", + "@esbuild/linux-x64": "0.17.11", + "@esbuild/netbsd-x64": "0.17.11", + "@esbuild/openbsd-x64": "0.17.11", + "@esbuild/sunos-x64": "0.17.11", + "@esbuild/win32-arm64": "0.17.11", + "@esbuild/win32-ia32": "0.17.11", + "@esbuild/win32-x64": "0.17.11" + } + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", diff --git a/package.json b/package.json index b86c2747..c1eb6f6f 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.0.0-development", "description": "AWS AppSync support for the Serverless Framework", "main": "lib/index.js", + "types": "lib/types/index.d.ts", "repository": "https://github.com/sid88in/serverless-appsync-plugin", "license": "MIT", "files": [ @@ -45,6 +46,7 @@ "ajv-merge-patch": "^5.0.1", "aws-sdk": "^2.1265.0", "chalk": "^4.1.2", + "esbuild": "^0.17.11", "globby": "^11.1.0", "graphql": "^16.6.0", "lodash": "^4.17.21", diff --git a/src/__tests__/basicConfig.ts b/src/__tests__/basicConfig.ts index 9c30b126..7d8692ff 100644 --- a/src/__tests__/basicConfig.ts +++ b/src/__tests__/basicConfig.ts @@ -1,6 +1,6 @@ -import { AppSyncConfigInput } from '../getAppSyncConfig'; +import { AppSyncConfig } from '../types'; -export const basicConfig: AppSyncConfigInput = { +export const basicConfig: AppSyncConfig = { name: 'My Api', authentication: { type: 'API_KEY', diff --git a/src/__tests__/getAppSyncConfig.test.ts b/src/__tests__/getAppSyncConfig.test.ts index d68400f7..db939541 100644 --- a/src/__tests__/getAppSyncConfig.test.ts +++ b/src/__tests__/getAppSyncConfig.test.ts @@ -1,6 +1,7 @@ import { pick } from 'lodash'; -import { getAppSyncConfig, ResolverConfigInput } from '../getAppSyncConfig'; +import { getAppSyncConfig } from '../getAppSyncConfig'; import { basicConfig } from './basicConfig'; +import { ResolverConfig } from '../types'; test('returns basic config', async () => { expect(getAppSyncConfig(basicConfig)).toMatchSnapshot(); @@ -228,7 +229,7 @@ describe('Resolvers', () => { field: 'getUsers', }, }, - ] as Record[], + ] as Record[], }); expect(config.resolvers).toMatchSnapshot(); }); diff --git a/src/__tests__/resolvers.test.ts b/src/__tests__/resolvers.test.ts index 36ac6634..e1dff5b4 100644 --- a/src/__tests__/resolvers.test.ts +++ b/src/__tests__/resolvers.test.ts @@ -1,24 +1,122 @@ +import * as esbuild from 'esbuild'; import fs from 'fs'; import { Api } from '../resources/Api'; import * as given from './given'; const plugin = given.plugin(); +jest.mock('esbuild', () => ({ + buildSync: jest.fn().mockImplementation((config) => { + return { + errors: [], + warnings: [], + metafile: undefined, + mangleCache: undefined, + outputFiles: [ + { + path: 'path/to/file', + contents: Uint8Array.from([]), + text: `Bundled content of ${`${config.entryPoints?.[0]}`.replace( + /\\/g, + '/', + )}`, + }, + ], + }; + }), +})); + describe('Resolvers', () => { let mock: jest.SpyInstance; - let mockEists: jest.SpyInstance; + let mockExists: jest.SpyInstance; + let mockEsbuild: jest.SpyInstance; beforeEach(() => { mock = jest .spyOn(fs, 'readFileSync') .mockImplementation( (path) => `Content of ${`${path}`.replace(/\\/g, '/')}`, ); - mockEists = jest.spyOn(fs, 'existsSync').mockReturnValue(true); + mockExists = jest.spyOn(fs, 'existsSync').mockReturnValue(true); + mockEsbuild = jest + .spyOn(esbuild, 'buildSync') + .mockImplementation((config) => { + return { + errors: [], + warnings: [], + metafile: undefined, + mangleCache: undefined, + outputFiles: [ + { + path: 'path/to/file', + contents: Uint8Array.from([]), + text: `Bundled content of ${`${config.entryPoints?.[0]}`.replace( + /\\/g, + '/', + )}`, + }, + ], + }; + }); }); afterEach(() => { mock.mockRestore(); - mockEists.mockRestore(); + mockExists.mockRestore(); + mockEsbuild.mockRestore(); + }); + + describe('esbuild', () => { + it('should skip esbuild when disabled', () => { + const api = new Api( + given.appSyncConfig({ + esbuild: false, + dataSources: { + myTable: { + name: 'myTable', + type: 'AMAZON_DYNAMODB', + config: { tableName: 'data' }, + }, + }, + }), + plugin, + ); + expect( + api.compilePipelineFunctionResource({ + dataSource: 'myTable', + code: 'path/to/my-resolver.js', + name: 'my-function', + }), + ).toMatchInlineSnapshot(` + Object { + "GraphQlFunctionConfigurationmyfunction": Object { + "Properties": Object { + "ApiId": Object { + "Fn::GetAtt": Array [ + "GraphQlApi", + "ApiId", + ], + }, + "Code": "Content of path/to/my-resolver.js", + "DataSourceName": Object { + "Fn::GetAtt": Array [ + "GraphQlDsmyTable", + "Name", + ], + }, + "Description": undefined, + "FunctionVersion": "2018-05-29", + "MaxBatchSize": undefined, + "Name": "my-function", + "Runtime": Object { + "Name": "APPSYNC_JS", + "RuntimeVersion": "1.0.0", + }, + }, + "Type": "AWS::AppSync::FunctionConfiguration", + }, + } + `); + }); }); describe('Unit Resolvers', () => { @@ -98,37 +196,37 @@ describe('Resolvers', () => { 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", - "DataSourceName": Object { - "Fn::GetAtt": Array [ - "GraphQlDsmyTable", - "Name", - ], - }, - "FieldName": "user", - "Kind": "UNIT", - "MaxBatchSize": undefined, - "Runtime": Object { - "Name": "APPSYNC_JS", - "RuntimeVersion": "1.0.0", - }, - "TypeName": "Query", - }, - "Type": "AWS::AppSync::Resolver", - }, - } + Object { + "GraphQlResolverQueryuser": Object { + "DependsOn": Array [ + "GraphQlSchema", + ], + "Properties": Object { + "ApiId": Object { + "Fn::GetAtt": Array [ + "GraphQlApi", + "ApiId", + ], + }, + "Code": "Bundled content of resolvers/getUserFunction.js", + "DataSourceName": Object { + "Fn::GetAtt": Array [ + "GraphQlDsmyTable", + "Name", + ], + }, + "FieldName": "user", + "Kind": "UNIT", + "MaxBatchSize": undefined, + "Runtime": Object { + "Name": "APPSYNC_JS", + "RuntimeVersion": "1.0.0", + }, + "TypeName": "Query", + }, + "Type": "AWS::AppSync::Resolver", + }, + } `); }); @@ -331,7 +429,7 @@ describe('Resolvers', () => { describe('Pipeline Resovlers', () => { it('should generate JS Resources with default empty resolver', () => { - mockEists.mockReturnValue(false); + mockExists.mockReturnValue(false); const api = new Api( given.appSyncConfig({ dataSources: { @@ -514,7 +612,7 @@ describe('Resolvers', () => { "ApiId", ], }, - "Code": "Content of resolvers/getUserFunction.js", + "Code": "Bundled content of resolvers/getUserFunction.js", "FieldName": "user", "Kind": "PIPELINE", "PipelineConfig": Object { @@ -602,7 +700,7 @@ describe('Resolvers', () => { "ApiId", ], }, - "Code": "Content of funciton1.js", + "Code": "Bundled content of funciton1.js", "DataSourceName": Object { "Fn::GetAtt": Array [ "GraphQlDsmyTable", diff --git a/src/__tests__/validation/__snapshots__/base.test.ts.snap b/src/__tests__/validation/__snapshots__/base.test.ts.snap index c0639dc0..79de3130 100644 --- a/src/__tests__/validation/__snapshots__/base.test.ts.snap +++ b/src/__tests__/validation/__snapshots__/base.test.ts.snap @@ -56,5 +56,6 @@ exports[`Valdiation should validate 1`] = ` : must have required property 'authentication' /unknownPorp: invalid (unknown) property /xrayEnabled: must be boolean -/visibility: must be \\"GLOBAL\\" or \\"PRIVATE\\"" +/visibility: must be \\"GLOBAL\\" or \\"PRIVATE\\" +/esbuild: must be an esbuild config object or false" `; diff --git a/src/__tests__/validation/auth.test.ts b/src/__tests__/validation/auth.test.ts index 6cc4cdff..28490656 100644 --- a/src/__tests__/validation/auth.test.ts +++ b/src/__tests__/validation/auth.test.ts @@ -1,4 +1,4 @@ -import { AppSyncConfigInput } from '../../getAppSyncConfig'; +import { AppSyncConfig } from '../../types'; import { validateConfig } from '../../validation'; import { basicConfig } from '../basicConfig'; @@ -12,7 +12,7 @@ describe('Valdiation', () => { authentication: { type: 'API_KEY', }, - } as AppSyncConfigInput, + } as AppSyncConfig, }, { name: 'Cognito', @@ -27,7 +27,7 @@ describe('Valdiation', () => { appIdClientRegex: '.*', }, }, - } as AppSyncConfigInput, + } as AppSyncConfig, }, { name: 'Cognito with Refs', @@ -44,7 +44,7 @@ describe('Valdiation', () => { }, }, }, - } as AppSyncConfigInput, + } as AppSyncConfig, }, { name: 'OIDC', @@ -59,7 +59,7 @@ describe('Valdiation', () => { authTTL: 3600, }, }, - } as AppSyncConfigInput, + } as AppSyncConfig, }, { name: 'OIDC without a clientId', @@ -73,7 +73,7 @@ describe('Valdiation', () => { authTTL: 3600, }, }, - } as AppSyncConfigInput, + } as AppSyncConfig, }, { name: 'IAM', @@ -82,7 +82,7 @@ describe('Valdiation', () => { authentication: { type: 'AWS_IAM', }, - } as AppSyncConfigInput, + } as AppSyncConfig, }, { name: 'Lambda with functionName', @@ -96,7 +96,7 @@ describe('Valdiation', () => { authorizerResultTtlInSeconds: 600, }, }, - } as AppSyncConfigInput, + } as AppSyncConfig, }, { name: 'Lambda with functionArn', @@ -108,7 +108,7 @@ describe('Valdiation', () => { functionArn: 'arn:aws:lambda:...', }, }, - } as AppSyncConfigInput, + } as AppSyncConfig, }, ]; diff --git a/src/__tests__/validation/base.test.ts b/src/__tests__/validation/base.test.ts index bd9b7ee6..4f899d21 100644 --- a/src/__tests__/validation/base.test.ts +++ b/src/__tests__/validation/base.test.ts @@ -1,4 +1,4 @@ -import { AppSyncConfigInput } from '../../getAppSyncConfig'; +import { AppSyncConfig } from '../../types'; import { validateConfig } from '../../validation'; import { basicConfig } from '../basicConfig'; @@ -12,6 +12,11 @@ describe('Valdiation', () => { tags: { foo: 'bar', }, + esbuild: { + target: 'es2020', + sourcemap: false, + treeShaking: false, + }, }), ).toBe(true); @@ -20,6 +25,7 @@ describe('Valdiation', () => { visibility: 'FOO', xrayEnabled: 'BAR', unknownPorp: 'foo', + esbuild: 'bad', }); }).toThrowErrorMatchingSnapshot(); }); @@ -34,7 +40,7 @@ describe('Valdiation', () => { logging: { level: 'ALL', }, - } as AppSyncConfigInput, + } as AppSyncConfig, }, { name: 'Full', @@ -46,7 +52,7 @@ describe('Valdiation', () => { excludeVerboseContent: true, loggingRoleArn: { Ref: 'MyLogGorupArn' }, }, - } as AppSyncConfigInput, + } as AppSyncConfig, }, ]; @@ -94,7 +100,7 @@ describe('Valdiation', () => { waf: { rules: [], }, - } as AppSyncConfigInput, + } as AppSyncConfig, }, { name: 'Full', @@ -164,7 +170,7 @@ describe('Valdiation', () => { }, ], }, - } as AppSyncConfigInput, + } as AppSyncConfig, }, { name: 'Using arn', @@ -260,7 +266,7 @@ describe('Valdiation', () => { name: 'api.example.com', certificateArn: 'arn:aws:', }, - } as AppSyncConfigInput, + } as AppSyncConfig, }, { name: 'Full', @@ -274,7 +280,7 @@ describe('Valdiation', () => { hostedZoneName: 'example.com.', route53: true, }, - } as AppSyncConfigInput, + } as AppSyncConfig, }, { name: 'useCloudFormation: false, missing certificateArn', @@ -284,7 +290,7 @@ describe('Valdiation', () => { name: 'api.example.com', useCloudFormation: false, }, - } as AppSyncConfigInput, + } as AppSyncConfig, }, ]; @@ -364,7 +370,7 @@ describe('Valdiation', () => { caching: { behavior: 'PER_RESOLVER_CACHING', }, - } as AppSyncConfigInput, + } as AppSyncConfig, }, { name: 'Full', @@ -378,7 +384,7 @@ describe('Valdiation', () => { atRestEncryption: true, transitEncryption: true, }, - } as AppSyncConfigInput, + } as AppSyncConfig, }, ]; diff --git a/src/__tests__/validation/pipelineFunctions.test.ts b/src/__tests__/validation/pipelineFunctions.test.ts index 7341e10c..c372a6f7 100644 --- a/src/__tests__/validation/pipelineFunctions.test.ts +++ b/src/__tests__/validation/pipelineFunctions.test.ts @@ -1,4 +1,4 @@ -import { FunctionConfigInput } from '../../getAppSyncConfig'; +import { PipelineFunctionConfig } from '../../types'; import { validateConfig } from '../../validation'; import { basicConfig } from '../basicConfig'; @@ -19,7 +19,7 @@ describe('Basic', () => { request: 'request.vtl', response: 'response.vtl', }, - } as Record, + } as Record, }, }, { @@ -51,7 +51,7 @@ describe('Basic', () => { }, function4: 'ds1', }, - ] as Record[], + ] as Record[], }, }, ]; diff --git a/src/getAppSyncConfig.ts b/src/getAppSyncConfig.ts index bdfc2fad..a9427728 100644 --- a/src/getAppSyncConfig.ts +++ b/src/getAppSyncConfig.ts @@ -1,53 +1,13 @@ +import { AppSyncConfig } from './types'; import { ApiKeyConfig, - AppSyncConfig, + AppSyncConfig as PluginAppSyncConfig, DataSourceConfig, PipelineFunctionConfig, ResolverConfig, } from './types/plugin'; -import { A, O } from 'ts-toolbelt'; import { forEach, merge } from 'lodash'; -/* Completely replaces keys of O1 with those of O */ -type Replace = O.Merge< - O, - O.Omit> ->; - -export type DataSourceConfigInput = O.Omit; - -export type ResolverConfigInput = O.Update< - O.Update< - O.Optional, - 'dataSource', - string | DataSourceConfigInput - >, - 'functions', - (string | FunctionConfigInput)[] ->; - -export type FunctionConfigInput = Replace< - { dataSource: string | DataSourceConfigInput }, - O.Omit ->; - -export type AppSyncConfigInput = Replace< - { - schema?: string | string[]; - apiKeys?: (ApiKeyConfig | string)[]; - resolvers?: - | Record[] - | Record; - pipelineFunctions?: - | Record[] - | Record; - dataSources: - | Record[] - | Record; - }, - O.Optional ->; - const flattenMaps = ( input?: Record | Record[], ): Record => { @@ -74,7 +34,9 @@ const toResourceName = (name: string) => { return name.replace(/[^a-z_]/i, '_'); }; -export const getAppSyncConfig = (config: AppSyncConfigInput): AppSyncConfig => { +export const getAppSyncConfig = ( + config: AppSyncConfig, +): PluginAppSyncConfig => { const schema = Array.isArray(config.schema) ? config.schema : [config.schema || 'schema.graphql']; @@ -129,6 +91,7 @@ export const getAppSyncConfig = (config: AppSyncConfigInput): AppSyncConfig => { if (typeof f === 'string') { return f; } + const name = `${toResourceName(typeAndField)}_${index}`; pipelineFunctions[name] = { ...f, diff --git a/src/index.ts b/src/index.ts index 6d93c2ea..89e15d12 100644 --- a/src/index.ts +++ b/src/index.ts @@ -326,6 +326,11 @@ class ServerlessAppsyncPlugin { 'before:appsync:domain:delete-record:run': async () => this.initDomainCommand(), 'appsync:domain:delete-record:run': async () => this.deleteRecord(), + finalize: () => { + writeText( + '\nLooking for a better AppSync development experience? Have you tried GraphBolt? https://graphbolt.dev', + ); + }, }; // These hooks need the config to be loaded and diff --git a/src/resources/JsResolver.ts b/src/resources/JsResolver.ts index 3e1bca8d..0cc78480 100644 --- a/src/resources/JsResolver.ts +++ b/src/resources/JsResolver.ts @@ -2,6 +2,7 @@ import { IntrinsicFunction } from '../types/cloudFormation'; import fs from 'fs'; import { Substitutions } from '../types/plugin'; import { Api } from './Api'; +import { buildSync } from 'esbuild'; type JsResolverConfig = { path: string; @@ -18,8 +19,47 @@ export class JsResolver { ); } - const requestTemplateContent = fs.readFileSync(this.config.path, 'utf8'); - return this.processTemplateSubstitutions(requestTemplateContent); + return this.processTemplateSubstitutions(this.getResolverContent()); + } + + getResolverContent(): string { + if (this.api.config.esbuild === false) { + return fs.readFileSync(this.config.path, 'utf8'); + } + + // process with esbuild + // this will: + // - Bundle the code into one file if necessary + // - Transpile typescript to javascript if necessary + const buildResult = buildSync({ + target: 'esnext', + sourcemap: 'inline', + sourcesContent: false, + treeShaking: true, + // custom config overrides + ...this.api.config.esbuild, + // These options are required and can't be changed + platform: 'node', + format: 'esm', + entryPoints: [this.config.path], + bundle: true, + write: false, + external: ['@aws-appsync/utils'], + }); + + if (buildResult.errors.length > 0) { + throw new this.api.plugin.serverless.classes.Error( + `Failed to compile resolver handler file '${this.config.path}': ${buildResult.errors[0].text}`, + ); + } + + if (buildResult.outputFiles.length === 0) { + throw new this.api.plugin.serverless.classes.Error( + `Failed to compile resolver handler file '${this.config.path}': No output files`, + ); + } + + return buildResult.outputFiles[0].text; } processTemplateSubstitutions(template: string): string | IntrinsicFunction { diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 00000000..f4543149 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,50 @@ +import { A, O } from 'ts-toolbelt'; +import { + DataSourceConfig as InternalDataSourceConfig, + ResolverConfig as InternalResolverConfig, + PipelineFunctionConfig as InternalPipelineFunctionConfig, + ApiKeyConfig, + AppSyncConfig as InternalAppSyncConfig, +} from './plugin'; + +/* Completely replaces keys of O1 with those of O */ +type Replace = O.Merge< + O, + O.Omit> +>; + +export * from './plugin'; + +export type DataSourceConfig = O.Omit; + +export type PipelineFunctionConfig = Replace< + { dataSource: string | DataSourceConfig }, + O.Omit +>; + +export type ResolverConfig = O.Update< + O.Update< + O.Optional, + 'dataSource', + string | DataSourceConfig + >, + 'functions', + (string | PipelineFunctionConfig)[] +>; + +export type AppSyncConfig = Replace< + { + schema?: string | string[]; + apiKeys?: (ApiKeyConfig | string)[]; + resolvers?: + | Record[] + | Record; + pipelineFunctions?: + | Record[] + | Record; + dataSources: + | Record[] + | Record; + }, + O.Optional +>; diff --git a/src/types/plugin.ts b/src/types/plugin.ts index f29fbed1..8e5538d2 100644 --- a/src/types/plugin.ts +++ b/src/types/plugin.ts @@ -1,4 +1,5 @@ import { CfnWafRuleStatement, IntrinsicFunction } from './cloudFormation'; +import { BuildOptions } from 'esbuild'; export type AppSyncConfig = { name: string; @@ -17,6 +18,7 @@ export type AppSyncConfig = { waf?: WafConfig; tags?: Record; visibility?: 'GLOBAL' | 'PRIVATE'; + esbuild?: BuildOptions | false; }; export type IamStatement = { diff --git a/src/types/serverless.d.ts b/src/types/serverless.d.ts index 1038c06b..3d298c52 100644 --- a/src/types/serverless.d.ts +++ b/src/types/serverless.d.ts @@ -24,7 +24,7 @@ declare module '@serverless/utils/log' { } declare module 'serverless/lib/Serverless' { - import { AppSyncConfigInput } from 'plugin'; + import { AppSyncConfig } from 'plugin'; import Provider from 'serverless/lib/plugins/aws/provider.js'; import type { AWS } from '@serverless/typescript'; @@ -59,7 +59,7 @@ declare module 'serverless/lib/Serverless' { options: Record; }; configurationInput: AWS & { - appSync: AppSyncConfigInput; + appSync: AppSyncConfig; }; service: AWS & { setFunctionNames(rawOptions: Record): void; diff --git a/src/validation.ts b/src/validation.ts index a1870492..bc2887e2 100644 --- a/src/validation.ts +++ b/src/validation.ts @@ -838,6 +838,15 @@ export const appSyncSchema = { ], errorMessage: 'contains invalid pipeline function definitions', }, + esbuild: { + oneOf: [ + { + type: 'object', + }, + { const: false }, + ], + errorMessage: 'must be an esbuild config object or false', + }, }, required: ['name', 'authentication'], additionalProperties: { diff --git a/tsconfig.json b/tsconfig.json index 2077e072..84c86304 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,8 @@ "resolveJsonModule": true, "skipLibCheck": true, "outDir": "lib", - "noImplicitAny": false + "noImplicitAny": false, + "declaration": true }, "include": ["src/**/*.ts"], "exclude": ["node_modules", "src/__tests__"]