UNPKG

@aws-amplify/graphql-transformer-core

Version:

A framework to transform from GraphQL SDL to AWS CloudFormation.

436 lines 25.8 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; }; 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