@aws-amplify/graphql-transformer-core
Version:
A framework to transform from GraphQL SDL to AWS CloudFormation.
436 lines • 25.8 kB
JavaScript
"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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TransformerResolver = exports.ResolverManager = exports.NONE_DATA_SOURCE_NAME = void 0;
const aws_cdk_lib_1 = require("aws-cdk-lib");
const graphql_transformer_common_1 = require("graphql-transformer-common");
const ts_dedent_1 = require("ts-dedent");
const cdk_compat_1 = require("../cdk-compat");
const errors_1 = require("../errors");
const SyncUtils = __importStar(require("../transformation/sync-utils"));
const function_runtime_1 = require("../utils/function-runtime");
const types_1 = require("../types");
exports.NONE_DATA_SOURCE_NAME = 'NONE_DS';
class ResolverManager {
constructor() {
this.resolvers = new Map();
this.generateQueryResolver = (typeName, fieldName, resolverLogicalId, dataSource, requestMappingTemplate, responseMappingTemplate) => new TransformerResolver(typeName, fieldName, resolverLogicalId, { requestMappingTemplate, responseMappingTemplate }, ['init', 'preAuth', 'auth', 'postAuth', 'preDataLoad'], ['postDataLoad', 'finish'], dataSource);
this.generateMutationResolver = (typeName, fieldName, resolverLogicalId, dataSource, requestMappingTemplate, responseMappingTemplate) => new TransformerResolver(typeName, fieldName, resolverLogicalId, { requestMappingTemplate, responseMappingTemplate }, ['init', 'preAuth', 'auth', 'postAuth', 'validate', 'preUpdate'], ['postUpdate', 'finish'], dataSource);
this.generateSubscriptionResolver = (typeName, fieldName, resolverLogicalId, requestMappingTemplate, responseMappingTemplate) => new TransformerResolver(typeName, fieldName, resolverLogicalId, { requestMappingTemplate, responseMappingTemplate }, ['init', 'preAuth', 'auth', 'postAuth', 'preSubscribe'], []);
this.addResolver = (typeName, fieldName, resolver) => {
const key = `${typeName}.${fieldName}`;
if (this.resolvers.has(key)) {
throw new Error(`A resolver for typeName ${typeName} fieldName: ${fieldName} already exists`);
}
this.resolvers.set(key, resolver);
return resolver;
};
this.getResolver = (typeName, fieldName) => {
const key = `${typeName}.${fieldName}`;
if (this.resolvers.has(key)) {
return this.resolvers.get(key);
}
};
this.hasResolver = (typeName, fieldName) => {
const key = `${typeName}.${fieldName}`;
return this.resolvers.has(key);
};
this.removeResolver = (typeName, fieldName) => {
const key = `${typeName}.${fieldName}`;
if (this.resolvers.has(key)) {
const resolver = this.resolvers.get(key);
this.resolvers.delete(key);
return resolver;
}
throw new Error(`Resolver for typeName ${typeName} fieldName: ${fieldName} does not exists`);
};
this.collectResolvers = () => new Map(this.resolvers.entries());
}
}
exports.ResolverManager = ResolverManager;
class TransformerResolver {
constructor(typeName, fieldName, resolverLogicalId, mappingTemplate, requestSlots, responseSlots, datasource, runtime) {
this.typeName = typeName;
this.fieldName = fieldName;
this.resolverLogicalId = resolverLogicalId;
this.mappingTemplate = mappingTemplate;
this.requestSlots = requestSlots;
this.responseSlots = responseSlots;
this.datasource = datasource;
this.runtime = runtime;
this.slotMap = new Map();
this.mapToStack = (stack) => {
this.scope = stack;
};
this.setScope = (scope) => {
this.scope = scope;
};
this.addJsFunctionToSlot = (slotName, codeMappingTemplate, dataSource) => {
this.addToSlot(slotName, { codeMappingTemplate }, dataSource, types_1.APPSYNC_JS_RUNTIME);
};
this.addVtlFunctionToSlot = (slotName, requestMappingTemplate, responseMappingTemplate, dataSource) => {
this.addToSlot(slotName, { requestMappingTemplate, responseMappingTemplate }, dataSource, types_1.VTL_RUNTIME);
};
this.addToSlot = (slotName, mappingTemplate, dataSource, runtime) => {
if (!this.slotNames.has(slotName)) {
throw new Error(`Resolver is missing slot ${slotName}`);
}
let slotEntry;
if (this.slotMap.has(slotName)) {
slotEntry = this.slotMap.get(slotName);
}
else {
slotEntry = [];
}
if (this.slotExists(slotName, mappingTemplate)) {
this.updateSlot(slotName, mappingTemplate);
}
else {
slotEntry.push({
mappingTemplate,
dataSource,
runtime,
});
}
this.slotMap.set(slotName, slotEntry);
};
this.slotExists = (slotName, mappingTemplate) => this.findSlot(slotName, mappingTemplate) !== undefined;
this.findSlot = (slotName, mappingTemplate) => {
const slotEntries = this.slotMap.get(slotName);
const mappingTemplateNames = this.getMappingTemplateNames(mappingTemplate);
if (!slotEntries || mappingTemplateNames.find((name) => name.includes('{slotIndex}'))) {
return;
}
let slotIndex = 1;
for (const slotEntry of slotEntries) {
const slotEntryMappingTemplateNames = this.getMappingTemplateNames(slotEntry.mappingTemplate, 'NOT-FOUND').map((name) => name.replace('{slotName}', slotName).replace('{slotIndex}', `${slotIndex}`));
if (slotEntryMappingTemplateNames.every((name) => name === '')) {
continue;
}
if (slotEntryMappingTemplateNames.length === mappingTemplateNames.length &&
slotEntryMappingTemplateNames.every((element, index) => element === mappingTemplateNames[index])) {
return slotEntry;
}
slotIndex++;
}
};
this.updateSlot = (slotName, mappingTemplate) => {
const slot = this.findSlot(slotName, mappingTemplate);
if (!slot) {
return;
}
if ((0, function_runtime_1.isJsRuntimeTemplate)(mappingTemplate)) {
slot.mappingTemplate = mappingTemplate;
slot.runtime = types_1.APPSYNC_JS_RUNTIME;
}
else {
const { requestMappingTemplate, responseMappingTemplate } = mappingTemplate;
if (requestMappingTemplate && responseMappingTemplate) {
slot.mappingTemplate = { requestMappingTemplate, responseMappingTemplate };
}
if ((0, function_runtime_1.isJsRuntimeTemplate)(slot.mappingTemplate)) {
slot.mappingTemplate = mappingTemplate;
slot.runtime = types_1.VTL_RUNTIME;
}
else if (slot.mappingTemplate) {
const { requestMappingTemplate: slotRequestMappingTemplate, responseMappingTemplate: slotResponseMappingTemplate } = slot.mappingTemplate;
slot.mappingTemplate.requestMappingTemplate = requestMappingTemplate.name
? requestMappingTemplate
: slotRequestMappingTemplate;
slot.mappingTemplate.responseMappingTemplate = responseMappingTemplate.name
? responseMappingTemplate
: slotResponseMappingTemplate;
}
else {
slot.mappingTemplate = mappingTemplate;
}
}
};
this.synthesize = (context, api) => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
const scope = this.scope || context.stackManager.scope;
this.ensureNoneDataSource(api);
const requestFns = this.synthesizeResolvers(scope, api, this.requestSlots);
const responseFns = this.synthesizeResolvers(scope, api, this.responseSlots);
if ((0, function_runtime_1.isJsRuntimeTemplate)(this.mappingTemplate)) {
const { codeMappingTemplate } = this.mappingTemplate;
this.substituteSlotInfo(codeMappingTemplate, 'main', 0);
}
else {
const { requestMappingTemplate, responseMappingTemplate } = this.mappingTemplate;
requestMappingTemplate && this.substituteSlotInfo(requestMappingTemplate, 'main', 0);
responseMappingTemplate && this.substituteSlotInfo(responseMappingTemplate, 'main', 0);
}
const dataSourceProviderFn = api.host.addAppSyncFunction((0, graphql_transformer_common_1.toPascalCase)([this.typeName, this.fieldName, 'DataResolverFn']), this.mappingTemplate, ((_a = this.datasource) === null || _a === void 0 ? void 0 : _a.name) || exports.NONE_DATA_SOURCE_NAME, scope, this.runtime);
const { stashString, stashExpression } = this.createStashStatementGenerator(this.runtime);
let dataSourceType = 'NONE';
let dataSource = '';
if (this.datasource) {
dataSourceType = this.datasource.ds.type;
switch (dataSourceType) {
case 'AMAZON_DYNAMODB':
if (this.datasource.ds.dynamoDbConfig && !(0, aws_cdk_lib_1.isResolvableObject)(this.datasource.ds.dynamoDbConfig)) {
const tableName = (_b = this.datasource.ds.dynamoDbConfig) === null || _b === void 0 ? void 0 : _b.tableName;
dataSource = stashString({ name: 'tableName', value: tableName });
if (((_c = this.datasource.ds.dynamoDbConfig) === null || _c === void 0 ? void 0 : _c.deltaSyncConfig) &&
!(0, aws_cdk_lib_1.isResolvableObject)((_d = this.datasource.ds.dynamoDbConfig) === null || _d === void 0 ? void 0 : _d.deltaSyncConfig)) {
const deltaSyncTableTtl = aws_cdk_lib_1.Lazy.string({
produce: () => {
if (this.datasource &&
this.datasource.ds.dynamoDbConfig &&
!(0, aws_cdk_lib_1.isResolvableObject)(this.datasource.ds.dynamoDbConfig) &&
this.datasource.ds.dynamoDbConfig.deltaSyncConfig &&
!(0, aws_cdk_lib_1.isResolvableObject)(this.datasource.ds.dynamoDbConfig.deltaSyncConfig) &&
this.datasource.ds.dynamoDbConfig.deltaSyncConfig.deltaSyncTableTtl) {
return this.datasource.ds.dynamoDbConfig.deltaSyncConfig.deltaSyncTableTtl;
}
return SyncUtils.syncDataSourceConfig().DeltaSyncTableTTL.toString();
},
});
dataSource += '\n' + stashString({ name: 'deltaSyncTableTtl', value: deltaSyncTableTtl });
}
}
if (context.isProjectUsingDataStore()) {
const modelName = this.datasource.name.slice(0, -5);
const syncConfig = SyncUtils.getSyncConfig(context, modelName);
const funcConf = dataSourceProviderFn.node.children.find((it) => it.cfnResourceType === 'AWS::AppSync::FunctionConfiguration');
if (funcConf) {
funcConf.syncConfig = {
conflictDetection: syncConfig.ConflictDetection,
conflictHandler: syncConfig.ConflictHandler,
...(SyncUtils.isLambdaSyncConfig(syncConfig)
? {
lambdaConflictHandlerConfig: {
lambdaConflictHandlerArn: syncConfig.LambdaConflictHandler.lambdaArn,
},
}
: {}),
};
}
}
break;
case 'AMAZON_ELASTICSEARCH':
if (this.datasource.ds.elasticsearchConfig && !(0, aws_cdk_lib_1.isResolvableObject)(this.datasource.ds.elasticsearchConfig)) {
const endpoint = (_e = this.datasource.ds.elasticsearchConfig) === null || _e === void 0 ? void 0 : _e.endpoint;
dataSource = stashString({ name: 'endpoint', value: endpoint });
}
break;
case 'AWS_LAMBDA':
if (this.datasource.ds.lambdaConfig && !(0, aws_cdk_lib_1.isResolvableObject)(this.datasource.ds.lambdaConfig)) {
const lambdaFunctionArn = (_f = this.datasource.ds.lambdaConfig) === null || _f === void 0 ? void 0 : _f.lambdaFunctionArn;
dataSource = stashString({ name: 'lambdaFunctionArn', value: lambdaFunctionArn });
}
break;
case 'HTTP':
if (this.datasource.ds.httpConfig && !(0, aws_cdk_lib_1.isResolvableObject)(this.datasource.ds.httpConfig)) {
const endpoint = (_g = this.datasource.ds.httpConfig) === null || _g === void 0 ? void 0 : _g.endpoint;
dataSource = stashString({ name: 'endpoint', value: endpoint });
}
break;
case 'RELATIONAL_DATABASE':
if (this.datasource.ds.relationalDatabaseConfig &&
!(0, aws_cdk_lib_1.isResolvableObject)(this.datasource.ds.relationalDatabaseConfig) &&
!(0, aws_cdk_lib_1.isResolvableObject)((_h = this.datasource.ds.relationalDatabaseConfig) === null || _h === void 0 ? void 0 : _h.rdsHttpEndpointConfig)) {
const databaseName = (_j = this.datasource.ds.relationalDatabaseConfig) === null || _j === void 0 ? void 0 : _j.rdsHttpEndpointConfig.databaseName;
dataSource = stashString({ name: 'databaseName', value: databaseName });
}
break;
default:
throw new Error('Unknown DataSource type');
}
}
let initResolver = (0, ts_dedent_1.dedent) `
${stashString({ name: 'typeName', value: this.typeName })}
${stashString({ name: 'fieldName', value: this.fieldName })}
${stashExpression({ name: 'conditions', value: '[]' })}
${stashExpression({ name: 'metadata', value: '{}' })}
${stashString({ name: 'dataSourceType', value: dataSourceType, object: 'metadata' })}
${stashString({ name: 'apiId', value: api.apiId, object: 'metadata' })}
${stashExpression({ name: 'connectionAttributes', value: '{}' })}
${dataSource}
`;
const account = aws_cdk_lib_1.Stack.of(context.stackManager.scope).account;
const partition = aws_cdk_lib_1.Stack.of(context.stackManager.scope).partition;
const authRole = context.synthParameters.authenticatedUserRoleName;
if (authRole) {
const authRoleArn = `arn:${partition}:sts::${account}:assumed-role/${authRole}/CognitoIdentityCredentials`;
const authRoleStatement = stashString({ name: 'authRole', value: authRoleArn });
initResolver += (0, ts_dedent_1.dedent) `\n
${authRoleStatement}
`;
}
const unauthRole = context.synthParameters.unauthenticatedUserRoleName;
if (unauthRole) {
const unauthRoleArn = `arn:${partition}:sts::${account}:assumed-role/${unauthRole}/CognitoIdentityCredentials`;
const unauthRoleStatement = stashString({ name: 'unauthRole', value: unauthRoleArn });
initResolver += (0, ts_dedent_1.dedent) `\n
${unauthRoleStatement}
`;
}
const identityPoolId = context.synthParameters.identityPoolId;
if (identityPoolId) {
const identityPoolStatement = stashString({ name: 'identityPoolId', value: identityPoolId });
initResolver += (0, ts_dedent_1.dedent) `\n
${identityPoolStatement}
`;
}
const adminRoles = (_k = context.synthParameters.adminRoles) !== null && _k !== void 0 ? _k : [];
const adminRolesStatement = stashExpression({ name: 'adminRoles', value: JSON.stringify(adminRoles) });
initResolver += (0, ts_dedent_1.dedent) `\n
${adminRolesStatement}
`;
if ((0, function_runtime_1.isJsResolverFnRuntime)(this.runtime)) {
initResolver = (0, ts_dedent_1.dedent) `
export const request = (ctx) => {
${initResolver}
return {};
}
`;
}
else {
initResolver += '\n$util.toJson({})';
}
const initResponseResolver = (0, function_runtime_1.isJsResolverFnRuntime)(this.runtime)
? (0, ts_dedent_1.dedent) `
export const response = (ctx) => {
return ctx.prev.result;
};
`
: '$util.toJson($ctx.prev.result)';
const initResolverMappingTemplate = (0, function_runtime_1.isJsResolverFnRuntime)(this.runtime)
? {
codeMappingTemplate: cdk_compat_1.MappingTemplate.inlineTemplateFromString(initResolver + '\n\n' + initResponseResolver),
}
: {
requestMappingTemplate: cdk_compat_1.MappingTemplate.inlineTemplateFromString(initResolver),
responseMappingTemplate: cdk_compat_1.MappingTemplate.inlineTemplateFromString(initResponseResolver),
};
api.host.addResolver(this.typeName, this.fieldName, initResolverMappingTemplate, this.resolverLogicalId, undefined, [...requestFns, dataSourceProviderFn, ...responseFns].map((fn) => fn.functionId), scope, this.runtime);
};
this.synthesizeResolvers = (scope, api, slotsNames) => {
const appSyncFunctions = [];
for (const slotName of slotsNames) {
if (this.slotMap.has(slotName)) {
const slotEntries = this.slotMap.get(slotName);
let index = 0;
for (const slotItem of slotEntries) {
const name = `${this.typeName}${this.fieldName}${slotName}${index++}Function`;
const { mappingTemplate, dataSource } = slotItem;
let template;
if ((0, function_runtime_1.isJsRuntimeTemplate)(mappingTemplate)) {
const { codeMappingTemplate } = mappingTemplate;
this.substituteSlotInfo(codeMappingTemplate, slotName, index);
template = { codeMappingTemplate };
}
else {
const { requestMappingTemplate, responseMappingTemplate } = mappingTemplate;
requestMappingTemplate && this.substituteSlotInfo(requestMappingTemplate, slotName, index);
responseMappingTemplate && this.substituteSlotInfo(responseMappingTemplate, slotName, index);
const defaultVtlTemplate = cdk_compat_1.MappingTemplate.inlineTemplateFromString('$util.toJson({})');
template = {
requestMappingTemplate: requestMappingTemplate || defaultVtlTemplate,
responseMappingTemplate: responseMappingTemplate || defaultVtlTemplate,
};
}
const fn = api.host.addAppSyncFunction(name, template, (dataSource === null || dataSource === void 0 ? void 0 : dataSource.name) || exports.NONE_DATA_SOURCE_NAME, scope, slotItem.runtime);
appSyncFunctions.push(fn);
}
}
}
return appSyncFunctions;
};
if (!typeName) {
throw new errors_1.InvalidDirectiveError('typeName is required');
}
if (!fieldName) {
throw new errors_1.InvalidDirectiveError('fieldName is required');
}
if (!resolverLogicalId) {
throw new errors_1.InvalidDirectiveError('resolverLogicalId is required');
}
if (!(0, function_runtime_1.isJsResolverFnRuntime)(runtime) && !('requestMappingTemplate' in mappingTemplate)) {
throw new errors_1.InvalidDirectiveError('requestMappingTemplate is required');
}
if (!(0, function_runtime_1.isJsResolverFnRuntime)(runtime) && !('responseMappingTemplate' in mappingTemplate)) {
throw new errors_1.InvalidDirectiveError('responseMappingTemplate is required');
}
if ((0, function_runtime_1.isJsResolverFnRuntime)(runtime) && !('codeMappingTemplate' in mappingTemplate)) {
throw new errors_1.InvalidDirectiveError('codeMappingTemplate is required for JavaScript resolver function runtimes');
}
this.slotNames = new Set([...requestSlots, ...responseSlots]);
}
substituteSlotInfo(template, slotName, index) {
if (template.constructor.name === cdk_compat_1.S3MappingTemplate.name) {
template.substituteValues({
slotName,
slotIndex: index,
typeName: this.typeName,
fieldName: this.fieldName,
});
}
}
ensureNoneDataSource(api) {
if (!api.host.hasDataSource(exports.NONE_DATA_SOURCE_NAME)) {
api.host.addNoneDataSource(exports.NONE_DATA_SOURCE_NAME, {
name: exports.NONE_DATA_SOURCE_NAME,
description: 'None Data Source for Pipeline functions',
});
}
}
createStashStatementGenerator(runtime) {
const jsStash = (props) => {
const { name, value, object } = props;
const objectPrefix = object ? `.${object}` : '';
return `ctx.stash${objectPrefix}.${name} = ${value};`;
};
const generateJsStashStatement = {
stashExpression: (props) => jsStash(props),
stashString: (props) => jsStash({ ...props, value: `"${props.value}"` }),
};
const vtlStash = (props) => {
const { name, value, object } = props;
const objectPrefix = object ? `.${object}` : '';
return `$util.qr($ctx.stash${objectPrefix}.put("${name}", ${value}))`;
};
const generateVtlStashStatement = {
stashExpression: (props) => vtlStash(props),
stashString: (props) => vtlStash({ ...props, value: `"${props.value}"` }),
};
return (0, function_runtime_1.isJsResolverFnRuntime)(runtime) ? generateJsStashStatement : generateVtlStashStatement;
}
getMappingTemplateNames(mappingTemplate, fallbackName = '') {
var _a, _b, _c, _d, _e;
if ((0, function_runtime_1.isJsRuntimeTemplate)(mappingTemplate)) {
return [(_a = mappingTemplate.codeMappingTemplate.name) !== null && _a !== void 0 ? _a : fallbackName];
}
else {
const requestMappingTemplateName = (_c = (_b = mappingTemplate === null || mappingTemplate === void 0 ? void 0 : mappingTemplate.requestMappingTemplate) === null || _b === void 0 ? void 0 : _b.name) !== null && _c !== void 0 ? _c : fallbackName;
const responseMappingTemplateName = (_e = (_d = mappingTemplate === null || mappingTemplate === void 0 ? void 0 : mappingTemplate.responseMappingTemplate) === null || _d === void 0 ? void 0 : _d.name) !== null && _e !== void 0 ? _e : fallbackName;
return [requestMappingTemplateName, responseMappingTemplateName];
}
}
}
exports.TransformerResolver = TransformerResolver;
//# sourceMappingURL=resolver.js.map