@aws-amplify/graphql-transformer-core
Version:
A framework to transform from GraphQL SDL to AWS CloudFormation.
560 lines • 29.3 kB
JavaScript
;
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