@ttoss/appsync-api 0.17.11 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -98,3 +98,38 @@ export const handler = createAppSyncResolverHandler({
98
98
  middlewares: [permissions],
99
99
  });
100
100
  ```
101
+
102
+ ### Custom domain name
103
+
104
+ You can add a custom domain name to your API using the `customDomain` option.
105
+
106
+ ```ts
107
+ import { createApiTemplate } from '@ttoss/appsync-api';
108
+
109
+ export const handler = createApiTemplate({
110
+ schemaComposer,
111
+ customDomain: {
112
+ domainName: 'api.example.com', // required
113
+ certificateArn: {
114
+ 'Fn::ImportValue': 'AppSyncDomainCertificateArn',
115
+ }, // required
116
+ },
117
+ });
118
+ ```
119
+
120
+ If your domain is on Route53, you can use the option `customDomain.hostedZoneName` to create the required DNS records.
121
+
122
+ ```ts
123
+ import { createApiTemplate } from '@ttoss/appsync-api';
124
+
125
+ export const template = createApiTemplate({
126
+ schemaComposer,
127
+ customDomain: {
128
+ domainName: 'api.example.com', // required
129
+ certificateArn: {
130
+ 'Fn::ImportValue': 'AppSyncDomainCertificateArn',
131
+ }, // required
132
+ hostedZoneName: 'example.com.', // optional
133
+ },
134
+ });
135
+ ```
package/dist/esm/index.js CHANGED
@@ -2,128 +2,6 @@
2
2
 
3
3
  // src/createApiTemplate.ts
4
4
  import { graphql } from "@ttoss/graphql-api";
5
-
6
- // ../../node_modules/.pnpm/tslib@2.6.2/node_modules/tslib/tslib.es6.mjs
7
- var __assign = function () {
8
- __assign = Object.assign || function __assign2(t) {
9
- for (var s, i = 1, n = arguments.length; i < n; i++) {
10
- s = arguments[i];
11
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
12
- }
13
- return t;
14
- };
15
- return __assign.apply(this, arguments);
16
- };
17
-
18
- // ../../node_modules/.pnpm/lower-case@2.0.2/node_modules/lower-case/dist.es2015/index.js
19
- function lowerCase(str) {
20
- return str.toLowerCase();
21
- }
22
-
23
- // ../../node_modules/.pnpm/no-case@3.0.4/node_modules/no-case/dist.es2015/index.js
24
- var DEFAULT_SPLIT_REGEXP = [/([a-z0-9])([A-Z])/g, /([A-Z])([A-Z][a-z])/g];
25
- var DEFAULT_STRIP_REGEXP = /[^A-Z0-9]+/gi;
26
- function noCase(input, options) {
27
- if (options === void 0) {
28
- options = {};
29
- }
30
- var _a = options.splitRegexp,
31
- splitRegexp = _a === void 0 ? DEFAULT_SPLIT_REGEXP : _a,
32
- _b = options.stripRegexp,
33
- stripRegexp = _b === void 0 ? DEFAULT_STRIP_REGEXP : _b,
34
- _c = options.transform,
35
- transform = _c === void 0 ? lowerCase : _c,
36
- _d = options.delimiter,
37
- delimiter = _d === void 0 ? " " : _d;
38
- var result = replace(replace(input, splitRegexp, "$1\0$2"), stripRegexp, "\0");
39
- var start = 0;
40
- var end = result.length;
41
- while (result.charAt(start) === "\0") start++;
42
- while (result.charAt(end - 1) === "\0") end--;
43
- return result.slice(start, end).split("\0").map(transform).join(delimiter);
44
- }
45
- function replace(input, re, value) {
46
- if (re instanceof RegExp) return input.replace(re, value);
47
- return re.reduce(function (input2, re2) {
48
- return input2.replace(re2, value);
49
- }, input);
50
- }
51
-
52
- // ../../node_modules/.pnpm/pascal-case@3.1.2/node_modules/pascal-case/dist.es2015/index.js
53
- function pascalCaseTransform(input, index) {
54
- var firstChar = input.charAt(0);
55
- var lowerChars = input.substr(1).toLowerCase();
56
- if (index > 0 && firstChar >= "0" && firstChar <= "9") {
57
- return "_" + firstChar + lowerChars;
58
- }
59
- return "" + firstChar.toUpperCase() + lowerChars;
60
- }
61
- function pascalCase(input, options) {
62
- if (options === void 0) {
63
- options = {};
64
- }
65
- return noCase(input, __assign({
66
- delimiter: "",
67
- transform: pascalCaseTransform
68
- }, options));
69
- }
70
-
71
- // ../carlin/src/deploy/lambdaLayer/getPackageLambdaLayerStackName.ts
72
- var lambdaLayerStackNamePrefix = `LambdaLayer`;
73
- var getPackageLambdaLayerStackName = packageName => {
74
- const [scopedName, version] = packageName.split("@").filter(part => {
75
- return !!part;
76
- });
77
- return [lambdaLayerStackNamePrefix, pascalCase(scopedName), version.replace(/[^0-9.]/g, "").replace(/\./g, "-")].join("-");
78
- };
79
-
80
- // package.json
81
- var package_default = {
82
- name: "@ttoss/appsync-api",
83
- version: "0.17.11",
84
- description: "A library for building GraphQL APIs for AWS AppSync.",
85
- author: "ttoss",
86
- contributors: ["Pedro Arantes <pedro@arantespp.com> (https://arantespp.com)"],
87
- repository: {
88
- type: "git",
89
- url: "https://github.com/ttoss/ttoss.git",
90
- directory: "packages/appsync-api"
91
- },
92
- main: "dist/index.js",
93
- module: "dist/esm/index.js",
94
- files: ["dist", "src"],
95
- scripts: {
96
- build: "tsup",
97
- test: "jest"
98
- },
99
- sideEffects: false,
100
- typings: "dist/index.d.ts",
101
- dependencies: {
102
- "@ttoss/cloudformation": "workspace:^"
103
- },
104
- peerDependencies: {
105
- "@ttoss/graphql-api": "workspace:^",
106
- graphql: "^16.6.0"
107
- },
108
- devDependencies: {
109
- "@ttoss/config": "workspace:^",
110
- "@ttoss/graphql-api": "workspace:^",
111
- "@ttoss/relay-amplify": "workspace:^",
112
- "@types/aws-lambda": "^8.10.130",
113
- carlin: "workspace:^",
114
- graphql: "^16.8.1",
115
- "graphql-shield": "^7.6.5",
116
- jest: "^29.7.0",
117
- tsup: "^8.0.1"
118
- },
119
- keywords: ["api", "appsync", "aws", "graphql"],
120
- publishConfig: {
121
- access: "public",
122
- provenance: true
123
- }
124
- };
125
-
126
- // src/createApiTemplate.ts
127
5
  var AppSyncGraphQLApiLogicalId = "AppSyncGraphQLApi";
128
6
  var AppSyncGraphQLSchemaLogicalId = "AppSyncGraphQLSchema";
129
7
  var AppSyncLambdaFunctionLogicalId = "AppSyncLambdaFunction";
@@ -135,7 +13,8 @@ var createApiTemplate = ({
135
13
  schemaComposer,
136
14
  dataSource,
137
15
  lambdaFunction,
138
- userPoolConfig
16
+ userPoolConfig,
17
+ customDomain
139
18
  }) => {
140
19
  const sdlWithoutComments = schemaComposer.toSDL({
141
20
  commentDescriptions: false,
@@ -158,21 +37,6 @@ var createApiTemplate = ({
158
37
  };
159
38
  });
160
39
  }).filter(Boolean);
161
- const getPeerDependenciesLambdaLayers = () => {
162
- const {
163
- peerDependencies
164
- } = package_default;
165
- const lambdaLayerStackNames = Object.entries(peerDependencies).filter(([dependencyName]) => {
166
- return ["graphql"].includes(dependencyName);
167
- }).map(([dependencyName, dependencyVersion]) => {
168
- return getPackageLambdaLayerStackName([dependencyName, dependencyVersion].join("@"));
169
- });
170
- return lambdaLayerStackNames.map(lambdaLayerStackName => {
171
- return {
172
- "Fn::ImportValue": lambdaLayerStackName
173
- };
174
- });
175
- };
176
40
  const template = {
177
41
  AWSTemplateFormatVersion: "2010-09-09",
178
42
  Parameters: {
@@ -228,10 +92,10 @@ var createApiTemplate = ({
228
92
  }
229
93
  },
230
94
  Handler: "index.handler",
231
- Layers: getPeerDependenciesLambdaLayers(),
95
+ Layers: lambdaFunction.layers,
232
96
  MemorySize: 512,
233
97
  Role: lambdaFunction.roleArn,
234
- Runtime: "nodejs18.x",
98
+ Runtime: "nodejs20.x",
235
99
  /**
236
100
  * https://docs.aws.amazon.com/general/latest/gr/appsync.html
237
101
  * Request execution time for mutations, queries, and subscriptions: 30 seconds
@@ -345,6 +209,58 @@ var createApiTemplate = ({
345
209
  Variables: lambdaFunction.environment.variables
346
210
  };
347
211
  }
212
+ if (customDomain) {
213
+ const AppSyncDomainNameLogicalId = "AppSyncDomainName";
214
+ template.Resources[AppSyncDomainNameLogicalId] = {
215
+ Type: "AWS::AppSync::DomainName",
216
+ Properties: {
217
+ CertificateArn: customDomain.certificateArn,
218
+ Description: "Custom domain for AppSync API",
219
+ DomainName: customDomain.domainName
220
+ }
221
+ };
222
+ if (customDomain.hostedZoneName) {
223
+ const hostedZoneName = customDomain.hostedZoneName.endsWith(".") ? customDomain.hostedZoneName : `${customDomain.hostedZoneName}.`;
224
+ template.Resources.AppSyncDomainNameRoute53RecordSet = {
225
+ Type: "AWS::Route53::RecordSet",
226
+ Properties: {
227
+ HostedZoneName: hostedZoneName,
228
+ Name: customDomain.domainName,
229
+ ResourceRecords: [{
230
+ "Fn::GetAtt": [AppSyncDomainNameLogicalId, "AppSyncDomainName"]
231
+ }],
232
+ TTL: "900",
233
+ Type: "CNAME"
234
+ }
235
+ };
236
+ }
237
+ template.Resources.AppSyncDomainNameApiAssociation = {
238
+ Type: "AWS::AppSync::DomainNameApiAssociation",
239
+ Properties: {
240
+ ApiId: {
241
+ "Fn::GetAtt": [AppSyncGraphQLApiLogicalId, "ApiId"]
242
+ },
243
+ DomainName: {
244
+ "Fn::GetAtt": [AppSyncDomainNameLogicalId, "DomainName"]
245
+ }
246
+ }
247
+ };
248
+ if (!template.Outputs) {
249
+ template.Outputs = {};
250
+ }
251
+ template.Outputs.DomainName = {
252
+ Description: "Custom domain name for AppSync API",
253
+ Value: {
254
+ "Fn::GetAtt": [AppSyncDomainNameLogicalId, "DomainName"]
255
+ }
256
+ };
257
+ template.Outputs.CloudFrontDomainName = {
258
+ Description: "CloudFront domain name for AppSync API",
259
+ Value: {
260
+ "Fn::GetAtt": [AppSyncDomainNameLogicalId, "AppSyncDomainName"]
261
+ }
262
+ };
263
+ }
348
264
  return template;
349
265
  };
350
266
 
@@ -361,6 +277,9 @@ var createAppSyncResolverHandler = ({
361
277
  ...buildSchemaInput
362
278
  }) => {
363
279
  return async (event, context) => {
280
+ const {
281
+ schemaComposer
282
+ } = buildSchemaInput;
364
283
  const {
365
284
  info,
366
285
  arguments: args,
@@ -389,7 +308,32 @@ var createAppSyncResolverHandler = ({
389
308
  if (!resolver) {
390
309
  throw new Error(`Resolver for ${parentTypeName}.${fieldName} not found`);
391
310
  }
392
- const response = await resolver(source, args, {
311
+ const argsWithEnumValues = (() => {
312
+ const fieldsArgsIsEnumType = field.args.filter(arg => {
313
+ return schemaComposer.isEnumType(arg.type);
314
+ });
315
+ const enumArgs = fieldsArgsIsEnumType.map(enumArg => {
316
+ if (!args[enumArg.name]) {
317
+ return {
318
+ [enumArg.name]: enumArg.defaultValue
319
+ };
320
+ }
321
+ const values = schemaComposer.getETC(enumArg.type).getFields();
322
+ return {
323
+ [enumArg.name]: values[args[enumArg.name]].value
324
+ };
325
+ }).reduce((acc, curr) => {
326
+ return {
327
+ ...acc,
328
+ ...curr
329
+ };
330
+ }, {});
331
+ return {
332
+ ...args,
333
+ ...enumArgs
334
+ };
335
+ })();
336
+ const response = await resolver(source, argsWithEnumValues, {
393
337
  ...context,
394
338
  identity: event.identity,
395
339
  credentials,
package/dist/index.d.mts CHANGED
@@ -87,10 +87,14 @@ type StringOrImport = string | {
87
87
  * https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html
88
88
  */
89
89
  type AuthenticationType = 'API_KEY' | 'AWS_LAMBDA' | 'AWS_IAM' | 'OPENID_CONNECT' | 'AMAZON_COGNITO_USER_POOLS';
90
- declare const createApiTemplate: ({ additionalAuthenticationProviders, authenticationType, schemaComposer, dataSource, lambdaFunction, userPoolConfig, }: {
90
+ declare const createApiTemplate: ({ additionalAuthenticationProviders, authenticationType, schemaComposer, dataSource, lambdaFunction, userPoolConfig, customDomain, }: {
91
91
  additionalAuthenticationProviders?: AuthenticationType[] | undefined;
92
92
  authenticationType?: AuthenticationType | undefined;
93
- schemaComposer: SchemaComposer<any>;
93
+ customDomain?: {
94
+ domainName: string;
95
+ certificateArn: string;
96
+ hostedZoneName?: string | undefined;
97
+ } | undefined;
94
98
  dataSource: {
95
99
  roleArn: StringOrImport;
96
100
  };
@@ -98,8 +102,10 @@ declare const createApiTemplate: ({ additionalAuthenticationProviders, authentic
98
102
  environment?: {
99
103
  variables: Record<string, string>;
100
104
  };
105
+ layers?: any;
101
106
  roleArn: StringOrImport;
102
107
  };
108
+ schemaComposer: SchemaComposer<any>;
103
109
  userPoolConfig?: {
104
110
  appIdClientRegex: StringOrImport;
105
111
  awsRegion: StringOrImport;
package/dist/index.d.ts CHANGED
@@ -87,10 +87,14 @@ type StringOrImport = string | {
87
87
  * https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html
88
88
  */
89
89
  type AuthenticationType = 'API_KEY' | 'AWS_LAMBDA' | 'AWS_IAM' | 'OPENID_CONNECT' | 'AMAZON_COGNITO_USER_POOLS';
90
- declare const createApiTemplate: ({ additionalAuthenticationProviders, authenticationType, schemaComposer, dataSource, lambdaFunction, userPoolConfig, }: {
90
+ declare const createApiTemplate: ({ additionalAuthenticationProviders, authenticationType, schemaComposer, dataSource, lambdaFunction, userPoolConfig, customDomain, }: {
91
91
  additionalAuthenticationProviders?: AuthenticationType[] | undefined;
92
92
  authenticationType?: AuthenticationType | undefined;
93
- schemaComposer: SchemaComposer<any>;
93
+ customDomain?: {
94
+ domainName: string;
95
+ certificateArn: string;
96
+ hostedZoneName?: string | undefined;
97
+ } | undefined;
94
98
  dataSource: {
95
99
  roleArn: StringOrImport;
96
100
  };
@@ -98,8 +102,10 @@ declare const createApiTemplate: ({ additionalAuthenticationProviders, authentic
98
102
  environment?: {
99
103
  variables: Record<string, string>;
100
104
  };
105
+ layers?: any;
101
106
  roleArn: StringOrImport;
102
107
  };
108
+ schemaComposer: SchemaComposer<any>;
103
109
  userPoolConfig?: {
104
110
  appIdClientRegex: StringOrImport;
105
111
  awsRegion: StringOrImport;
package/dist/index.js CHANGED
@@ -34,128 +34,6 @@ module.exports = __toCommonJS(src_exports);
34
34
 
35
35
  // src/createApiTemplate.ts
36
36
  var import_graphql_api = require("@ttoss/graphql-api");
37
-
38
- // ../../node_modules/.pnpm/tslib@2.6.2/node_modules/tslib/tslib.es6.mjs
39
- var __assign = function () {
40
- __assign = Object.assign || function __assign2(t) {
41
- for (var s, i = 1, n = arguments.length; i < n; i++) {
42
- s = arguments[i];
43
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
44
- }
45
- return t;
46
- };
47
- return __assign.apply(this, arguments);
48
- };
49
-
50
- // ../../node_modules/.pnpm/lower-case@2.0.2/node_modules/lower-case/dist.es2015/index.js
51
- function lowerCase(str) {
52
- return str.toLowerCase();
53
- }
54
-
55
- // ../../node_modules/.pnpm/no-case@3.0.4/node_modules/no-case/dist.es2015/index.js
56
- var DEFAULT_SPLIT_REGEXP = [/([a-z0-9])([A-Z])/g, /([A-Z])([A-Z][a-z])/g];
57
- var DEFAULT_STRIP_REGEXP = /[^A-Z0-9]+/gi;
58
- function noCase(input, options) {
59
- if (options === void 0) {
60
- options = {};
61
- }
62
- var _a = options.splitRegexp,
63
- splitRegexp = _a === void 0 ? DEFAULT_SPLIT_REGEXP : _a,
64
- _b = options.stripRegexp,
65
- stripRegexp = _b === void 0 ? DEFAULT_STRIP_REGEXP : _b,
66
- _c = options.transform,
67
- transform = _c === void 0 ? lowerCase : _c,
68
- _d = options.delimiter,
69
- delimiter = _d === void 0 ? " " : _d;
70
- var result = replace(replace(input, splitRegexp, "$1\0$2"), stripRegexp, "\0");
71
- var start = 0;
72
- var end = result.length;
73
- while (result.charAt(start) === "\0") start++;
74
- while (result.charAt(end - 1) === "\0") end--;
75
- return result.slice(start, end).split("\0").map(transform).join(delimiter);
76
- }
77
- function replace(input, re, value) {
78
- if (re instanceof RegExp) return input.replace(re, value);
79
- return re.reduce(function (input2, re2) {
80
- return input2.replace(re2, value);
81
- }, input);
82
- }
83
-
84
- // ../../node_modules/.pnpm/pascal-case@3.1.2/node_modules/pascal-case/dist.es2015/index.js
85
- function pascalCaseTransform(input, index) {
86
- var firstChar = input.charAt(0);
87
- var lowerChars = input.substr(1).toLowerCase();
88
- if (index > 0 && firstChar >= "0" && firstChar <= "9") {
89
- return "_" + firstChar + lowerChars;
90
- }
91
- return "" + firstChar.toUpperCase() + lowerChars;
92
- }
93
- function pascalCase(input, options) {
94
- if (options === void 0) {
95
- options = {};
96
- }
97
- return noCase(input, __assign({
98
- delimiter: "",
99
- transform: pascalCaseTransform
100
- }, options));
101
- }
102
-
103
- // ../carlin/src/deploy/lambdaLayer/getPackageLambdaLayerStackName.ts
104
- var lambdaLayerStackNamePrefix = `LambdaLayer`;
105
- var getPackageLambdaLayerStackName = packageName => {
106
- const [scopedName, version] = packageName.split("@").filter(part => {
107
- return !!part;
108
- });
109
- return [lambdaLayerStackNamePrefix, pascalCase(scopedName), version.replace(/[^0-9.]/g, "").replace(/\./g, "-")].join("-");
110
- };
111
-
112
- // package.json
113
- var package_default = {
114
- name: "@ttoss/appsync-api",
115
- version: "0.17.11",
116
- description: "A library for building GraphQL APIs for AWS AppSync.",
117
- author: "ttoss",
118
- contributors: ["Pedro Arantes <pedro@arantespp.com> (https://arantespp.com)"],
119
- repository: {
120
- type: "git",
121
- url: "https://github.com/ttoss/ttoss.git",
122
- directory: "packages/appsync-api"
123
- },
124
- main: "dist/index.js",
125
- module: "dist/esm/index.js",
126
- files: ["dist", "src"],
127
- scripts: {
128
- build: "tsup",
129
- test: "jest"
130
- },
131
- sideEffects: false,
132
- typings: "dist/index.d.ts",
133
- dependencies: {
134
- "@ttoss/cloudformation": "workspace:^"
135
- },
136
- peerDependencies: {
137
- "@ttoss/graphql-api": "workspace:^",
138
- graphql: "^16.6.0"
139
- },
140
- devDependencies: {
141
- "@ttoss/config": "workspace:^",
142
- "@ttoss/graphql-api": "workspace:^",
143
- "@ttoss/relay-amplify": "workspace:^",
144
- "@types/aws-lambda": "^8.10.130",
145
- carlin: "workspace:^",
146
- graphql: "^16.8.1",
147
- "graphql-shield": "^7.6.5",
148
- jest: "^29.7.0",
149
- tsup: "^8.0.1"
150
- },
151
- keywords: ["api", "appsync", "aws", "graphql"],
152
- publishConfig: {
153
- access: "public",
154
- provenance: true
155
- }
156
- };
157
-
158
- // src/createApiTemplate.ts
159
37
  var AppSyncGraphQLApiLogicalId = "AppSyncGraphQLApi";
160
38
  var AppSyncGraphQLSchemaLogicalId = "AppSyncGraphQLSchema";
161
39
  var AppSyncLambdaFunctionLogicalId = "AppSyncLambdaFunction";
@@ -167,7 +45,8 @@ var createApiTemplate = ({
167
45
  schemaComposer,
168
46
  dataSource,
169
47
  lambdaFunction,
170
- userPoolConfig
48
+ userPoolConfig,
49
+ customDomain
171
50
  }) => {
172
51
  const sdlWithoutComments = schemaComposer.toSDL({
173
52
  commentDescriptions: false,
@@ -190,21 +69,6 @@ var createApiTemplate = ({
190
69
  };
191
70
  });
192
71
  }).filter(Boolean);
193
- const getPeerDependenciesLambdaLayers = () => {
194
- const {
195
- peerDependencies
196
- } = package_default;
197
- const lambdaLayerStackNames = Object.entries(peerDependencies).filter(([dependencyName]) => {
198
- return ["graphql"].includes(dependencyName);
199
- }).map(([dependencyName, dependencyVersion]) => {
200
- return getPackageLambdaLayerStackName([dependencyName, dependencyVersion].join("@"));
201
- });
202
- return lambdaLayerStackNames.map(lambdaLayerStackName => {
203
- return {
204
- "Fn::ImportValue": lambdaLayerStackName
205
- };
206
- });
207
- };
208
72
  const template = {
209
73
  AWSTemplateFormatVersion: "2010-09-09",
210
74
  Parameters: {
@@ -260,10 +124,10 @@ var createApiTemplate = ({
260
124
  }
261
125
  },
262
126
  Handler: "index.handler",
263
- Layers: getPeerDependenciesLambdaLayers(),
127
+ Layers: lambdaFunction.layers,
264
128
  MemorySize: 512,
265
129
  Role: lambdaFunction.roleArn,
266
- Runtime: "nodejs18.x",
130
+ Runtime: "nodejs20.x",
267
131
  /**
268
132
  * https://docs.aws.amazon.com/general/latest/gr/appsync.html
269
133
  * Request execution time for mutations, queries, and subscriptions: 30 seconds
@@ -377,6 +241,58 @@ var createApiTemplate = ({
377
241
  Variables: lambdaFunction.environment.variables
378
242
  };
379
243
  }
244
+ if (customDomain) {
245
+ const AppSyncDomainNameLogicalId = "AppSyncDomainName";
246
+ template.Resources[AppSyncDomainNameLogicalId] = {
247
+ Type: "AWS::AppSync::DomainName",
248
+ Properties: {
249
+ CertificateArn: customDomain.certificateArn,
250
+ Description: "Custom domain for AppSync API",
251
+ DomainName: customDomain.domainName
252
+ }
253
+ };
254
+ if (customDomain.hostedZoneName) {
255
+ const hostedZoneName = customDomain.hostedZoneName.endsWith(".") ? customDomain.hostedZoneName : `${customDomain.hostedZoneName}.`;
256
+ template.Resources.AppSyncDomainNameRoute53RecordSet = {
257
+ Type: "AWS::Route53::RecordSet",
258
+ Properties: {
259
+ HostedZoneName: hostedZoneName,
260
+ Name: customDomain.domainName,
261
+ ResourceRecords: [{
262
+ "Fn::GetAtt": [AppSyncDomainNameLogicalId, "AppSyncDomainName"]
263
+ }],
264
+ TTL: "900",
265
+ Type: "CNAME"
266
+ }
267
+ };
268
+ }
269
+ template.Resources.AppSyncDomainNameApiAssociation = {
270
+ Type: "AWS::AppSync::DomainNameApiAssociation",
271
+ Properties: {
272
+ ApiId: {
273
+ "Fn::GetAtt": [AppSyncGraphQLApiLogicalId, "ApiId"]
274
+ },
275
+ DomainName: {
276
+ "Fn::GetAtt": [AppSyncDomainNameLogicalId, "DomainName"]
277
+ }
278
+ }
279
+ };
280
+ if (!template.Outputs) {
281
+ template.Outputs = {};
282
+ }
283
+ template.Outputs.DomainName = {
284
+ Description: "Custom domain name for AppSync API",
285
+ Value: {
286
+ "Fn::GetAtt": [AppSyncDomainNameLogicalId, "DomainName"]
287
+ }
288
+ };
289
+ template.Outputs.CloudFrontDomainName = {
290
+ Description: "CloudFront domain name for AppSync API",
291
+ Value: {
292
+ "Fn::GetAtt": [AppSyncDomainNameLogicalId, "AppSyncDomainName"]
293
+ }
294
+ };
295
+ }
380
296
  return template;
381
297
  };
382
298
 
@@ -393,6 +309,9 @@ var createAppSyncResolverHandler = ({
393
309
  ...buildSchemaInput
394
310
  }) => {
395
311
  return async (event, context) => {
312
+ const {
313
+ schemaComposer
314
+ } = buildSchemaInput;
396
315
  const {
397
316
  info,
398
317
  arguments: args,
@@ -421,7 +340,32 @@ var createAppSyncResolverHandler = ({
421
340
  if (!resolver) {
422
341
  throw new Error(`Resolver for ${parentTypeName}.${fieldName} not found`);
423
342
  }
424
- const response = await resolver(source, args, {
343
+ const argsWithEnumValues = (() => {
344
+ const fieldsArgsIsEnumType = field.args.filter(arg => {
345
+ return schemaComposer.isEnumType(arg.type);
346
+ });
347
+ const enumArgs = fieldsArgsIsEnumType.map(enumArg => {
348
+ if (!args[enumArg.name]) {
349
+ return {
350
+ [enumArg.name]: enumArg.defaultValue
351
+ };
352
+ }
353
+ const values = schemaComposer.getETC(enumArg.type).getFields();
354
+ return {
355
+ [enumArg.name]: values[args[enumArg.name]].value
356
+ };
357
+ }).reduce((acc, curr) => {
358
+ return {
359
+ ...acc,
360
+ ...curr
361
+ };
362
+ }, {});
363
+ return {
364
+ ...args,
365
+ ...enumArgs
366
+ };
367
+ })();
368
+ const response = await resolver(source, argsWithEnumValues, {
425
369
  ...context,
426
370
  identity: event.identity,
427
371
  credentials,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttoss/appsync-api",
3
- "version": "0.17.11",
3
+ "version": "0.18.0",
4
4
  "description": "A library for building GraphQL APIs for AWS AppSync.",
5
5
  "author": "ttoss",
6
6
  "contributors": [
@@ -35,7 +35,7 @@
35
35
  "@ttoss/config": "^1.31.4",
36
36
  "@ttoss/graphql-api": "^0.5.0",
37
37
  "@ttoss/relay-amplify": "^0.5.4",
38
- "carlin": "^1.31.7"
38
+ "carlin": "^1.31.8"
39
39
  },
40
40
  "keywords": [
41
41
  "api",
@@ -1,6 +1,4 @@
1
1
  import { type SchemaComposer, graphql } from '@ttoss/graphql-api';
2
- import { getPackageLambdaLayerStackName } from 'carlin/src/deploy/lambdaLayer/getPackageLambdaLayerStackName';
3
- import packageJson from '../package.json';
4
2
 
5
3
  /**
6
4
  * Absolute path to avoid:
@@ -44,11 +42,15 @@ export const createApiTemplate = ({
44
42
  dataSource,
45
43
  lambdaFunction,
46
44
  userPoolConfig,
45
+ customDomain,
47
46
  }: {
48
47
  additionalAuthenticationProviders?: AuthenticationType[];
49
48
  authenticationType?: AuthenticationType;
50
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
51
- schemaComposer: SchemaComposer<any>;
49
+ customDomain?: {
50
+ domainName: string;
51
+ certificateArn: string;
52
+ hostedZoneName?: string;
53
+ };
52
54
  dataSource: {
53
55
  roleArn: StringOrImport;
54
56
  };
@@ -56,8 +58,12 @@ export const createApiTemplate = ({
56
58
  environment?: {
57
59
  variables: Record<string, string>;
58
60
  };
61
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
62
+ layers?: any;
59
63
  roleArn: StringOrImport;
60
64
  };
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
+ schemaComposer: SchemaComposer<any>;
61
67
  userPoolConfig?: {
62
68
  appIdClientRegex: StringOrImport;
63
69
  awsRegion: StringOrImport;
@@ -102,26 +108,6 @@ export const createApiTemplate = ({
102
108
  })
103
109
  .filter(Boolean) as Array<{ fieldName: string; typeName: string }>;
104
110
 
105
- const getPeerDependenciesLambdaLayers = () => {
106
- const { peerDependencies } = packageJson;
107
-
108
- const lambdaLayerStackNames = Object.entries(peerDependencies)
109
- .filter(([dependencyName]) => {
110
- return ['graphql'].includes(dependencyName);
111
- })
112
- .map(([dependencyName, dependencyVersion]) => {
113
- return getPackageLambdaLayerStackName(
114
- [dependencyName, dependencyVersion].join('@')
115
- );
116
- });
117
-
118
- return lambdaLayerStackNames.map((lambdaLayerStackName) => {
119
- return {
120
- 'Fn::ImportValue': lambdaLayerStackName,
121
- };
122
- });
123
- };
124
-
125
111
  const template: CloudFormationTemplate = {
126
112
  AWSTemplateFormatVersion: '2010-09-09',
127
113
  Parameters: {
@@ -169,10 +155,10 @@ export const createApiTemplate = ({
169
155
  S3ObjectVersion: { Ref: 'LambdaS3ObjectVersion' },
170
156
  },
171
157
  Handler: 'index.handler',
172
- Layers: getPeerDependenciesLambdaLayers(),
158
+ Layers: lambdaFunction.layers,
173
159
  MemorySize: 512,
174
160
  Role: lambdaFunction.roleArn,
175
- Runtime: 'nodejs18.x',
161
+ Runtime: 'nodejs20.x',
176
162
  /**
177
163
  * https://docs.aws.amazon.com/general/latest/gr/appsync.html
178
164
  * Request execution time for mutations, queries, and subscriptions: 30 seconds
@@ -300,5 +286,75 @@ export const createApiTemplate = ({
300
286
  };
301
287
  }
302
288
 
289
+ if (customDomain) {
290
+ const AppSyncDomainNameLogicalId = 'AppSyncDomainName';
291
+
292
+ /**
293
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-domainname.html
294
+ */
295
+ template.Resources[AppSyncDomainNameLogicalId] = {
296
+ Type: 'AWS::AppSync::DomainName',
297
+ Properties: {
298
+ CertificateArn: customDomain.certificateArn,
299
+ Description: 'Custom domain for AppSync API',
300
+ DomainName: customDomain.domainName,
301
+ },
302
+ };
303
+
304
+ if (customDomain.hostedZoneName) {
305
+ const hostedZoneName = customDomain.hostedZoneName.endsWith('.')
306
+ ? customDomain.hostedZoneName
307
+ : `${customDomain.hostedZoneName}.`;
308
+
309
+ /**
310
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53-recordset.html
311
+ */
312
+ template.Resources.AppSyncDomainNameRoute53RecordSet = {
313
+ Type: 'AWS::Route53::RecordSet',
314
+ Properties: {
315
+ HostedZoneName: hostedZoneName,
316
+ Name: customDomain.domainName,
317
+ ResourceRecords: [
318
+ {
319
+ 'Fn::GetAtt': [AppSyncDomainNameLogicalId, 'AppSyncDomainName'],
320
+ },
321
+ ],
322
+ TTL: '900',
323
+ Type: 'CNAME',
324
+ },
325
+ };
326
+ }
327
+
328
+ template.Resources.AppSyncDomainNameApiAssociation = {
329
+ Type: 'AWS::AppSync::DomainNameApiAssociation',
330
+ Properties: {
331
+ ApiId: {
332
+ 'Fn::GetAtt': [AppSyncGraphQLApiLogicalId, 'ApiId'],
333
+ },
334
+ DomainName: {
335
+ 'Fn::GetAtt': [AppSyncDomainNameLogicalId, 'DomainName'],
336
+ },
337
+ },
338
+ };
339
+
340
+ if (!template.Outputs) {
341
+ template.Outputs = {};
342
+ }
343
+
344
+ template.Outputs.DomainName = {
345
+ Description: 'Custom domain name for AppSync API',
346
+ Value: {
347
+ 'Fn::GetAtt': [AppSyncDomainNameLogicalId, 'DomainName'],
348
+ },
349
+ };
350
+
351
+ template.Outputs.CloudFrontDomainName = {
352
+ Description: 'CloudFront domain name for AppSync API',
353
+ Value: {
354
+ 'Fn::GetAtt': [AppSyncDomainNameLogicalId, 'AppSyncDomainName'],
355
+ },
356
+ };
357
+ }
358
+
303
359
  return template;
304
360
  };
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1
2
  import { BuildSchemaInput, buildSchema } from '@ttoss/graphql-api';
2
3
  import { type GraphQLObjectType } from 'graphql';
3
4
  import { decodeCredentials } from '@ttoss/relay-amplify/src/encodeCredentials';
@@ -6,13 +7,15 @@ import type { AppSyncResolverHandler as AwsAppSyncResolverHandler } from 'aws-la
6
7
  export type AppSyncResolverHandler<
7
8
  TArguments,
8
9
  TResult,
9
- TSource = Record<string, any> | null
10
+ TSource = Record<string, any> | null,
10
11
  > = AwsAppSyncResolverHandler<TArguments, TResult, TSource>;
11
12
 
12
13
  export const createAppSyncResolverHandler = ({
13
14
  ...buildSchemaInput
14
15
  }: BuildSchemaInput): AppSyncResolverHandler<any, any, any> => {
15
16
  return async (event, context) => {
17
+ const { schemaComposer } = buildSchemaInput;
18
+
16
19
  const { info, arguments: args, source, request } = event;
17
20
 
18
21
  const { parentTypeName, fieldName } = info;
@@ -45,9 +48,44 @@ export const createAppSyncResolverHandler = ({
45
48
  throw new Error(`Resolver for ${parentTypeName}.${fieldName} not found`);
46
49
  }
47
50
 
51
+ /**
52
+ * `composeWithConnection` findMany resolver needs sort to be the enum
53
+ * value defined in the enum instead the enum name.
54
+ * For example, if the config `sort.ID_ASC.value` is `{ order : 'ASC' }`,
55
+ * then the value of the argument `sort` enm should be
56
+ * `{ order : 'ASC' }` instead of `'ID_ASC'`.
57
+ */
58
+ const argsWithEnumValues = (() => {
59
+ const fieldsArgsIsEnumType = field.args.filter((arg) => {
60
+ return schemaComposer.isEnumType(arg.type);
61
+ });
62
+
63
+ const enumArgs = fieldsArgsIsEnumType
64
+ .map((enumArg) => {
65
+ if (!args[enumArg.name]) {
66
+ return { [enumArg.name]: enumArg.defaultValue };
67
+ }
68
+
69
+ const values = schemaComposer.getETC(enumArg.type).getFields();
70
+
71
+ return {
72
+ [enumArg.name]: values[args[enumArg.name]].value,
73
+ };
74
+ })
75
+ .reduce((acc, curr) => {
76
+ return { ...acc, ...curr };
77
+ }, {});
78
+
79
+ /**
80
+ * If `args.sort` has sort name, i.e. `ID_ASC`, then `enumArgs` will
81
+ * replace it with the value of the enum, i.e. `{ order : 'ASC' }`.
82
+ */
83
+ return { ...args, ...enumArgs };
84
+ })();
85
+
48
86
  const response = await resolver(
49
87
  source,
50
- args,
88
+ argsWithEnumValues,
51
89
  { ...context, identity: event.identity, credentials, headers },
52
90
  info as any
53
91
  );