@ttoss/appsync-api 0.18.33 → 0.18.35

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.
@@ -1,360 +0,0 @@
1
- import { type SchemaComposer, graphql } from '@ttoss/graphql-api';
2
-
3
- /**
4
- * Absolute path to avoid:
5
- * The inferred type of 'template' cannot be named without a reference to
6
- * '@ttoss/appsync-api/node_modules/@ttoss/cloudformation'. This is likely not
7
- * portable. A type annotation is necessary.ts(2742)
8
- */
9
- import type { CloudFormationTemplate } from '../../cloudformation/src/';
10
-
11
- export const AppSyncGraphQLApiLogicalId = 'AppSyncGraphQLApi';
12
-
13
- export const AppSyncGraphQLSchemaLogicalId = 'AppSyncGraphQLSchema';
14
-
15
- export const AppSyncLambdaFunctionLogicalId = 'AppSyncLambdaFunction';
16
-
17
- const AppSyncLambdaFunctionAppSyncDataSourceLogicalId =
18
- 'AppSyncLambdaFunctionAppSyncDataSource';
19
-
20
- export const AppSyncGraphQLApiKeyLogicalId = 'AppSyncGraphQLApiKey';
21
-
22
- type StringOrImport =
23
- | string
24
- | {
25
- 'Fn::ImportValue': string;
26
- };
27
-
28
- /**
29
- * https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html
30
- */
31
- type AuthenticationType =
32
- | 'API_KEY'
33
- | 'AWS_LAMBDA'
34
- | 'AWS_IAM'
35
- | 'OPENID_CONNECT'
36
- | 'AMAZON_COGNITO_USER_POOLS';
37
-
38
- export const createApiTemplate = ({
39
- additionalAuthenticationProviders,
40
- authenticationType = 'AMAZON_COGNITO_USER_POOLS',
41
- schemaComposer,
42
- dataSource,
43
- lambdaFunction,
44
- userPoolConfig,
45
- customDomain,
46
- }: {
47
- additionalAuthenticationProviders?: AuthenticationType[];
48
- authenticationType?: AuthenticationType;
49
- customDomain?: {
50
- domainName: string;
51
- certificateArn: string;
52
- hostedZoneName?: string;
53
- };
54
- dataSource: {
55
- roleArn: StringOrImport;
56
- };
57
- lambdaFunction: {
58
- environment?: {
59
- variables: Record<string, string>;
60
- };
61
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
62
- layers?: any;
63
- roleArn: StringOrImport;
64
- };
65
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
- schemaComposer: SchemaComposer<any>;
67
- userPoolConfig?: {
68
- appIdClientRegex: StringOrImport;
69
- awsRegion: StringOrImport;
70
- defaultAction: 'ALLOW' | 'DENY';
71
- userPoolId: StringOrImport;
72
- };
73
- }): CloudFormationTemplate => {
74
- /**
75
- * It should be on top of the file, otherwise it will have empty Mutation
76
- * or Subscription if there are no resolvers for them.
77
- */
78
- const sdlWithoutComments = schemaComposer.toSDL({
79
- commentDescriptions: false,
80
- omitDescriptions: true,
81
- omitScalars: true,
82
- });
83
-
84
- graphql.validateSchema(schemaComposer.buildSchema());
85
-
86
- /**
87
- * Get FieldName and TypeName. `resolveMethods` is a Map of
88
- * `typeName: { fieldName: resolverFn }`
89
- */
90
- const resolveMethods = schemaComposer.getResolveMethods();
91
-
92
- const resolveMethodsEntries = Object.entries(resolveMethods)
93
- .flatMap(([typeName, fieldResolvers]) => {
94
- return Object.entries(fieldResolvers).map(([fieldName, resolver]) => {
95
- if (typeof resolver !== 'function') {
96
- return undefined;
97
- }
98
-
99
- if (typeName.toLowerCase().includes('enum')) {
100
- return undefined;
101
- }
102
-
103
- return {
104
- fieldName,
105
- typeName,
106
- };
107
- });
108
- })
109
- .filter(Boolean) as Array<{ fieldName: string; typeName: string }>;
110
-
111
- const template: CloudFormationTemplate = {
112
- AWSTemplateFormatVersion: '2010-09-09',
113
- Parameters: {
114
- Environment: {
115
- Default: 'Staging',
116
- Type: 'String',
117
- AllowedValues: ['Staging', 'Production'],
118
- },
119
- LambdaS3Bucket: {
120
- Type: 'String',
121
- },
122
- LambdaS3Key: {
123
- Type: 'String',
124
- },
125
- LambdaS3ObjectVersion: {
126
- Type: 'String',
127
- },
128
- },
129
- Resources: {
130
- /**
131
- * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-graphqlapi.html#cfn-appsync-graphqlapi-name
132
- */
133
- [AppSyncGraphQLApiLogicalId]: {
134
- Type: 'AWS::AppSync::GraphQLApi',
135
- Properties: {
136
- AuthenticationType: authenticationType,
137
- Name: {
138
- Ref: 'AWS::StackName',
139
- },
140
- },
141
- },
142
- [AppSyncGraphQLSchemaLogicalId]: {
143
- Type: 'AWS::AppSync::GraphQLSchema',
144
- Properties: {
145
- ApiId: { 'Fn::GetAtt': [AppSyncGraphQLApiLogicalId, 'ApiId'] },
146
- Definition: sdlWithoutComments,
147
- },
148
- },
149
- [AppSyncLambdaFunctionLogicalId]: {
150
- Type: 'AWS::Lambda::Function',
151
- Properties: {
152
- Code: {
153
- S3Bucket: { Ref: 'LambdaS3Bucket' },
154
- S3Key: { Ref: 'LambdaS3Key' },
155
- S3ObjectVersion: { Ref: 'LambdaS3ObjectVersion' },
156
- },
157
- Handler: 'index.handler',
158
- Layers: lambdaFunction.layers,
159
- MemorySize: 512,
160
- Role: lambdaFunction.roleArn,
161
- Runtime: 'nodejs20.x',
162
- /**
163
- * https://docs.aws.amazon.com/general/latest/gr/appsync.html
164
- * Request execution time for mutations, queries, and subscriptions: 30 seconds
165
- */
166
- Timeout: 29,
167
- },
168
- },
169
- [AppSyncLambdaFunctionAppSyncDataSourceLogicalId]: {
170
- Type: 'AWS::AppSync::DataSource',
171
- Properties: {
172
- ApiId: { 'Fn::GetAtt': [AppSyncGraphQLApiLogicalId, 'ApiId'] },
173
- LambdaConfig: {
174
- LambdaFunctionArn: {
175
- 'Fn::GetAtt': [AppSyncLambdaFunctionLogicalId, 'Arn'],
176
- },
177
- },
178
- Name: 'AppSyncLambdaFunctionAppSyncDataSource',
179
- ServiceRoleArn: dataSource.roleArn,
180
- Type: 'AWS_LAMBDA',
181
- },
182
- },
183
- },
184
- Outputs: {
185
- AppSyncApiGraphQLUrl: {
186
- Export: {
187
- Name: {
188
- 'Fn::Join': [
189
- ':',
190
- [{ Ref: 'AWS::StackName' }, 'AppSyncApiGraphQLUrl'],
191
- ],
192
- },
193
- },
194
- Value: {
195
- 'Fn::GetAtt': [AppSyncGraphQLApiLogicalId, 'GraphQLUrl'],
196
- },
197
- },
198
- AppSyncApiArn: {
199
- Export: {
200
- Name: {
201
- 'Fn::Join': [':', [{ Ref: 'AWS::StackName' }, 'AppSyncApiArn']],
202
- },
203
- },
204
- Value: {
205
- 'Fn::GetAtt': [AppSyncGraphQLApiLogicalId, 'Arn'],
206
- },
207
- },
208
- },
209
- };
210
-
211
- resolveMethodsEntries.forEach(({ fieldName, typeName }) => {
212
- template.Resources[`${fieldName}${typeName}AppSyncResolver`] = {
213
- Type: 'AWS::AppSync::Resolver',
214
- DependsOn: AppSyncGraphQLSchemaLogicalId,
215
- Properties: {
216
- ApiId: { 'Fn::GetAtt': [AppSyncGraphQLApiLogicalId, 'ApiId'] },
217
- FieldName: fieldName,
218
- TypeName: typeName,
219
- DataSourceName: {
220
- 'Fn::GetAtt': [
221
- AppSyncLambdaFunctionAppSyncDataSourceLogicalId,
222
- 'Name',
223
- ],
224
- },
225
- },
226
- };
227
- });
228
-
229
- const apiKey =
230
- additionalAuthenticationProviders?.includes('API_KEY') ||
231
- authenticationType === 'API_KEY';
232
-
233
- const cognitoUserPoolAuth =
234
- additionalAuthenticationProviders?.includes('AMAZON_COGNITO_USER_POOLS') ||
235
- authenticationType === 'AMAZON_COGNITO_USER_POOLS';
236
-
237
- if (additionalAuthenticationProviders) {
238
- template.Resources[
239
- AppSyncGraphQLApiLogicalId
240
- ].Properties.AdditionalAuthenticationProviders =
241
- additionalAuthenticationProviders?.map((provider) => {
242
- return {
243
- AuthenticationType: provider,
244
- };
245
- });
246
- }
247
-
248
- if (apiKey) {
249
- template.Resources[AppSyncGraphQLApiKeyLogicalId] = {
250
- Type: 'AWS::AppSync::ApiKey',
251
- Properties: {
252
- ApiId: { 'Fn::GetAtt': [AppSyncGraphQLApiLogicalId, 'ApiId'] },
253
- },
254
- };
255
-
256
- if (!template.Outputs) {
257
- template.Outputs = {};
258
- }
259
-
260
- template.Outputs[AppSyncGraphQLApiKeyLogicalId] = {
261
- Value: {
262
- 'Fn::GetAtt': [AppSyncGraphQLApiKeyLogicalId, 'ApiKey'],
263
- },
264
- };
265
- }
266
-
267
- if (cognitoUserPoolAuth) {
268
- if (!userPoolConfig) {
269
- throw new Error(
270
- 'userPoolConfig is required when using AMAZON_COGNITO_USER_POOLS authentication.'
271
- );
272
- }
273
-
274
- template.Resources[AppSyncGraphQLApiLogicalId].Properties.UserPoolConfig = {
275
- AppIdClientRegex: userPoolConfig.appIdClientRegex,
276
- AwsRegion: userPoolConfig.awsRegion,
277
- DefaultAction: userPoolConfig.defaultAction,
278
- UserPoolId: userPoolConfig.userPoolId,
279
- };
280
- }
281
-
282
- if (lambdaFunction.environment?.variables) {
283
- template.Resources[AppSyncLambdaFunctionLogicalId].Properties.Environment =
284
- {
285
- Variables: lambdaFunction.environment.variables,
286
- };
287
- }
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
-
359
- return template;
360
- };
@@ -1,95 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { BuildSchemaInput, buildSchema } from '@ttoss/graphql-api';
3
- import { type GraphQLObjectType } from 'graphql';
4
- import type { AppSyncResolverHandler as AwsAppSyncResolverHandler } from 'aws-lambda';
5
-
6
- export type AppSyncResolverHandler<
7
- TArguments,
8
- TResult,
9
- TSource = Record<string, any> | null,
10
- > = AwsAppSyncResolverHandler<TArguments, TResult, TSource>;
11
-
12
- export const createAppSyncResolverHandler = ({
13
- ...buildSchemaInput
14
- }: BuildSchemaInput): AppSyncResolverHandler<any, any, any> => {
15
- return async (event, appSyncHandlerContext) => {
16
- const { schemaComposer } = buildSchemaInput;
17
-
18
- /**
19
- * https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference-js.html
20
- */
21
- const { info, arguments: args, source, request } = event;
22
-
23
- const { parentTypeName, fieldName } = info;
24
-
25
- const context = {
26
- handler: appSyncHandlerContext,
27
- request,
28
- identity: event.identity,
29
- };
30
-
31
- const schema = buildSchema(buildSchemaInput);
32
-
33
- const parentType = schema.getType(parentTypeName) as GraphQLObjectType;
34
-
35
- if (!parentType) {
36
- throw new Error(`Type ${parentTypeName} not found`);
37
- }
38
-
39
- const field = parentType.getFields()[fieldName];
40
-
41
- const resolver = field?.resolve;
42
-
43
- if (!resolver) {
44
- throw new Error(`Resolver for ${parentTypeName}.${fieldName} not found`);
45
- }
46
-
47
- /**
48
- * `composeWithConnection` findMany resolver needs sort to be the enum
49
- * value defined in the enum instead the enum name.
50
- * For example, if the config `sort.ID_ASC.value` is `{ order : 'ASC' }`,
51
- * then the value of the argument `sort` enm should be
52
- * `{ order : 'ASC' }` instead of `'ID_ASC'`.
53
- */
54
- const argsWithEnumValues = (() => {
55
- const fieldsArgsIsEnumType = field.args.filter((arg) => {
56
- return schemaComposer.isEnumType(arg.type);
57
- });
58
-
59
- const enumArgs = fieldsArgsIsEnumType
60
- .map((enumArg) => {
61
- if (!args[enumArg.name]) {
62
- return { [enumArg.name]: enumArg.defaultValue };
63
- }
64
-
65
- const values = schemaComposer.getETC(enumArg.type).getFields();
66
-
67
- return {
68
- [enumArg.name]: values[args[enumArg.name]].value,
69
- };
70
- })
71
- .reduce((acc, curr) => {
72
- return { ...acc, ...curr };
73
- }, {});
74
-
75
- /**
76
- * If `args.sort` has sort name, i.e. `ID_ASC`, then `enumArgs` will
77
- * replace it with the value of the enum, i.e. `{ order : 'ASC' }`.
78
- */
79
- return { ...args, ...enumArgs };
80
- })();
81
-
82
- const response = await resolver(
83
- source,
84
- argsWithEnumValues,
85
- context,
86
- info as any
87
- );
88
-
89
- if (response instanceof Error) {
90
- throw response;
91
- }
92
-
93
- return response;
94
- };
95
- };
package/src/index.ts DELETED
@@ -1,6 +0,0 @@
1
- export { createApiTemplate } from './createApiTemplate';
2
- export {
3
- type AppSyncResolverHandler,
4
- createAppSyncResolverHandler,
5
- } from './createAppSyncResolverHandler';
6
- export type { AppSyncIdentityCognito } from 'aws-lambda';