UNPKG

@aws-amplify/graphql-transformer-core

Version:

A framework to transform from GraphQL SDL to AWS CloudFormation.

560 lines 29.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.GraphQLTransform = void 0; const graphql_transformer_interfaces_1 = require("@aws-amplify/graphql-transformer-interfaces"); const aws_appsync_1 = require("aws-cdk-lib/aws-appsync"); const aws_cdk_lib_1 = require("aws-cdk-lib"); const graphql_1 = require("graphql"); const lodash_1 = __importDefault(require("lodash")); const errors_1 = require("../errors"); const graphql_api_1 = require("../graphql-api"); const transformer_context_1 = require("../transformer-context"); const authType_1 = require("../utils/authType"); const cdk_compat_1 = require("../cdk-compat"); const pre_process_context_1 = require("../transformer-context/pre-process-context"); const transform_parameters_1 = require("../transformer-context/transform-parameters"); const utils_1 = require("../utils"); const SyncUtils = __importStar(require("./sync-utils")); const utils_2 = require("./utils"); const validation_1 = require("./validation"); const isFunction = (obj) => obj && typeof obj === 'function'; class GraphQLTransform { constructor(options) { var _a; this.options = options; this.seenTransformations = {}; if (!options.transformers || options.transformers.length === 0) { throw new Error('Must provide at least one transformer.'); } const sortedTransformers = (0, utils_2.sortTransformerPlugins)(options.transformers); this.transformers = sortedTransformers; this.authConfig = options.authConfig || { defaultAuthentication: { authenticationType: 'API_KEY', apiKeyConfig: { apiKeyExpirationDays: 7, description: 'Default API Key', }, }, additionalAuthenticationProviders: [], }; (0, validation_1.validateAuthModes)(this.authConfig); this.stackMappingOverrides = options.stackMapping || {}; this.userDefinedSlots = options.userDefinedSlots || {}; this.resolverConfig = options.resolverConfig || {}; this.transformParameters = { ...transform_parameters_1.defaultTransformParameters, ...((_a = options.transformParameters) !== null && _a !== void 0 ? _a : {}), }; this.logs = []; } preProcessSchema(schema) { const context = new pre_process_context_1.TransformerPreProcessContext(schema, this.transformParameters); this.transformers .filter((transformer) => isFunction(transformer.preMutateSchema)) .forEach((transformer) => transformer.preMutateSchema && transformer.preMutateSchema(context)); return this.transformers .filter((transformer) => isFunction(transformer.mutateSchema)) .reduce((mutateContext, transformer) => { const updatedInputDocument = transformer.mutateSchema ? transformer.mutateSchema(mutateContext) : mutateContext.inputDocument; return { ...mutateContext, inputDocument: updatedInputDocument, }; }, context).inputDocument; } transform({ assetProvider, dataSourceStrategies, nestedStackProvider, parameterProvider, rdsLayerMapping, rdsSnsTopicMapping, schema, scope, sqlDirectiveDataSourceStrategies, synthParameters, logging, }) { this.seenTransformations = {}; const parsedDocument = (0, graphql_1.parse)(schema); const context = new transformer_context_1.TransformerContext({ assetProvider, authConfig: this.authConfig, dataSourceStrategies: dataSourceStrategies, inputDocument: parsedDocument, nestedStackProvider, parameterProvider, rdsLayerMapping, rdsSnsTopicMapping, resolverConfig: this.resolverConfig, scope, sqlDirectiveDataSourceStrategies: sqlDirectiveDataSourceStrategies !== null && sqlDirectiveDataSourceStrategies !== void 0 ? sqlDirectiveDataSourceStrategies : [], stackMapping: this.stackMappingOverrides, synthParameters, transformParameters: this.transformParameters, logging, }); const validDirectiveNameMap = this.transformers.reduce((acc, t) => ({ ...acc, [t.directive.name.value]: true }), { aws_subscribe: true, aws_auth: true, aws_api_key: true, aws_iam: true, aws_oidc: true, aws_lambda: true, aws_cognito_user_pools: true, deprecated: true, }); let allModelDefinitions = [...context.inputDocument.definitions]; for (const transformer of this.transformers) { allModelDefinitions = allModelDefinitions.concat(...transformer.typeDefinitions, transformer.directive); } const errors = (0, validation_1.validateModelSchema)({ kind: graphql_1.Kind.DOCUMENT, definitions: allModelDefinitions, }); if (errors && errors.length) { throw new errors_1.SchemaValidationError(errors); } for (const transformer of this.transformers) { if (isFunction(transformer.before)) { transformer.before(context); } } for (const transformer of this.transformers) { for (const def of context.inputDocument.definitions) { switch (def.kind) { case 'ObjectTypeDefinition': this.transformObject(transformer, def, validDirectiveNameMap, context); break; case 'ObjectTypeExtension': this.transformObject(transformer, def, validDirectiveNameMap, context); break; case 'InterfaceTypeDefinition': this.transformInterface(transformer, def, validDirectiveNameMap, context); break; case 'ScalarTypeDefinition': this.transformScalar(transformer, def, validDirectiveNameMap, context); break; case 'UnionTypeDefinition': this.transformUnion(transformer, def, validDirectiveNameMap, context); break; case 'EnumTypeDefinition': this.transformEnum(transformer, def, validDirectiveNameMap, context); break; case 'InputObjectTypeDefinition': this.transformInputObject(transformer, def, validDirectiveNameMap, context); break; default: continue; } } } for (const transformer of this.transformers) { if (isFunction(transformer.validate)) { transformer.validate(context); } } for (const transformer of this.transformers) { if (isFunction(transformer.prepare)) { transformer.prepare(context); } } for (const transformer of this.transformers) { if (isFunction(transformer.transformSchema)) { transformer.transformSchema(context); } } const output = context.output; const api = this.generateGraphQlApi(context.stackManager, context.assetProvider, context.synthParameters, output, context.transformParameters, context.logging); context.bind(api); if (!lodash_1.default.isEmpty(this.resolverConfig)) { SyncUtils.createSyncTable(context); } for (const transformer of this.transformers) { if (isFunction(transformer.generateResolvers)) { transformer.generateResolvers(context); } } let reverseThroughTransformers = this.transformers.length - 1; while (reverseThroughTransformers >= 0) { const transformer = this.transformers[reverseThroughTransformers]; if (isFunction(transformer.after)) { transformer.after(context); } reverseThroughTransformers -= 1; } for (const transformer of this.transformers) { if (isFunction(transformer.getLogs)) { const logs = transformer.getLogs(); this.logs.push(...logs); } } this.collectResolvers(context, context.api); this.ensureNoneDataSource(context.api); } generateGraphQlApi(stackManager, assetProvider, synthParameters, output, transformParameters, logging) { var _a, _b; const { scope } = stackManager; const authorizationConfig = (0, authType_1.adoptAuthModes)(stackManager, synthParameters, this.authConfig); const apiName = synthParameters.apiName; const env = synthParameters.amplifyEnvironmentName; const name = env === 'NONE' ? apiName : `${apiName}-${env}`; const api = new graphql_api_1.GraphQLApi(scope, 'GraphQLAPI', { name, authorizationConfig, host: this.options.host, sandboxModeEnabled: this.transformParameters.sandboxModeEnabled, environmentName: env, disableResolverDeduping: this.transformParameters.disableResolverDeduping, assetProvider, logging, }); const authModes = [authorizationConfig.defaultAuthorization, ...(authorizationConfig.additionalAuthorizationModes || [])].map((mode) => mode === null || mode === void 0 ? void 0 : mode.authorizationType); if (authModes.includes(aws_appsync_1.AuthorizationType.API_KEY) && !this.transformParameters.suppressApiKeyGeneration) { const apiKeyConfig = [ authorizationConfig.defaultAuthorization, ...(authorizationConfig.additionalAuthorizationModes || []), ].find((auth) => (auth === null || auth === void 0 ? void 0 : auth.authorizationType) === aws_appsync_1.AuthorizationType.API_KEY); const apiKeyDescription = (_a = apiKeyConfig.apiKeyConfig) === null || _a === void 0 ? void 0 : _a.description; const apiKeyExpirationDays = (_b = apiKeyConfig.apiKeyConfig) === null || _b === void 0 ? void 0 : _b.expires; const apiKey = api.createAPIKey({ description: apiKeyDescription, expires: apiKeyExpirationDays, }); if (transformParameters.enableTransformerCfnOutputs) { new aws_cdk_lib_1.CfnOutput(aws_cdk_lib_1.Stack.of(scope), 'GraphQLAPIKeyOutput', { value: apiKey.attrApiKey, description: 'Your GraphQL API ID.', exportName: aws_cdk_lib_1.Fn.join(':', [aws_cdk_lib_1.Aws.STACK_NAME, 'GraphQLApiKey']), }); } } if (transformParameters.enableTransformerCfnOutputs) { new aws_cdk_lib_1.CfnOutput(aws_cdk_lib_1.Stack.of(scope), 'GraphQLAPIIdOutput', { value: api.apiId, description: 'Your GraphQL API ID.', exportName: aws_cdk_lib_1.Fn.join(':', [aws_cdk_lib_1.Aws.STACK_NAME, 'GraphQLApiId']), }); new aws_cdk_lib_1.CfnOutput(aws_cdk_lib_1.Stack.of(scope), 'GraphQLAPIEndpointOutput', { value: api.graphqlUrl, description: 'Your GraphQL API endpoint.', exportName: aws_cdk_lib_1.Fn.join(':', [aws_cdk_lib_1.Aws.STACK_NAME, 'GraphQLApiEndpoint']), }); } api.addToSchema(output.buildSchema()); return api; } collectResolvers(context, api) { const resolverEntries = context.resolvers.collectResolvers(); const seenMappingKeys = new Set(); for (const [resolverName, resolver] of resolverEntries) { const logicalId = resolver.resolverLogicalId; if (this.stackMappingOverrides[logicalId]) { seenMappingKeys.add(logicalId); } const userSlots = this.userDefinedSlots[resolverName] || []; userSlots.forEach((slot) => { const requestTemplate = slot.requestResolver ? cdk_compat_1.MappingTemplate.s3MappingTemplateFromString(slot.requestResolver.template, slot.requestResolver.fileName) : undefined; const responseTemplate = slot.responseResolver ? cdk_compat_1.MappingTemplate.s3MappingTemplateFromString(slot.responseResolver.template, slot.responseResolver.fileName) : undefined; resolver.addVtlFunctionToSlot(slot.slotName, requestTemplate, responseTemplate); }); resolver.synthesize(context, api); } const unusedKeys = Object.keys(this.stackMappingOverrides).filter((key) => !seenMappingKeys.has(key)); if (unusedKeys.length > 0) { this.logs.push({ level: graphql_transformer_interfaces_1.TransformerLogLevel.WARN, message: `stackMappings contains keys that don't match any generated resolver: [${unusedKeys.join(', ')}]. These keys will be ignored. You can discover valid resolver names by running \`npx ampx sandbox\` and examining the CloudFormation output, or by running \`cdk synth\` to see the generated template.`, }); } } transformObject(transformer, def, validDirectiveNameMap, context) { var _a, _b; let index = 0; for (const dir of (_a = def.directives) !== null && _a !== void 0 ? _a : []) { if (!validDirectiveNameMap[dir.name.value]) { throw new errors_1.UnknownDirectiveError(`Unknown directive '${dir.name.value}'. Either remove the directive from the schema or add a transformer to handle it.`); } try { if (!(0, utils_2.matchDirective)(transformer.directive, dir, def)) { continue; } const transformKey = (0, utils_2.makeSeenTransformationKey)(dir, def, undefined, undefined, index); if (this.seenTransformations[transformKey]) { continue; } if (def.kind === graphql_1.Kind.OBJECT_TYPE_EXTENSION) { throw new errors_1.InvalidTransformerError(`Directives are not supported on object or interface extensions. See the '@${dir.name.value}' directive on '${def.name.value}'`); } if (!isFunction(transformer.object)) { throw new errors_1.InvalidTransformerError(`The transformer '${transformer.name}' must implement the 'object()' method`); } transformer.object(def, dir, context); this.seenTransformations[transformKey] = true; } finally { index++; } } for (const field of (_b = def.fields) !== null && _b !== void 0 ? _b : []) { this.transformField(transformer, def, field, validDirectiveNameMap, context); } } transformField(transformer, parent, def, validDirectiveNameMap, context) { var _a, _b; let index = 0; for (const dir of (_a = def.directives) !== null && _a !== void 0 ? _a : []) { if (!validDirectiveNameMap[dir.name.value]) { throw new errors_1.UnknownDirectiveError(`Unknown directive '${dir.name.value}'. Either remove the directive from the schema or add a transformer to handle it.`); } try { if (!(0, utils_2.matchFieldDirective)(transformer.directive, dir, def)) { continue; } const transformKey = (0, utils_2.makeSeenTransformationKey)(dir, parent, def, undefined, index); if (this.seenTransformations[transformKey]) { continue; } if (parent.kind === graphql_1.Kind.OBJECT_TYPE_EXTENSION) { if (!isFunction(transformer.fieldOfExtendedType)) { throw new errors_1.InvalidTransformerError(`The '@${dir.name.value}' directive is not supported on fields of extended types`); } if (!(0, utils_1.isBuiltInGraphqlNode)(parent)) { throw new errors_1.InvalidDirectiveError(`The '@${dir.name.value}' directive cannot be used on fields of type extensions other than 'Query', 'Mutation', and 'Subscription'. See ${parent.name.value}.${def.name.value}`); } transformer.fieldOfExtendedType(parent, def, dir, context); } else { if (!isFunction(transformer.field)) { throw new errors_1.InvalidTransformerError(`The transformer '${transformer.name}' must implement the 'field()' method`); } transformer.field(parent, def, dir, context); } this.seenTransformations[transformKey] = true; } finally { index++; } } for (const arg of (_b = def.arguments) !== null && _b !== void 0 ? _b : []) { this.transformArgument(transformer, parent, def, arg, validDirectiveNameMap, context); } } transformArgument(transformer, parent, field, arg, validDirectiveNameMap, context) { var _a; let index = 0; for (const dir of (_a = arg.directives) !== null && _a !== void 0 ? _a : []) { if (!validDirectiveNameMap[dir.name.value]) { throw new errors_1.UnknownDirectiveError(`Unknown directive '${dir.name.value}'. Either remove the directive from the schema or add a transformer to handle it.`); } if ((0, utils_2.matchArgumentDirective)(transformer.directive, dir, arg)) { if (isFunction(transformer.argument)) { const transformKey = (0, utils_2.makeSeenTransformationKey)(dir, parent, field, arg, index); if (!this.seenTransformations[transformKey]) { transformer.argument(arg, dir, context); this.seenTransformations[transformKey] = true; } } else { throw new errors_1.InvalidTransformerError(`The transformer '${transformer.name}' must implement the 'argument()' method`); } } index++; } } transformInterface(transformer, def, validDirectiveNameMap, context) { var _a, _b; let index = 0; for (const dir of (_a = def.directives) !== null && _a !== void 0 ? _a : []) { if (!validDirectiveNameMap[dir.name.value]) { throw new errors_1.UnknownDirectiveError(`Unknown directive '${dir.name.value}'. Either remove the directive from the schema or add a transformer to handle it.`); } if ((0, utils_2.matchDirective)(transformer.directive, dir, def)) { if (isFunction(transformer.interface)) { const transformKey = (0, utils_2.makeSeenTransformationKey)(dir, def, undefined, undefined, index); if (!this.seenTransformations[transformKey]) { transformer.interface(def, dir, context); this.seenTransformations[transformKey] = true; } } else { throw new errors_1.InvalidTransformerError(`The transformer '${transformer.name}' must implement the 'interface()' method`); } } index++; } for (const field of (_b = def.fields) !== null && _b !== void 0 ? _b : []) { this.transformField(transformer, def, field, validDirectiveNameMap, context); } } transformScalar(transformer, def, validDirectiveNameMap, context) { var _a; let index = 0; for (const dir of (_a = def.directives) !== null && _a !== void 0 ? _a : []) { if (!validDirectiveNameMap[dir.name.value]) { throw new errors_1.UnknownDirectiveError(`Unknown directive '${dir.name.value}'. Either remove the directive from the schema or add a transformer to handle it.`); } if ((0, utils_2.matchDirective)(transformer.directive, dir, def)) { if (isFunction(transformer.scalar)) { const transformKey = (0, utils_2.makeSeenTransformationKey)(dir, def, undefined, undefined, index); if (!this.seenTransformations[transformKey]) { transformer.scalar(def, dir, context); this.seenTransformations[transformKey] = true; } } else { throw new errors_1.InvalidTransformerError(`The transformer '${transformer.name}' must implement the 'scalar()' method`); } } index++; } } transformUnion(transformer, def, validDirectiveNameMap, context) { var _a; let index = 0; for (const dir of (_a = def.directives) !== null && _a !== void 0 ? _a : []) { if (!validDirectiveNameMap[dir.name.value]) { throw new errors_1.UnknownDirectiveError(`Unknown directive '${dir.name.value}'. Either remove the directive from the schema or add a transformer to handle it.`); } if ((0, utils_2.matchDirective)(transformer.directive, dir, def)) { if (isFunction(transformer.union)) { const transformKey = (0, utils_2.makeSeenTransformationKey)(dir, def, undefined, undefined, index); if (!this.seenTransformations[transformKey]) { transformer.union(def, dir, context); this.seenTransformations[transformKey] = true; } } else { throw new errors_1.InvalidTransformerError(`The transformer '${transformer.name}' must implement the 'union()' method`); } } index++; } } transformEnum(transformer, def, validDirectiveNameMap, context) { var _a, _b; let index = 0; for (const dir of (_a = def.directives) !== null && _a !== void 0 ? _a : []) { if (!validDirectiveNameMap[dir.name.value]) { throw new errors_1.UnknownDirectiveError(`Unknown directive '${dir.name.value}'. Either remove the directive from the schema or add a transformer to handle it.`); } if ((0, utils_2.matchDirective)(transformer.directive, dir, def)) { if (isFunction(transformer.enum)) { const transformKey = (0, utils_2.makeSeenTransformationKey)(dir, def, undefined, undefined, index); if (!this.seenTransformations[transformKey]) { transformer.enum(def, dir, context); this.seenTransformations[transformKey] = true; } } else { throw new errors_1.InvalidTransformerError(`The transformer '${transformer.name}' must implement the 'enum()' method`); } } index++; } for (const value of (_b = def.values) !== null && _b !== void 0 ? _b : []) { this.transformEnumValue(transformer, def, value, validDirectiveNameMap, context); } } transformEnumValue(transformer, enm, def, validDirectiveNameMap, context) { var _a; let index = 0; for (const dir of (_a = def.directives) !== null && _a !== void 0 ? _a : []) { if (!validDirectiveNameMap[dir.name.value]) { throw new errors_1.UnknownDirectiveError(`Unknown directive '${dir.name.value}'. Either remove the directive from the schema or add a transformer to handle it.`); } if ((0, utils_2.matchEnumValueDirective)(transformer.directive, dir, def)) { if (isFunction(transformer.enumValue)) { const transformKey = (0, utils_2.makeSeenTransformationKey)(dir, enm, def, undefined, index); if (!this.seenTransformations[transformKey]) { transformer.enumValue(def, dir, context); this.seenTransformations[transformKey] = true; } } else { throw new errors_1.InvalidTransformerError(`The transformer '${transformer.name}' must implement the 'enumValue()' method`); } } index++; } } transformInputObject(transformer, def, validDirectiveNameMap, context) { var _a, _b; let index = 0; for (const dir of (_a = def.directives) !== null && _a !== void 0 ? _a : []) { if (!validDirectiveNameMap[dir.name.value]) { throw new errors_1.UnknownDirectiveError(`Unknown directive '${dir.name.value}'. Either remove the directive from the schema or add a transformer to handle it.`); } if ((0, utils_2.matchDirective)(transformer.directive, dir, def)) { if (isFunction(transformer.input)) { const transformKey = (0, utils_2.makeSeenTransformationKey)(dir, def, undefined, undefined, index); if (!this.seenTransformations[transformKey]) { transformer.input(def, dir, context); this.seenTransformations[transformKey] = true; } } else { throw new errors_1.InvalidTransformerError(`The transformer '${transformer.name}' must implement the 'input()' method`); } } index++; } for (const field of (_b = def.fields) !== null && _b !== void 0 ? _b : []) { this.transformInputField(transformer, def, field, validDirectiveNameMap, context); } } transformInputField(transformer, input, def, validDirectiveNameMap, context) { var _a; let index = 0; for (const dir of (_a = def.directives) !== null && _a !== void 0 ? _a : []) { if (!validDirectiveNameMap[dir.name.value]) { throw new errors_1.UnknownDirectiveError(`Unknown directive '${dir.name.value}'. Either remove the directive from the schema or add a transformer to handle it.`); } if ((0, utils_2.matchInputFieldDirective)(transformer.directive, dir, def)) { if (isFunction(transformer.inputValue)) { const transformKey = (0, utils_2.makeSeenTransformationKey)(dir, input, def, undefined, index); if (!this.seenTransformations[transformKey]) { transformer.inputValue(def, dir, context); this.seenTransformations[transformKey] = true; } } else { throw new errors_1.InvalidTransformerError(`The transformer '${transformer.name}' must implement the 'inputValue()' method`); } } index++; } } getLogs() { return this.logs; } ensureNoneDataSource(api) { if (!api.host.hasDataSource(transformer_context_1.NONE_DATA_SOURCE_NAME)) { api.host.addNoneDataSource(transformer_context_1.NONE_DATA_SOURCE_NAME, { name: transformer_context_1.NONE_DATA_SOURCE_NAME, description: 'None Data Source for Pipeline functions', }); } } } exports.GraphQLTransform = GraphQLTransform; //# sourceMappingURL=transform.js.map