diff --git a/jest.config.js b/jest.config.js index 4fc7c7f591..03dd6ff519 100644 --- a/jest.config.js +++ b/jest.config.js @@ -18,6 +18,7 @@ module.exports = { collectCoverageFrom: ['src/**/.(ts|tsx|js|jsx)$', '!src/**/*.test.(ts|tsx|js|jsx)$', '!src/**/*.d.ts'], coverageDirectory: 'coverage', projects: [ + // '/packages/amplify-category-api', '/packages/amplify-graphql-api-construct', '/packages/amplify-graphql-auth-transformer', '/packages/amplify-graphql-default-value-transformer', diff --git a/packages/amplify-category-api/src/__tests__/graphql-transformer/transform-graphql-schema-v2.test.ts b/packages/amplify-category-api/src/__tests__/graphql-transformer/transform-graphql-schema-v2.test.ts index 540a83f696..935f326ecc 100644 --- a/packages/amplify-category-api/src/__tests__/graphql-transformer/transform-graphql-schema-v2.test.ts +++ b/packages/amplify-category-api/src/__tests__/graphql-transformer/transform-graphql-schema-v2.test.ts @@ -1,7 +1,7 @@ import { $TSContext, pathManager, ApiCategoryFacade } from '@aws-amplify/amplify-cli-core'; import { printer } from '@aws-amplify/amplify-prompts'; import { constructTransformerChain } from '@aws-amplify/graphql-transformer'; -import { constructDataSourceMap, DDB_DEFAULT_DATASOURCE_TYPE } from '@aws-amplify/graphql-transformer-core'; +import { DataSourceType, DynamoDBProvisionStrategy } from 'graphql-transformer-core'; import { getUserOverridenSlots, transformGraphQLSchemaV2 } from '../../graphql-transformer/transform-graphql-schema-v2'; import { generateTransformerOptions } from '../../graphql-transformer/transformer-options-v2'; import { getAppSyncAPIName } from '../../provider-utils/awscloudformation/utils/amplify-meta-utils'; @@ -51,17 +51,30 @@ describe('transformGraphQLSchemaV2', () => { pathManagerMock.getBackendDirPath.mockReturnValue('backenddir'); pathManagerMock.getCurrentCloudBackendDirPath.mockReturnValue('currentcloudbackenddir'); ApiCategoryFacadeMock.getTransformerVersion.mockReturnValue(Promise.resolve(2)); + const schema = ` type Todo @model @auth(rules: [{ allow: owner }]) { content: String } `; + // Intentionally generating the V1 flavor of the project config to emulate the Gen1 CLI flow. This is fixed up in the transformer + const modelToDatasourceMap = new Map([ + [ + 'Todo', + { + dbType: 'DYNAMODB', + provisionDB: true, + provisionStrategy: DynamoDBProvisionStrategy.DEFAULT, + }, + ], + ]); + generateTransformerOptionsMock.mockReturnValue({ projectConfig: { // schema that will generate auth warnings schema, config: { StackMapping: {} }, - modelToDatasourceMap: constructDataSourceMap(schema, DDB_DEFAULT_DATASOURCE_TYPE), + modelToDatasourceMap, }, transformersFactory: constructTransformerChain(), transformersFactoryArgs: {}, diff --git a/packages/amplify-category-api/src/__tests__/graphql-transformer/utils.test.ts b/packages/amplify-category-api/src/__tests__/graphql-transformer/utils.test.ts index b2b2b0959d..9069a3994f 100644 --- a/packages/amplify-category-api/src/__tests__/graphql-transformer/utils.test.ts +++ b/packages/amplify-category-api/src/__tests__/graphql-transformer/utils.test.ts @@ -1,7 +1,6 @@ import * as path from 'path'; import * as fs from 'fs-extra'; import { $TSContext, CloudformationProviderFacade, pathManager, JSONUtilities } from '@aws-amplify/amplify-cli-core'; -import { DataSourceType } from '@aws-amplify/graphql-transformer-interfaces'; import { mergeUserConfigWithTransformOutput, writeDeploymentToDisk, getAdminRoles } from '../../graphql-transformer/utils'; import { TransformerProjectConfig } from '../../graphql-transformer/cdk-compat/project-config'; import { DeploymentResources } from '../../graphql-transformer/cdk-compat/deployment-resources'; @@ -74,10 +73,8 @@ describe('graphql transformer utils', () => { pipelineFunctions: {}, resolvers: {}, stacks: {}, - modelToDatasourceMap: new Map(), + dataSourceStrategies: {}, config: { Version: 5, ElasticsearchWarning: true }, - customQueries: new Map(), - customSqlDataSourceStrategies: [], } as TransformerProjectConfig; }); @@ -98,10 +95,8 @@ describe('graphql transformer utils', () => { 'Query.listTodos.req.vtl': '$util.unauthorized\n', }, stacks: {}, - modelToDatasourceMap: new Map(), + dataSourceStrategies: {}, config: { Version: 5, ElasticsearchWarning: true }, - customQueries: new Map(), - customSqlDataSourceStrategies: [], } as TransformerProjectConfig; }); @@ -122,10 +117,8 @@ describe('graphql transformer utils', () => { }, resolvers: {}, stacks: {}, - modelToDatasourceMap: new Map(), + dataSourceStrategies: {}, config: { Version: 5, ElasticsearchWarning: true }, - customQueries: new Map(), - customSqlDataSourceStrategies: [], } as TransformerProjectConfig; }); @@ -210,7 +203,7 @@ describe('graphql transformer utils', () => { }, }, }, - modelToDatasourceMap: new Map(), + dataSourceStrategies: {}, config: { Version: 5, ElasticsearchWarning: true }, } as unknown as TransformerProjectConfig; }); diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/rds-utils.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/rds-utils.test.ts index e66810f388..a4a2c20ba0 100644 --- a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/rds-utils.test.ts +++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/rds-utils.test.ts @@ -1,9 +1,25 @@ +import { DataSourceStrategiesProvider, ModelDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; +import { DDB_DEFAULT_DATASOURCE_STRATEGY, MYSQL_DB_TYPE } from '@aws-amplify/graphql-transformer-core'; import { checkForUnsupportedDirectives } from '../../../../provider-utils/awscloudformation/utils/rds-resources/utils'; describe('check for unsupported RDS directives', () => { - const modelToDatasourceMap = new Map(); - modelToDatasourceMap.set('Post', { dbType: 'MYSQL' }); - modelToDatasourceMap.set('Tag', { dbType: 'DYNAMODB' }); + const dataSourceStrategies: Record = { + Post: { + name: 'mysqlstrategy', + dbType: MYSQL_DB_TYPE, + dbConnectionConfig: { + databaseNameSsmPath: '/databaseNameSsmPath', + hostnameSsmPath: '/hostnameSsmPath', + portSsmPath: '/portSsmPath', + usernameSsmPath: '/usernameSsmPath', + passwordSsmPath: '/passwordSsmPath', + }, + }, + Tag: DDB_DEFAULT_DATASOURCE_STRATEGY, + }; + + const dataSourceStrategiesProvider: DataSourceStrategiesProvider = { dataSourceStrategies }; + const emptyProvider: DataSourceStrategiesProvider = { dataSourceStrategies: {} }; it('should throw error if searchable directive is present on a model', () => { const schema = ` @@ -12,7 +28,7 @@ describe('check for unsupported RDS directives', () => { title: String! } `; - expect(() => checkForUnsupportedDirectives(schema, modelToDatasourceMap)).toThrowErrorMatchingInlineSnapshot( + expect(() => checkForUnsupportedDirectives(schema, dataSourceStrategiesProvider)).toThrowErrorMatchingInlineSnapshot( `"@searchable directive on type \\"Post\\" is not supported on a SQL datasource. Following directives are not supported on a SQL datasource: searchable, predictions, function, manyToMany, http, mapsTo"`, ); }); @@ -23,7 +39,7 @@ describe('check for unsupported RDS directives', () => { recognizeTextFromImage: String @predictions(actions: [identifyText]) } `; - expect(() => checkForUnsupportedDirectives(schema, modelToDatasourceMap)).toThrowErrorMatchingInlineSnapshot( + expect(() => checkForUnsupportedDirectives(schema, dataSourceStrategiesProvider)).toThrowErrorMatchingInlineSnapshot( `"@predictions directive on type \\"Query\\" and field \\"recognizeTextFromImage\\" is not supported on a SQL datasource. Following directives are not supported on a SQL datasource: searchable, predictions, function, manyToMany, http, mapsTo"`, ); }); @@ -34,7 +50,7 @@ describe('check for unsupported RDS directives', () => { echo(msg: String): String @function(name: "echofunction") } `; - expect(() => checkForUnsupportedDirectives(schema, modelToDatasourceMap)).toThrowErrorMatchingInlineSnapshot( + expect(() => checkForUnsupportedDirectives(schema, dataSourceStrategiesProvider)).toThrowErrorMatchingInlineSnapshot( `"@function directive on type \\"Query\\" and field \\"echo\\" is not supported on a SQL datasource. Following directives are not supported on a SQL datasource: searchable, predictions, function, manyToMany, http, mapsTo"`, ); }); @@ -54,7 +70,7 @@ describe('check for unsupported RDS directives', () => { posts: [Post] @manyToMany(relationName: "PostTags") } `; - expect(() => checkForUnsupportedDirectives(schema, modelToDatasourceMap)).toThrowErrorMatchingInlineSnapshot( + expect(() => checkForUnsupportedDirectives(schema, dataSourceStrategiesProvider)).toThrowErrorMatchingInlineSnapshot( `"@manyToMany directive on type \\"Post\\" and field \\"tags\\" is not supported on a SQL datasource. Following directives are not supported on a SQL datasource: searchable, predictions, function, manyToMany, http, mapsTo"`, ); }); @@ -72,7 +88,7 @@ describe('check for unsupported RDS directives', () => { listPosts: [Post] @http(url: "https://www.example.com/posts") } `; - expect(() => checkForUnsupportedDirectives(schema, modelToDatasourceMap)).toThrowErrorMatchingInlineSnapshot( + expect(() => checkForUnsupportedDirectives(schema, dataSourceStrategiesProvider)).toThrowErrorMatchingInlineSnapshot( `"@http directive on type \\"Query\\" and field \\"listPosts\\" is not supported on a SQL datasource. Following directives are not supported on a SQL datasource: searchable, predictions, function, manyToMany, http, mapsTo"`, ); }); @@ -84,38 +100,48 @@ describe('check for unsupported RDS directives', () => { title: String! } `; - expect(() => checkForUnsupportedDirectives(schema, modelToDatasourceMap)).toThrowErrorMatchingInlineSnapshot( + expect(() => checkForUnsupportedDirectives(schema, dataSourceStrategiesProvider)).toThrowErrorMatchingInlineSnapshot( `"@mapsTo directive on type \\"Post\\" is not supported on a SQL datasource. Following directives are not supported on a SQL datasource: searchable, predictions, function, manyToMany, http, mapsTo"`, ); }); it('should not throw error if there are only DDB models', () => { - const modelToDatasourceMap = new Map(); - modelToDatasourceMap.set('Post', { dbType: 'DYNAMODB' }); const schema = ` type Post @model @mapsTo(name: "Article") { id: ID! title: String! } `; - expect(() => checkForUnsupportedDirectives(schema, modelToDatasourceMap)).not.toThrowError(); + const ddbProvider: DataSourceStrategiesProvider = { + dataSourceStrategies: { + Post: DDB_DEFAULT_DATASOURCE_STRATEGY, + }, + }; + expect(() => checkForUnsupportedDirectives(schema, ddbProvider)).not.toThrowError(); }); it('early return if model_to_datasource map is empty or undefined', () => { - const modelToDatasourceMap = new Map(); const schema = ` type Post @model @mapsTo(name: "Article") { id: ID! title: String! } `; - expect(() => checkForUnsupportedDirectives(schema, modelToDatasourceMap)).not.toThrowError(); + expect(() => checkForUnsupportedDirectives(schema, emptyProvider)).not.toThrowError(); + }); + + it('early return for a schema with no models', () => { + const schema = ` + type Post { + id: ID! + title: String! + } + `; + expect(() => checkForUnsupportedDirectives(schema, emptyProvider)).not.toThrowError(); }); it('early return if schema is empty or undefined', () => { - const modelToDatasourceMap = new Map(); - modelToDatasourceMap.set('Post', { dbType: 'MYSQL' }); const schema = ''; - expect(() => checkForUnsupportedDirectives(schema, modelToDatasourceMap)).not.toThrowError(); + expect(() => checkForUnsupportedDirectives(schema, dataSourceStrategiesProvider)).not.toThrowError(); }); }); diff --git a/packages/amplify-category-api/src/graphql-transformer/cdk-compat/project-config.ts b/packages/amplify-category-api/src/graphql-transformer/cdk-compat/project-config.ts index 11103200a5..d193eaf6c1 100644 --- a/packages/amplify-category-api/src/graphql-transformer/cdk-compat/project-config.ts +++ b/packages/amplify-category-api/src/graphql-transformer/cdk-compat/project-config.ts @@ -1,4 +1,4 @@ -import { CustomSqlDataSourceStrategy, DataSourceType } from '@aws-amplify/graphql-transformer-interfaces'; +import { DataSourceStrategiesProvider } from '@aws-amplify/graphql-transformer-interfaces'; import { TransformConfig } from 'graphql-transformer-core'; export type Template = { @@ -8,14 +8,11 @@ export type Template = { Conditions: Record; }; -export interface TransformerProjectConfig { +export interface TransformerProjectConfig extends DataSourceStrategiesProvider { schema: string; functions: Record; pipelineFunctions: Record; resolvers: Record; stacks: Record; config: TransformConfig; - modelToDatasourceMap: Map; - customSqlDataSourceStrategies: CustomSqlDataSourceStrategy[]; - customQueries: Map; } diff --git a/packages/amplify-category-api/src/graphql-transformer/transform-graphql-schema-v2.ts b/packages/amplify-category-api/src/graphql-transformer/transform-graphql-schema-v2.ts index 07388e78c3..e459a8a1be 100644 --- a/packages/amplify-category-api/src/graphql-transformer/transform-graphql-schema-v2.ts +++ b/packages/amplify-category-api/src/graphql-transformer/transform-graphql-schema-v2.ts @@ -1,19 +1,27 @@ import path from 'path'; import { - getEngineFromDBType, - getImportedRDSType, - isImportedRDSType, - RDSConnectionSecrets, + getImportedRDSTypeFromStrategyDbType, + isSqlDbType, UserDefinedSlot, + DDB_DEFAULT_DATASOURCE_STRATEGY, + constructDataSourceStrategies, + constructSqlDirectiveDataSourceStrategies, + isDynamoDbType, + DDB_AMPLIFY_MANAGED_DATASOURCE_STRATEGY, } from '@aws-amplify/graphql-transformer-core'; import { AppSyncAuthConfiguration, + DataSourceStrategiesProvider, + ModelDataSourceStrategy, + ModelDataSourceStrategyDbType, + ModelDataSourceStrategySqlDbType, + RDSLayerMapping, + SQLLambdaModelDataSourceStrategy, + SqlDirectiveDataSourceStrategy, + SqlModelDataSourceDbConnectionConfig, TransformerLog, TransformerLogLevel, VpcConfig, - RDSLayerMapping, - DataSourceType, - ModelDataSourceStrategyDbType, } from '@aws-amplify/graphql-transformer-interfaces'; import * as fs from 'fs-extra'; import { ResourceConstants } from 'graphql-transformer-common'; @@ -26,7 +34,7 @@ import { getHostVpc } from '@aws-amplify/graphql-schema-generator'; import fetch from 'node-fetch'; import { getConnectionSecrets, - getExistingConnectionSecretNames, + getExistingConnectionDbConnectionConfig, getSecretsKey, } from '../provider-utils/awscloudformation/utils/rds-resources/database-resources'; import { getAppSyncAPIName } from '../provider-utils/awscloudformation/utils/amplify-meta-utils'; @@ -162,6 +170,8 @@ export const transformGraphQLSchemaV2 = async (context: $TSContext, options): Pr fs.ensureDirSync(buildDir); } + // The buildConfig.projectConfig returned by `generateTransformerOptions` is not actually compatible with DataSourceStrategiesProvider. We + // will correct that in buildAPIProject. const buildConfig: TransformerProjectOptions = await generateTransformerOptions(context, options); if (!buildConfig) { return undefined; @@ -193,29 +203,118 @@ const hasIamAuth = (authConfig?: AppSyncAuthConfiguration): boolean => const hasUserPoolAuth = (authConfig?: AppSyncAuthConfiguration): boolean => getAuthenticationTypesForAuthConfig(authConfig).some((authType) => authType === 'AMAZON_COGNITO_USER_POOLS'); +/** + * Given an array of DataSourceType shapes from the Gen1 CLI import flow, finds the single SQL database type. Throws an error if more than + * one SQL database type is detected. + */ +const getSqlDbTypeFromDataSourceTypes = ( + dataSourceTypes: Array<{ + dbType: ModelDataSourceStrategyDbType; + provisionDB: boolean; + provisionStrategy: 'DEFAULT' | 'AMPLIFY_TABLE'; + }>, +): ModelDataSourceStrategySqlDbType | undefined => { + const dbTypes = Object.values(dataSourceTypes) + .map((dsType) => dsType.dbType) + .filter(isSqlDbType); + if (dbTypes.length === 0) { + return undefined; + } + if (new Set(dbTypes).size > 1) { + throw new Error(`Multiple imported SQL datasource types ${Array.from(dbTypes)} are detected. Only one type is supported.`); + } + return dbTypes[0]; +}; + +/** + * Utility to fix up the project config generated by the Gen1 CLI flow into the ModelDataSourceStrategy types expected by the transformer + * internals. Internally, this function makes network calls to retrieve the database connection parameter info from SSM, and uses those + * values to discover VPC configurations for the database. + */ +const fixUpDataSourceStrategiesProvider = async (context: $TSContext, projectConfig: any): Promise => { + const modelToDatasourceMap = projectConfig.modelToDatasourceMap ?? new Map(); + const datasourceMapValues: Array<{ + dbType: ModelDataSourceStrategyDbType; + provisionDB: boolean; + provisionStrategy: 'DEFAULT' | 'AMPLIFY_TABLE'; + }> = modelToDatasourceMap ? Array.from(modelToDatasourceMap.values()) : []; + + // We allow a DynamoDB and SQL data source to live alongside each other, although we don't (yet) support relationships between them. We'll + // process the dbTypes separately so we can validate that there is only one SQL source. + const dataSourceStrategies: Record = {}; + modelToDatasourceMap.forEach((value, key) => { + if (!isDynamoDbType(value.dbType)) { + return; + } + switch (value.provisionStrategy) { + case 'DEFAULT': + dataSourceStrategies[key] = DDB_DEFAULT_DATASOURCE_STRATEGY; + break; + case 'AMPLIFY_TABLE': + dataSourceStrategies[key] = DDB_AMPLIFY_MANAGED_DATASOURCE_STRATEGY; + break; + default: + throw new Error(`Unsupported provisionStrategy ${value.provisionStrategy}`); + } + }); + + const sqlDbType = getSqlDbTypeFromDataSourceTypes(datasourceMapValues); + let sqlDirectiveDataSourceStrategies: SqlDirectiveDataSourceStrategy[] | undefined; + if (sqlDbType) { + const dbConnectionConfig = getDbConnectionConfig(); + const vpcConfiguration = await isSqlLambdaVpcConfigRequired(context, sqlDbType); + const strategy: SQLLambdaModelDataSourceStrategy = { + name: `${sqlDbType}DataSourceStrategy`, + dbType: sqlDbType, + dbConnectionConfig, + vpcConfiguration, + }; + modelToDatasourceMap.forEach((value, key) => { + if (!isSqlDbType(value.dbType)) { + return; + } + dataSourceStrategies[key] = strategy; + }); + + let customSqlStatements: Record | undefined; + if (typeof projectConfig.customQueries === 'object') { + customSqlStatements = {}; + (projectConfig.customQueries as Map).forEach((value, key) => { + customSqlStatements[key] = value; + }); + } + + sqlDirectiveDataSourceStrategies = constructSqlDirectiveDataSourceStrategies(projectConfig.schema, strategy, customSqlStatements); + } + + return { + dataSourceStrategies, + sqlDirectiveDataSourceStrategies, + }; +}; + /** * buildAPIProject - * Note that this explicitly does not support the ability to declare a schema without a `@model`. + * + * Note that SQL-backed API support is quite limited in this function. Notably: + * - It requires there is only one SQL data source in the API + * - It does not support declaring a SQL schema without a `@model` tied to a SQL data source + * + * TODO: Remove SQL handling from Gen1 CLI. */ const buildAPIProject = async (context: $TSContext, opts: TransformerProjectOptions): Promise => { + // The incoming opts.projectConfig is not actually compatible with DataSourceStrategiesProvider. We will correct that in this function. + const schema = opts.projectConfig.schema.toString(); // Skip building the project if the schema is blank if (!schema) { return undefined; } - checkForUnsupportedDirectives(schema, opts.projectConfig.modelToDatasourceMap); - - const { modelToDatasourceMap } = opts.projectConfig; - const datasourceMapValues: Array = modelToDatasourceMap ? Array.from(modelToDatasourceMap.values()) : []; - let datasourceSecretMap: Map | undefined = undefined; - let sqlLambdaVpcConfig: VpcConfig | undefined; - if (datasourceMapValues.some((value) => isImportedRDSType(value))) { - const dbType = getImportedRDSType(modelToDatasourceMap); - datasourceSecretMap = new Map(); - datasourceSecretMap.set(dbType, await getRDSConnectionSecrets(context)); - sqlLambdaVpcConfig = await isSqlLambdaVpcConfigRequired(context, dbType); - } + const { dataSourceStrategies, sqlDirectiveDataSourceStrategies } = await fixUpDataSourceStrategiesProvider(context, opts.projectConfig); + + checkForUnsupportedDirectives(schema, { dataSourceStrategies }); + const rdsLayerMapping = await getRDSLayerMapping(); const transformManager = new TransformManager( @@ -234,11 +333,9 @@ const buildAPIProject = async (context: $TSContext, opts: TransformerProjectOpti parameterProvider: transformManager.getParameterProvider(), synthParameters: transformManager.getSynthParameters(), schema, - modelToDatasourceMap: opts.projectConfig.modelToDatasourceMap, - customQueries: opts.projectConfig.customQueries, - datasourceSecretParameterLocations: datasourceSecretMap, + dataSourceStrategies, + sqlDirectiveDataSourceStrategies, printTransformerLog, - sqlLambdaVpcConfig, rdsLayerMapping, }); @@ -292,15 +389,15 @@ const isSqlLambdaVpcConfigRequired = async (context: $TSContext, dbType: ModelDa return vpcSubnetConfig; }; -const getRDSConnectionSecrets = async (context: $TSContext): Promise => { +const getDbConnectionConfig = (): SqlModelDataSourceDbConnectionConfig => { const apiName = getAppSyncAPIName(); - const secretsKey = await getSecretsKey(); - const rdsSecretPaths = await getExistingConnectionSecretNames(context, apiName, secretsKey); - return rdsSecretPaths; + const secretsKey = getSecretsKey(); + const paths = getExistingConnectionDbConnectionConfig(apiName, secretsKey); + return paths; }; const getSQLLambdaVpcConfig = async (context: $TSContext, dbType: ModelDataSourceStrategyDbType): Promise => { - const [secretsKey, engine] = [getSecretsKey(), getEngineFromDBType(dbType)]; + const [secretsKey, engine] = [getSecretsKey(), getImportedRDSTypeFromStrategyDbType(dbType)]; const { secrets } = await getConnectionSecrets(context, secretsKey, engine); const region = context.amplify.getProjectMeta().providers.awscloudformation.Region; const vpcConfig = await getHostVpc(secrets.host, region); diff --git a/packages/amplify-category-api/src/graphql-transformer/transformer-options-v2.ts b/packages/amplify-category-api/src/graphql-transformer/transformer-options-v2.ts index 045da24bf2..ad11a45a79 100644 --- a/packages/amplify-category-api/src/graphql-transformer/transformer-options-v2.ts +++ b/packages/amplify-category-api/src/graphql-transformer/transformer-options-v2.ts @@ -165,6 +165,8 @@ export const generateTransformerOptions = async (context: $TSContext, options: a S3DeploymentRootKey: deploymentRootKey, }; + // The project configuration loaded here uses the Gen1 CLI DataSourceTypes and modelToDatasourceMap to hold model information. We'll + // convert it to the supported ModelDataSourceStrategy types later. const project = await loadProject(resourceDir); const lastDeployedProjectConfig = fs.existsSync(previouslyDeployedBackendDir) @@ -239,6 +241,7 @@ export const generateTransformerOptions = async (context: $TSContext, options: a }, rootStackFileName: 'cloudformation-template.json', currentCloudBackendDirectory: previouslyDeployedBackendDir, + // Reminder that `project` has type `any`, and is not actually compatible with DataSourceStrategiesProvider. We will correct that later. projectConfig: project, lastDeployedProjectConfig, authConfig, diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/rds-resources/database-resources.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/rds-resources/database-resources.ts index c34b04600c..287e9a2ebe 100644 --- a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/rds-resources/database-resources.ts +++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/rds-resources/database-resources.ts @@ -10,11 +10,19 @@ import { MySQLDataSourceAdapter, DataSourceAdapter, MySQLDataSourceConfig } from import { printer } from '@aws-amplify/amplify-prompts'; import { DeleteFunctionCommand, LambdaClient } from '@aws-sdk/client-lambda'; import { DeleteRoleCommand, IAMClient } from '@aws-sdk/client-iam'; +import { SqlModelDataSourceDbConnectionConfig } from '@aws-amplify/graphql-transformer-interfaces'; import { getAppSyncAPIName } from '../amplify-meta-utils'; import { databaseConfigurationInputWalkthrough } from '../../service-walkthroughs/appSync-rds-db-config'; import { SSMClient } from './ssmClient'; const secretNames = ['database', 'host', 'port', 'username', 'password']; +const secretNamesToDbConnectionConfigFields: Record = { + database: 'databaseNameSsmPath', + host: 'hostnameSsmPath', + port: 'portSsmPath', + username: 'usernameSsmPath', + password: 'passwordSsmPath', +}; // eslint-disable-next-line @typescript-eslint/no-explicit-any const isConnectionSecrets = (obj: any): obj is RDSConnectionSecrets => { @@ -93,6 +101,20 @@ export const getExistingConnectionSecrets = async ( } }; +/** + * Derives expected path names for database connection config parameters stored during the Gen1 CLI import flow. + */ +export const getExistingConnectionDbConnectionConfig = (apiName: string, secretsKey: string): SqlModelDataSourceDbConnectionConfig => { + const environmentName = stateManager.getCurrentEnvName(); + const appId = stateManager.getAppID(); + const dbConnectionConfig: any = {}; + secretNames.forEach((name) => { + const path = getParameterStoreSecretPath(name, secretsKey, apiName, environmentName, appId); + dbConnectionConfig[secretNamesToDbConnectionConfigFields[name]] = path; + }); + return dbConnectionConfig; +}; + /** * Get SSM paths for database connection information * @param context the Amplify CLI context diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/rds-resources/utils.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/rds-resources/utils.ts index e1fb2f0e94..a6c7098fa4 100644 --- a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/rds-resources/utils.ts +++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/rds-resources/utils.ts @@ -1,17 +1,16 @@ import { parse, FieldDefinitionNode, ObjectTypeDefinitionNode, visit } from 'graphql'; import _ from 'lodash'; -import { isImportedRDSType } from '@aws-amplify/graphql-transformer-core'; -import { DataSourceType } from '@aws-amplify/graphql-transformer-interfaces'; +import { isSqlStrategy } from '@aws-amplify/graphql-transformer-core'; +import { DataSourceStrategiesProvider } from '@aws-amplify/graphql-transformer-interfaces'; -export const checkForUnsupportedDirectives = (schema: string, modelToDatasourceMap: Map): void => { +export const checkForUnsupportedDirectives = (schema: string, context: DataSourceStrategiesProvider): void => { const unsupportedRDSDirectives = ['searchable', 'predictions', 'function', 'manyToMany', 'http', 'mapsTo']; - if (_.isEmpty(schema) || _.isEmpty(modelToDatasourceMap)) { + if (_.isEmpty(schema) || _.isEmpty(context.dataSourceStrategies)) { return; } - // get all the models in the modelToDatasourceMap that are backed by RDS whose value is present in the db_type property inside the map - const rdsModels = Array.from(modelToDatasourceMap?.entries()) - .filter(([key, value]) => isImportedRDSType(value)) + const rdsModels = Object.entries(context.dataSourceStrategies) + .filter(([key, value]) => isSqlStrategy(value)) .map(([key, value]) => key); if (_.isEmpty(rdsModels)) { @@ -70,4 +69,5 @@ const getParentName = (ancestors: any[]): string | undefined => { if (ancestors && ancestors?.length > 0) { return (ancestors[ancestors.length - 1] as ObjectTypeDefinitionNode)?.name?.value; } + return undefined; }; diff --git a/packages/amplify-graphql-api-construct/.jsii b/packages/amplify-graphql-api-construct/.jsii index 85cbfa4522..6e84664d08 100644 --- a/packages/amplify-graphql-api-construct/.jsii +++ b/packages/amplify-graphql-api-construct/.jsii @@ -3675,7 +3675,7 @@ "fqn": "@aws-amplify/graphql-api-construct.AmplifyDynamoDbModelDataSourceStrategy", "kind": "interface", "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 41 }, "name": "AmplifyDynamoDbModelDataSourceStrategy", @@ -3687,7 +3687,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 42 }, "name": "dbType", @@ -3702,7 +3702,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 43 }, "name": "provisionStrategy", @@ -3711,7 +3711,7 @@ } } ], - "symbolId": "src/model-datasource-strategy:AmplifyDynamoDbModelDataSourceStrategy" + "symbolId": "src/model-datasource-strategy-types:AmplifyDynamoDbModelDataSourceStrategy" }, "@aws-amplify/graphql-api-construct.AmplifyDynamoDbTableWrapper": { "assembly": "@aws-amplify/graphql-api-construct", @@ -3928,7 +3928,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 141 + "line": 138 }, "parameters": [ { @@ -3963,7 +3963,7 @@ "kind": "class", "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 87 + "line": 84 }, "methods": [ { @@ -3975,7 +3975,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 356 + "line": 272 }, "name": "addDynamoDbDataSource", "parameters": [ @@ -4024,7 +4024,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 368 + "line": 284 }, "name": "addElasticsearchDataSource", "parameters": [ @@ -4071,7 +4071,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 378 + "line": 294 }, "name": "addEventBridgeDataSource", "parameters": [ @@ -4118,7 +4118,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 460 + "line": 376 }, "name": "addFunction", "parameters": [ @@ -4153,7 +4153,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 389 + "line": 305 }, "name": "addHttpDataSource", "parameters": [ @@ -4201,7 +4201,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 400 + "line": 316 }, "name": "addLambdaDataSource", "parameters": [ @@ -4249,7 +4249,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 411 + "line": 327 }, "name": "addNoneDataSource", "parameters": [ @@ -4288,7 +4288,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 422 + "line": 338 }, "name": "addOpenSearchDataSource", "parameters": [ @@ -4336,7 +4336,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 435 + "line": 351 }, "name": "addRdsDataSource", "parameters": [ @@ -4403,7 +4403,7 @@ }, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 451 + "line": 367 }, "name": "addResolver", "parameters": [ @@ -4444,7 +4444,7 @@ "immutable": true, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 122 + "line": 119 }, "name": "apiId", "type": { @@ -4459,7 +4459,7 @@ "immutable": true, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 102 + "line": 99 }, "name": "generatedFunctionSlots", "type": { @@ -4492,7 +4492,7 @@ "immutable": true, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 107 + "line": 104 }, "name": "graphqlUrl", "type": { @@ -4508,7 +4508,7 @@ "immutable": true, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 112 + "line": 109 }, "name": "realtimeUrl", "type": { @@ -4523,7 +4523,7 @@ "immutable": true, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 91 + "line": 88 }, "name": "resources", "type": { @@ -4539,7 +4539,7 @@ "immutable": true, "locationInModule": { "filename": "src/amplify-graphql-api.ts", - "line": 117 + "line": 114 }, "name": "apiKey", "optional": true, @@ -5797,7 +5797,7 @@ "fqn": "@aws-amplify/graphql-api-construct.CustomSqlDataSourceStrategy", "kind": "interface", "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 157 }, "name": "CustomSqlDataSourceStrategy", @@ -5805,12 +5805,13 @@ { "abstract": true, "docs": { - "stability": "stable" + "stability": "stable", + "summary": "The field name with which the custom SQL is associated." }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", - "line": 159 + "filename": "src/model-datasource-strategy-types.ts", + "line": 162 }, "name": "fieldName", "type": { @@ -5820,12 +5821,13 @@ { "abstract": true, "docs": { - "stability": "stable" + "stability": "stable", + "summary": "The strategy used to create the datasource that will resolve the custom SQL statement." }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", - "line": 160 + "filename": "src/model-datasource-strategy-types.ts", + "line": 165 }, "name": "strategy", "type": { @@ -5835,12 +5837,13 @@ { "abstract": true, "docs": { - "stability": "stable" + "stability": "stable", + "summary": "The built-in type (either \"Query\" or \"Mutation\") with which the custom SQL is associated." }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", - "line": 158 + "filename": "src/model-datasource-strategy-types.ts", + "line": 159 }, "name": "typeName", "type": { @@ -5848,7 +5851,7 @@ } } ], - "symbolId": "src/model-datasource-strategy:CustomSqlDataSourceStrategy" + "symbolId": "src/model-datasource-strategy-types:CustomSqlDataSourceStrategy" }, "@aws-amplify/graphql-api-construct.DefaultDynamoDbModelDataSourceStrategy": { "assembly": "@aws-amplify/graphql-api-construct", @@ -5860,7 +5863,7 @@ "fqn": "@aws-amplify/graphql-api-construct.DefaultDynamoDbModelDataSourceStrategy", "kind": "interface", "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 32 }, "name": "DefaultDynamoDbModelDataSourceStrategy", @@ -5872,7 +5875,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 33 }, "name": "dbType", @@ -5887,7 +5890,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 34 }, "name": "provisionStrategy", @@ -5896,7 +5899,7 @@ } } ], - "symbolId": "src/model-datasource-strategy:DefaultDynamoDbModelDataSourceStrategy" + "symbolId": "src/model-datasource-strategy-types:DefaultDynamoDbModelDataSourceStrategy" }, "@aws-amplify/graphql-api-construct.FunctionSlotBase": { "assembly": "@aws-amplify/graphql-api-construct", @@ -6893,7 +6896,7 @@ "fqn": "@aws-amplify/graphql-api-construct.ProvisionedConcurrencyConfig", "kind": "interface", "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 108 }, "name": "ProvisionedConcurrencyConfig", @@ -6907,7 +6910,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 110 }, "name": "provisionedConcurrentExecutions", @@ -6916,7 +6919,7 @@ } } ], - "symbolId": "src/model-datasource-strategy:ProvisionedConcurrencyConfig" + "symbolId": "src/model-datasource-strategy-types:ProvisionedConcurrencyConfig" }, "@aws-amplify/graphql-api-construct.ProvisionedThroughput": { "assembly": "@aws-amplify/graphql-api-construct", @@ -7032,7 +7035,7 @@ "fqn": "@aws-amplify/graphql-api-construct.SQLLambdaModelDataSourceStrategy", "kind": "interface", "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 51 }, "name": "SQLLambdaModelDataSourceStrategy", @@ -7045,7 +7048,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 66 }, "name": "dbConnectionConfig", @@ -7061,7 +7064,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 61 }, "name": "dbType", @@ -7078,7 +7081,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 56 }, "name": "name", @@ -7095,7 +7098,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 77 }, "name": "customSqlStatements", @@ -7117,7 +7120,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 82 }, "name": "sqlLambdaProvisionedConcurrencyConfig", @@ -7134,7 +7137,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 71 }, "name": "vpcConfiguration", @@ -7144,7 +7147,7 @@ } } ], - "symbolId": "src/model-datasource-strategy:SQLLambdaModelDataSourceStrategy" + "symbolId": "src/model-datasource-strategy-types:SQLLambdaModelDataSourceStrategy" }, "@aws-amplify/graphql-api-construct.SQLLambdaModelDataSourceStrategyFactory": { "assembly": "@aws-amplify/graphql-api-construct", @@ -7317,7 +7320,7 @@ "fqn": "@aws-amplify/graphql-api-construct.SqlModelDataSourceDbConnectionConfig", "kind": "interface", "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 133 }, "name": "SqlModelDataSourceDbConnectionConfig", @@ -7330,7 +7333,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 149 }, "name": "databaseNameSsmPath", @@ -7347,7 +7350,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 137 }, "name": "hostnameSsmPath", @@ -7363,7 +7366,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 146 }, "name": "passwordSsmPath", @@ -7379,7 +7382,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 140 }, "name": "portSsmPath", @@ -7395,7 +7398,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 143 }, "name": "usernameSsmPath", @@ -7404,7 +7407,7 @@ } } ], - "symbolId": "src/model-datasource-strategy:SqlModelDataSourceDbConnectionConfig" + "symbolId": "src/model-datasource-strategy-types:SqlModelDataSourceDbConnectionConfig" }, "@aws-amplify/graphql-api-construct.StreamSpecification": { "assembly": "@aws-amplify/graphql-api-construct", @@ -7452,7 +7455,7 @@ "fqn": "@aws-amplify/graphql-api-construct.SubnetAvailabilityZone", "kind": "interface", "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 119 }, "name": "SubnetAvailabilityZone", @@ -7465,7 +7468,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 124 }, "name": "availabilityZone", @@ -7481,7 +7484,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 121 }, "name": "subnetId", @@ -7490,7 +7493,7 @@ } } ], - "symbolId": "src/model-datasource-strategy:SubnetAvailabilityZone" + "symbolId": "src/model-datasource-strategy-types:SubnetAvailabilityZone" }, "@aws-amplify/graphql-api-construct.SubscriptionFunctionSlot": { "assembly": "@aws-amplify/graphql-api-construct", @@ -7893,7 +7896,7 @@ "fqn": "@aws-amplify/graphql-api-construct.VpcConfig", "kind": "interface", "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 93 }, "name": "VpcConfig", @@ -7906,7 +7909,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 98 }, "name": "securityGroupIds", @@ -7927,7 +7930,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 101 }, "name": "subnetAvailabilityZoneConfig", @@ -7948,7 +7951,7 @@ }, "immutable": true, "locationInModule": { - "filename": "src/model-datasource-strategy.ts", + "filename": "src/model-datasource-strategy-types.ts", "line": 95 }, "name": "vpcId", @@ -7957,9 +7960,9 @@ } } ], - "symbolId": "src/model-datasource-strategy:VpcConfig" + "symbolId": "src/model-datasource-strategy-types:VpcConfig" } }, "version": "1.4.3", - "fingerprint": "cuAzPvIpUk9/V6diCUbNbBrOumGIc4yJ7vCxD8JrAR0=" + "fingerprint": "M1VBfKntwKlt3Ze9qevVcis3EmuUHpT8ZSBfsiR8QXY=" } \ No newline at end of file diff --git a/packages/amplify-graphql-api-construct/API.md b/packages/amplify-graphql-api-construct/API.md index 3847b86198..a6bded278b 100644 --- a/packages/amplify-graphql-api-construct/API.md +++ b/packages/amplify-graphql-api-construct/API.md @@ -198,11 +198,8 @@ export interface CustomConflictResolutionStrategy extends ConflictResolutionStra // @public export interface CustomSqlDataSourceStrategy { - // (undocumented) readonly fieldName: string; - // (undocumented) readonly strategy: SQLLambdaModelDataSourceStrategy; - // (undocumented) readonly typeName: 'Query' | 'Mutation'; } diff --git a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/sql-model-definition.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/sql-model-definition.test.ts index def232de86..48da4d8952 100644 --- a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/sql-model-definition.test.ts +++ b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/sql-model-definition.test.ts @@ -3,7 +3,7 @@ import * as cognito from 'aws-cdk-lib/aws-cognito'; import { CfnFunction, CfnAlias } from 'aws-cdk-lib/aws-lambda'; import { AmplifyGraphqlApi } from '../../amplify-graphql-api'; import { AmplifyGraphqlDefinition } from '../../amplify-graphql-definition'; -import { SqlModelDataSourceDbConnectionConfig, VpcConfig } from '../../model-datasource-strategy'; +import { SqlModelDataSourceDbConnectionConfig, VpcConfig } from '../../model-datasource-strategy-types'; const defaultSchema = /* GraphQL */ ` type Todo @model @auth(rules: [{ allow: owner }]) { diff --git a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/sql-model-strategy.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/sql-model-strategy.test.ts index 7ff40a0fbf..de8e5feeaa 100644 --- a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/sql-model-strategy.test.ts +++ b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/sql-model-strategy.test.ts @@ -2,7 +2,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { SQLLambdaModelDataSourceStrategyFactory } from '../../sql-model-datasource-strategy'; -import { SQLLambdaModelDataSourceStrategy } from '../../model-datasource-strategy'; +import { SQLLambdaModelDataSourceStrategy } from '../../model-datasource-strategy-types'; describe('SQL bound API definitions', () => { let tmpDir: string; diff --git a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/sql-no-model.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/sql-no-model.test.ts index 3793457f96..2c492485d2 100644 --- a/packages/amplify-graphql-api-construct/src/__tests__/__functional__/sql-no-model.test.ts +++ b/packages/amplify-graphql-api-construct/src/__tests__/__functional__/sql-no-model.test.ts @@ -4,7 +4,7 @@ import * as cdk from 'aws-cdk-lib'; import * as cognito from 'aws-cdk-lib/aws-cognito'; import { AmplifyGraphqlApi } from '../../amplify-graphql-api'; import { AmplifyGraphqlDefinition } from '../../amplify-graphql-definition'; -import { SQLLambdaModelDataSourceStrategy } from '../../model-datasource-strategy'; +import { SQLLambdaModelDataSourceStrategy } from '../../model-datasource-strategy-types'; const NO_MODEL_SCHEMA = /* GraphQL */ ` type Todo { diff --git a/packages/amplify-graphql-api-construct/src/__tests__/amplify-graphql-definition.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/amplify-graphql-definition.test.ts index 513a6548b5..aa7f104c27 100644 --- a/packages/amplify-graphql-api-construct/src/__tests__/amplify-graphql-definition.test.ts +++ b/packages/amplify-graphql-api-construct/src/__tests__/amplify-graphql-definition.test.ts @@ -4,7 +4,7 @@ import * as os from 'os'; import { IFunction } from 'aws-cdk-lib/aws-lambda'; import { AmplifyGraphqlDefinition, DEFAULT_MODEL_DATA_SOURCE_STRATEGY } from '../amplify-graphql-definition'; import { IAmplifyGraphqlDefinition } from '../types'; -import { ModelDataSourceStrategy, SQLLambdaModelDataSourceStrategy } from '../model-datasource-strategy'; +import { ModelDataSourceStrategy, SQLLambdaModelDataSourceStrategy } from '../model-datasource-strategy-types'; const TEST_SCHEMA = /* GraphQL */ ` type Todo @model { diff --git a/packages/amplify-graphql-api-construct/src/__tests__/internal/data-source-config.test.ts b/packages/amplify-graphql-api-construct/src/__tests__/internal/data-source-config.test.ts index 10b2464694..daeb339171 100644 --- a/packages/amplify-graphql-api-construct/src/__tests__/internal/data-source-config.test.ts +++ b/packages/amplify-graphql-api-construct/src/__tests__/internal/data-source-config.test.ts @@ -1,75 +1,124 @@ -import { DynamoDBProvisionStrategy, SQLLambdaModelProvisionStrategy } from '@aws-amplify/graphql-transformer-interfaces'; -import { DDB_AMPLIFY_MANAGED_DATASOURCE_STRATEGY } from '@aws-amplify/graphql-transformer-core'; -import { parseDataSourceConfig } from '../../internal/data-source-config'; -import { ModelDataSourceStrategy } from '../../model-datasource-strategy'; +import { constructCustomSqlDataSourceStrategies, getDataSourceStrategiesProvider } from '../../internal/data-source-config'; +import { SQLLambdaModelDataSourceStrategy, SqlModelDataSourceDbConnectionConfig } from '../../model-datasource-strategy-types'; +import { IAmplifyGraphqlDefinition } from '../../types'; describe('datasource config', () => { - it('should parse the datasource config correctly', () => { - const input: Record = { - Todo: { - dbType: 'DYNAMODB', - provisionStrategy: 'DEFAULT', - }, - Author: DDB_AMPLIFY_MANAGED_DATASOURCE_STRATEGY, - Post: { - name: 'mysqlTable', - dbType: 'MYSQL', - dbConnectionConfig: { - hostnameSsmPath: 'hostnameSsmPath', - portSsmPath: 'portSsmPath', - usernameSsmPath: 'usernameSsmPath', - passwordSsmPath: 'passwordSsmPath', - databaseNameSsmPath: 'databaseNameSsmPath', + const dbConnectionConfig: SqlModelDataSourceDbConnectionConfig = { + hostnameSsmPath: '/test/hostname', + portSsmPath: '/test/port', + usernameSsmPath: '/test/username', + passwordSsmPath: '/test/password', + databaseNameSsmPath: '/test/databaseName', + }; + + it('constructCustomSqlDataSourceStrategies', () => { + const schema = /* GraphQL */ ` + type Todo @model { + id: ID! @primaryKey + description: String + } + type Query { + myCustomQuery: [Int] @sql(statement: "SELECT 1") + } + type Mutation { + myCustomMutation: [Int] @sql(statement: "UPDATE mytable SET id=1; return 1;") + } + `; + + const mysqlStrategy: SQLLambdaModelDataSourceStrategy = { + name: 'mysqlStrategy', + dbType: 'MYSQL', + dbConnectionConfig, + }; + + const sqlDataSourceStrategies = constructCustomSqlDataSourceStrategies(schema, mysqlStrategy); + expect(sqlDataSourceStrategies).toEqual( + expect.arrayContaining([ + { + typeName: 'Query', + fieldName: 'myCustomQuery', + strategy: mysqlStrategy, }, - }, - Comment: { - name: 'pgTable', - dbType: 'POSTGRES', - dbConnectionConfig: { - hostnameSsmPath: 'hostnameSsmPath', - portSsmPath: 'portSsmPath', - usernameSsmPath: 'usernameSsmPath', - passwordSsmPath: 'passwordSsmPath', - databaseNameSsmPath: 'databaseNameSsmPath', + { + typeName: 'Mutation', + fieldName: 'myCustomMutation', + strategy: mysqlStrategy, }, + ]), + ); + }); + + it('getDataSourceStrategiesProvider for a single definition', () => { + const schema = /* GraphQL */ ` + type Todo @model { + id: ID! @primaryKey + description: String + } + type Query { + myCustomQuery: [Int] @sql(reference: "myCustomQuery") + } + type Mutation { + myCustomMutation: [Int] @sql(reference: "myCustomMutation") + } + `; + + const strategy: SQLLambdaModelDataSourceStrategy = { + name: 'strategy', + dbType: 'MYSQL', + dbConnectionConfig, + customSqlStatements: { + myCustomQuery: 'SELECT 1;', + myCustomMutation: 'UPDATE mytable SET id=1; return 1;', }, }; - const datasourceConfig = parseDataSourceConfig(input); - expect(datasourceConfig).toEqual({ - modelToDatasourceMap: new Map([ - [ - 'Todo', - { - dbType: 'DYNAMODB', - provisionDB: true, - provisionStrategy: DynamoDBProvisionStrategy.DEFAULT, - }, - ], - [ - 'Author', - { - dbType: 'DYNAMODB', - provisionDB: true, - provisionStrategy: DynamoDBProvisionStrategy.AMPLIFY_TABLE, - }, - ], - [ - 'Post', - { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, + + const definition: IAmplifyGraphqlDefinition = { + schema, + functionSlots: [], + dataSourceStrategies: { + Todo: strategy, + }, + customSqlDataSourceStrategies: [ + { + typeName: 'Query', + fieldName: 'myCustomQuery', + strategy, + }, + { + typeName: 'Mutation', + fieldName: 'myCustomMutation', + strategy, + }, + ], + }; + + const provider = getDataSourceStrategiesProvider(definition); + // We don't care that the custom SQL is defined on the internal type, as long as it's collected into the sqlDirective strategies + expect(provider.dataSourceStrategies).toMatchObject({ + Todo: strategy, + }); + + expect(provider.sqlDirectiveDataSourceStrategies).toEqual( + expect.arrayContaining([ + { + typeName: 'Query', + fieldName: 'myCustomQuery', + strategy: strategy, + customSqlStatements: { + myCustomQuery: 'SELECT 1;', + myCustomMutation: 'UPDATE mytable SET id=1; return 1;', }, - ], - [ - 'Comment', - { - dbType: 'POSTGRES', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, + }, + { + typeName: 'Mutation', + fieldName: 'myCustomMutation', + strategy: strategy, + customSqlStatements: { + myCustomQuery: 'SELECT 1;', + myCustomMutation: 'UPDATE mytable SET id=1; return 1;', }, - ], + }, ]), - }); + ); }); }); diff --git a/packages/amplify-graphql-api-construct/src/amplify-graphql-api.ts b/packages/amplify-graphql-api-construct/src/amplify-graphql-api.ts index c824e1babe..e8676cc3be 100644 --- a/packages/amplify-graphql-api-construct/src/amplify-graphql-api.ts +++ b/packages/amplify-graphql-api-construct/src/amplify-graphql-api.ts @@ -29,7 +29,6 @@ import { IEventBus } from 'aws-cdk-lib/aws-events'; import { IFunction } from 'aws-cdk-lib/aws-lambda'; import { IServerlessCluster } from 'aws-cdk-lib/aws-rds'; import { ISecret } from 'aws-cdk-lib/aws-secretsmanager'; -import { MYSQL_DB_TYPE, POSTGRES_DB_TYPE, RDSConnectionSecrets } from '@aws-amplify/graphql-transformer-core'; import { parseUserDefinedSlots, validateFunctionSlots, separateSlots } from './internal/user-defined-slots'; import type { AmplifyGraphqlApiResources, @@ -49,10 +48,8 @@ import { CodegenAssets, getAdditionalAuthenticationTypes, } from './internal'; -import { mapInterfaceCustomSqlStrategiesToImplementationStrategies, parseDataSourceConfig } from './internal/data-source-config'; import { getStackForScope, walkAndProcessNodes } from './internal/construct-tree'; -import { SQLLambdaModelDataSourceStrategy } from './model-datasource-strategy'; -import { isSQLLambdaModelDataSourceStrategy } from './sql-model-datasource-strategy'; +import { getDataSourceStrategiesProvider } from './internal/data-source-config'; /** * L3 Construct which invokes the Amplify Transformer Pattern over an input Graphql Schema. @@ -173,7 +170,7 @@ export class AmplifyGraphqlApi extends Construct { const assetManager = new AssetManager(); - let executeTransformConfig: ExecuteTransformConfig = { + const executeTransformConfig: ExecuteTransformConfig = { scope: this, nestedStackProvider: { provide: (nestedStackScope: Construct, name: string) => new NestedStack(nestedStackScope, name), @@ -204,33 +201,12 @@ export class AmplifyGraphqlApi extends Construct { ...defaultTranslationBehavior, ...(translationBehavior ?? {}), }, - - // Adds a modelToDataSourceMap field/value - ...parseDataSourceConfig(definition.dataSourceStrategies), + // CDK construct uses a custom resource. We'll define this explicitly here to remind ourselves that this value is unused in the CDK + // construct flow + rdsLayerMapping: undefined, + ...getDataSourceStrategiesProvider(definition), }; - // TODO: Normalize all of this once we start using strategies internally. Right now the data source configuration (VPC, connection info, - // etc) is separate from the DataSourceType, and singular - const customSqlDataSourceStrategies = mapInterfaceCustomSqlStrategiesToImplementationStrategies( - definition.customSqlDataSourceStrategies, - ); - if (customSqlDataSourceStrategies.length > 0) { - executeTransformConfig = { - ...executeTransformConfig, - customSqlDataSourceStrategies, - }; - } - - // TODO: Update this to support multiple definitions; right now we assume only one SQL data source type - const modelStrategies = Object.values(definition.dataSourceStrategies).filter(isSQLLambdaModelDataSourceStrategy); - const customSqlStrategies = definition.customSqlDataSourceStrategies?.map((css) => css.strategy) ?? []; - for (const strategy of [...modelStrategies, ...customSqlStrategies]) { - if (isSQLLambdaModelDataSourceStrategy(strategy)) { - executeTransformConfig = this.extendTransformConfig(executeTransformConfig, strategy); - break; - } - } - executeTransform(executeTransformConfig); this.codegenAssets = new CodegenAssets(this, 'AmplifyCodegenAssets', { modelSchema: definition.schema }); @@ -246,66 +222,6 @@ export class AmplifyGraphqlApi extends Construct { this.apiKey = this.resources.cfnResources.cfnApiKey?.attrApiKey; } - /** - * Extends executeTransformConfig with fields for provisioning a SQL Lambda - * @param executeTransformConfig the executeTransformConfig to extend - * @param strategy the SQLLambdaModelDataSourceStrategy containing the SQL connection values to add to the transform config - * @returns the extended configuration that includes SQL DB connection information - */ - private extendTransformConfig( - executeTransformConfig: ExecuteTransformConfig, - strategy: SQLLambdaModelDataSourceStrategy, - ): ExecuteTransformConfig { - const extendedConfig = { ...executeTransformConfig }; - - if (strategy.customSqlStatements) { - extendedConfig.customQueries = new Map(Object.entries(strategy.customSqlStatements)); - } - - const dbSecrets: Map = new Map(); - let dbSecretDbTypeKey: string; - switch (strategy.dbType) { - case 'MYSQL': - dbSecretDbTypeKey = MYSQL_DB_TYPE; - break; - case 'POSTGRES': - dbSecretDbTypeKey = POSTGRES_DB_TYPE; - break; - default: - throw new Error(`Unsupported binding type ${strategy.dbType}`); - } - dbSecrets.set(dbSecretDbTypeKey, { - username: strategy.dbConnectionConfig.usernameSsmPath, - password: strategy.dbConnectionConfig.passwordSsmPath, - host: strategy.dbConnectionConfig.hostnameSsmPath, - // Cast through `any` to allow the SSM Path string to be used on a type expecting a number. This flow expects the incoming value to be - // a string containing the SSM path. - port: strategy.dbConnectionConfig.portSsmPath as any, - database: strategy.dbConnectionConfig.databaseNameSsmPath, - }); - extendedConfig.datasourceSecretParameterLocations = dbSecrets; - - if (strategy.vpcConfiguration) { - const subnetAvailabilityZoneConfig = strategy.vpcConfiguration.subnetAvailabilityZoneConfig.map( - (saz): { subnetId: string; availabilityZone: string } => ({ - subnetId: saz.subnetId, - availabilityZone: saz.availabilityZone, - }), - ); - extendedConfig.sqlLambdaVpcConfig = { - vpcId: strategy.vpcConfiguration.vpcId, - securityGroupIds: strategy.vpcConfiguration.securityGroupIds, - subnetAvailabilityZoneConfig, - }; - } - - if (strategy.sqlLambdaProvisionedConcurrencyConfig) { - extendedConfig.sqlLambdaProvisionedConcurrencyConfig = strategy.sqlLambdaProvisionedConcurrencyConfig; - } - - return extendedConfig; - } - /** * Stores graphql api output to be used for client config generation * @param outputStorageStrategy Strategy to store construct outputs. If no strategy is provided a default strategy will be used. diff --git a/packages/amplify-graphql-api-construct/src/amplify-graphql-definition.ts b/packages/amplify-graphql-api-construct/src/amplify-graphql-definition.ts index 14d6bc6214..a1f542b4ef 100644 --- a/packages/amplify-graphql-api-construct/src/amplify-graphql-definition.ts +++ b/packages/amplify-graphql-api-construct/src/amplify-graphql-definition.ts @@ -1,8 +1,8 @@ import * as os from 'os'; import { SchemaFile } from 'aws-cdk-lib/aws-appsync'; import { IAmplifyGraphqlDefinition } from './types'; -import { constructDataSourceStrategyMap } from './internal'; -import { ModelDataSourceStrategy } from './model-datasource-strategy'; +import { constructDataSourceStrategies } from './internal'; +import { ModelDataSourceStrategy } from './model-datasource-strategy-types'; import { constructCustomSqlDataSourceStrategies } from './internal/data-source-config'; export const DEFAULT_MODEL_DATA_SOURCE_STRATEGY: ModelDataSourceStrategy = { @@ -33,7 +33,7 @@ export class AmplifyGraphqlDefinition { schema, functionSlots: [], referencedLambdaFunctions: {}, - dataSourceStrategies: constructDataSourceStrategyMap(schema, dataSourceStrategy), + dataSourceStrategies: constructDataSourceStrategies(schema, dataSourceStrategy), customSqlDataSourceStrategies: constructCustomSqlDataSourceStrategies(schema, dataSourceStrategy), }; } diff --git a/packages/amplify-graphql-api-construct/src/index.ts b/packages/amplify-graphql-api-construct/src/index.ts index a10f0d4998..b6b732b78f 100644 --- a/packages/amplify-graphql-api-construct/src/index.ts +++ b/packages/amplify-graphql-api-construct/src/index.ts @@ -43,4 +43,4 @@ export { StreamSpecification, } from './amplify-dynamodb-table-wrapper'; export { SQLLambdaModelDataSourceStrategyFactory } from './sql-model-datasource-strategy'; -export * from './model-datasource-strategy'; +export * from './model-datasource-strategy-types'; diff --git a/packages/amplify-graphql-api-construct/src/internal/data-source-config.ts b/packages/amplify-graphql-api-construct/src/internal/data-source-config.ts index fad517340d..5a36c979a0 100644 --- a/packages/amplify-graphql-api-construct/src/internal/data-source-config.ts +++ b/packages/amplify-graphql-api-construct/src/internal/data-source-config.ts @@ -1,37 +1,11 @@ import { parse } from 'graphql'; +import { isSqlStrategy, isQueryNode, isMutationNode, fieldsWithSqlDirective } from '@aws-amplify/graphql-transformer-core'; +import { DataSourceStrategiesProvider } from '@aws-amplify/graphql-transformer-interfaces'; import { - CustomSqlDataSourceStrategy as ImplementationCustomSqlDataSourceStrategy, - DataSourceType, - SQLLambdaModelProvisionStrategy, -} from '@aws-amplify/graphql-transformer-interfaces'; -import { - dataSourceStrategyToDataSourceType, - isSqlStrategy, - isQueryNode, - isMutationNode, - fieldsWithSqlDirective, -} from '@aws-amplify/graphql-transformer-core'; -import { normalizeDbType } from '@aws-amplify/graphql-transformer-core/lib/utils'; -import { CustomSqlDataSourceStrategy as InterfaceCustomSqlDataSourceStrategy, ModelDataSourceStrategy } from '../model-datasource-strategy'; - -type DataSourceConfig = { - modelToDatasourceMap: Map; -}; - -/** - * An internal helper to convert from a map of model-to-ModelDataSourceStrategies to the map of model-to-DataSourceTypes that internal - * transform processing requires. TODO: We can remove this once we refactor the internals to use ModelDataSourceStrategies natively. - */ -export const parseDataSourceConfig = (dataSourceDefinitionMap: Record): DataSourceConfig => { - const modelToDatasourceMap = new Map(); - for (const [key, value] of Object.entries(dataSourceDefinitionMap)) { - const dataSourceType = dataSourceStrategyToDataSourceType(value); - modelToDatasourceMap.set(key, dataSourceType); - } - return { - modelToDatasourceMap, - }; -}; + CustomSqlDataSourceStrategy as ConstructCustomSqlDataSourceStrategy, + ModelDataSourceStrategy as ConstructModelDataSourceStrategy, +} from '../model-datasource-strategy-types'; +import { IAmplifyGraphqlDefinition } from '../types'; /** * Creates an interface flavor of customSqlDataSourceStrategies from a factory method's schema and data source. Internally, this function @@ -40,13 +14,11 @@ export const parseDataSourceConfig = (dataSourceDefinitionMap: Record { + dataSourceStrategy: ConstructModelDataSourceStrategy, +): ConstructCustomSqlDataSourceStrategy[] => { if (!isSqlStrategy(dataSourceStrategy)) { return []; } @@ -59,7 +31,7 @@ export const constructCustomSqlDataSourceStrategies = ( return []; } - const customSqlDataSourceStrategies: InterfaceCustomSqlDataSourceStrategy[] = []; + const customSqlDataSourceStrategies: ConstructCustomSqlDataSourceStrategy[] = []; if (queryNode) { const fields = fieldsWithSqlDirective(queryNode); @@ -87,25 +59,39 @@ export const constructCustomSqlDataSourceStrategies = ( }; /** - * We currently use a different type structure to model strategies in the interface than we do in the implementation. This maps the - * interface CustomSqlDataSourceStrategy (which uses SQLLambdaModelDataSourceStrategy) to the implementation flavor (which uses - * DataSourceType). + * Extracts the data source provider from the definition. This jumps through some hoops to avoid changing the public interface. If we decide + * to change the public interface to simplify the structure, then this process gets a lot simpler. * - * TODO: Remove this once we refactor the internals to use strategies rather than DataSourceTypes + * TODO: Verify that this supports combined definitions when we add combine support */ -export const mapInterfaceCustomSqlStrategiesToImplementationStrategies = ( - strategies?: InterfaceCustomSqlDataSourceStrategy[], -): ImplementationCustomSqlDataSourceStrategy[] => { - if (!strategies) { - return []; - } - return strategies.map((interfaceStrategy) => ({ - fieldName: interfaceStrategy.fieldName, - typeName: interfaceStrategy.typeName, - dataSourceType: { - dbType: normalizeDbType(interfaceStrategy.strategy.dbType), - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }, - })); +export const getDataSourceStrategiesProvider = (definition: IAmplifyGraphqlDefinition): DataSourceStrategiesProvider => { + const provider: DataSourceStrategiesProvider = { + // We can directly use the interface strategies, even though the SQL strategies have the customSqlStatements field that is unused by the + // transformer flavor of this type + dataSourceStrategies: definition.dataSourceStrategies, + sqlDirectiveDataSourceStrategies: [], + }; + + // We'll collect all the custom SQL statements from the definition into a single map, and use that to make our + // SqlDirectiveDataSourceStrategies + const customSqlStatements: Record = {}; + + const constructSqlStrategies = definition.customSqlDataSourceStrategies ?? []; + + // Note that we're relying on the `customSqlStatements` object reference to stay the same throughout this loop. Don't reassign it, or the + // collected sqlDirectiveStrategies will break + constructSqlStrategies.forEach((sqlStrategy) => { + if (sqlStrategy.strategy.customSqlStatements) { + Object.assign(customSqlStatements, sqlStrategy.strategy.customSqlStatements); + } + + provider.sqlDirectiveDataSourceStrategies!.push({ + typeName: sqlStrategy.typeName, + fieldName: sqlStrategy.fieldName, + strategy: sqlStrategy.strategy, + customSqlStatements, + }); + }); + + return provider; }; diff --git a/packages/amplify-graphql-api-construct/src/internal/model-type-name.ts b/packages/amplify-graphql-api-construct/src/internal/model-type-name.ts index 450b38c67e..4c5337df6f 100644 --- a/packages/amplify-graphql-api-construct/src/internal/model-type-name.ts +++ b/packages/amplify-graphql-api-construct/src/internal/model-type-name.ts @@ -1,8 +1,9 @@ import { Kind, ObjectTypeDefinitionNode, StringValueNode, parse } from 'graphql'; -import { ModelDataSourceStrategy } from '../model-datasource-strategy'; +import { ModelDataSourceStrategy } from '../model-datasource-strategy-types'; const MODEL_DIRECTIVE_NAME = 'model'; const MANY_TO_MANY_DIRECTIVE_NAME = 'manyToMany'; + /** * Get the type names with model directives in the GraphQL schema in SDL * @param schema graphql schema in SDL @@ -30,7 +31,7 @@ export const getModelTypeNames = (schema: string): string[] => { return modelKeys.filter((key, idx) => modelKeys.indexOf(key) === idx); }; -export const constructDataSourceStrategyMap = ( +export const constructDataSourceStrategies = ( schema: string, dataSourceStrategy: ModelDataSourceStrategy, ): Record => { diff --git a/packages/amplify-graphql-api-construct/src/model-datasource-strategy.ts b/packages/amplify-graphql-api-construct/src/model-datasource-strategy-types.ts similarity index 96% rename from packages/amplify-graphql-api-construct/src/model-datasource-strategy.ts rename to packages/amplify-graphql-api-construct/src/model-datasource-strategy-types.ts index 69f54f82d1..03b3a5d5c8 100644 --- a/packages/amplify-graphql-api-construct/src/model-datasource-strategy.ts +++ b/packages/amplify-graphql-api-construct/src/model-datasource-strategy-types.ts @@ -155,7 +155,12 @@ export interface SqlModelDataSourceDbConnectionConfig { * `fromFilesAndStrategy`) will automatically construct this structure for you. */ export interface CustomSqlDataSourceStrategy { + /** The built-in type (either "Query" or "Mutation") with which the custom SQL is associated */ readonly typeName: 'Query' | 'Mutation'; + + /** The field name with which the custom SQL is associated */ readonly fieldName: string; + + /** The strategy used to create the datasource that will resolve the custom SQL statement. */ readonly strategy: SQLLambdaModelDataSourceStrategy; } diff --git a/packages/amplify-graphql-api-construct/src/sql-model-datasource-strategy.ts b/packages/amplify-graphql-api-construct/src/sql-model-datasource-strategy.ts index bb4850d7b4..a609af2a86 100644 --- a/packages/amplify-graphql-api-construct/src/sql-model-datasource-strategy.ts +++ b/packages/amplify-graphql-api-construct/src/sql-model-datasource-strategy.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; -import { SQLLambdaModelDataSourceStrategy, SqlModelDataSourceDbConnectionConfig } from './model-datasource-strategy'; +import { SQLLambdaModelDataSourceStrategy, SqlModelDataSourceDbConnectionConfig } from './model-datasource-strategy-types'; /** * Type predicate that returns true if the object is a SQLLambdaModelDataSourceStrategy. diff --git a/packages/amplify-graphql-api-construct/src/types.ts b/packages/amplify-graphql-api-construct/src/types.ts index 7c18f4be54..c06e5c02e2 100644 --- a/packages/amplify-graphql-api-construct/src/types.ts +++ b/packages/amplify-graphql-api-construct/src/types.ts @@ -18,7 +18,7 @@ import { IUserPool } from 'aws-cdk-lib/aws-cognito'; import { IFunction, CfnFunction } from 'aws-cdk-lib/aws-lambda'; import { IBucket } from 'aws-cdk-lib/aws-s3'; import { AmplifyDynamoDbTableWrapper } from './amplify-dynamodb-table-wrapper'; -import { CustomSqlDataSourceStrategy, ModelDataSourceStrategy } from './model-datasource-strategy'; +import { CustomSqlDataSourceStrategy, ModelDataSourceStrategy } from './model-datasource-strategy-types'; /** * Configuration for IAM Authorization on the Graphql Api. diff --git a/packages/amplify-graphql-auth-transformer/src/__tests__/rds-mutation-auth.test.ts b/packages/amplify-graphql-auth-transformer/src/__tests__/rds-mutation-auth.test.ts index cf3c27c7f3..1369be3c8c 100644 --- a/packages/amplify-graphql-auth-transformer/src/__tests__/rds-mutation-auth.test.ts +++ b/packages/amplify-graphql-auth-transformer/src/__tests__/rds-mutation-auth.test.ts @@ -1,8 +1,12 @@ import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; -import { validateModelSchema } from '@aws-amplify/graphql-transformer-core'; +import { constructDataSourceStrategies, validateModelSchema } from '@aws-amplify/graphql-transformer-core'; import { testTransform } from '@aws-amplify/graphql-transformer-test-utils'; import { parse } from 'graphql'; -import { AppSyncAuthConfiguration, SQLLambdaModelProvisionStrategy } from '@aws-amplify/graphql-transformer-interfaces'; +import { + AppSyncAuthConfiguration, + SQLLambdaModelDataSourceStrategy, + SqlModelDataSourceDbConnectionConfig, +} from '@aws-amplify/graphql-transformer-interfaces'; import { PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer'; import { AuthTransformer } from '../graphql-auth-transformer'; import { expectStashValueLike } from './test-helpers'; @@ -12,6 +16,20 @@ describe('Verify RDS Model level Auth rules on mutations:', () => { const ADMIN_UI_ADMIN_ROLES = '$util.qr($ctx.stash.put(\\"adminRoles\\", [\\"us-fake-1_uuid_Full-access/CognitoIdentityCredentials\\",\\"us-fake-1_uuid_Manage-only/CognitoIdentityCredentials\\"])'; + const dbConnectionConfig: SqlModelDataSourceDbConnectionConfig = { + hostnameSsmPath: '/test/hostname', + portSsmPath: '/test/port', + usernameSsmPath: '/test/username', + passwordSsmPath: '/test/password', + databaseNameSsmPath: '/test/databaseName', + }; + + const mysqlStrategy: SQLLambdaModelDataSourceStrategy = { + name: 'mysqlStrategy', + dbType: 'MYSQL', + dbConnectionConfig, + }; + it('should successfully transform apiKey auth rule', async () => { const validSchema = ` type Post @model @@ -24,15 +42,7 @@ describe('Verify RDS Model level Auth rules on mutations:', () => { const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new AuthTransformer(), new PrimaryKeyTransformer()], - modelToDatasourceMap: new Map( - Object.entries({ - Post: { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }, - }), - ), + dataSourceStrategies: constructDataSourceStrategies(validSchema, mysqlStrategy), synthParameters: { identityPoolId: 'TEST_IDENTITY_POOL_ID', }, @@ -121,20 +131,11 @@ describe('Verify RDS Model level Auth rules on mutations:', () => { additionalAuthenticationProviders: [], }; - const modelToDatasourceMap = new Map(); - ['PostPrivate', 'PostSingleOwner', 'PostOwners', 'PostStaticGroups', 'PostSingleGroup', 'PostGroups'].forEach((model) => { - modelToDatasourceMap.set(model, { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - }); - const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new AuthTransformer(), new PrimaryKeyTransformer()], authConfig, - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(validSchema, mysqlStrategy), synthParameters: { identityPoolId: 'TEST_IDENTITY_POOL_ID', }, @@ -273,20 +274,11 @@ describe('Verify RDS Model level Auth rules on mutations:', () => { additionalAuthenticationProviders: [], }; - const modelToDatasourceMap = new Map(); - ['PostPrivate', 'PostSingleOwner', 'PostOwners', 'PostStaticGroups', 'PostSingleGroup', 'PostGroups'].forEach((model) => { - modelToDatasourceMap.set(model, { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - }); - const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new AuthTransformer(), new PrimaryKeyTransformer()], authConfig, - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(validSchema, mysqlStrategy), synthParameters: { identityPoolId: 'TEST_IDENTITY_POOL_ID', }, @@ -382,15 +374,7 @@ describe('Verify RDS Model level Auth rules on mutations:', () => { schema: validSchema, transformers: [new ModelTransformer(), new AuthTransformer(), new PrimaryKeyTransformer()], authConfig, - modelToDatasourceMap: new Map( - Object.entries({ - Post: { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }, - }), - ), + dataSourceStrategies: constructDataSourceStrategies(validSchema, mysqlStrategy), synthParameters: { identityPoolId: 'TEST_IDENTITY_POOL_ID', }, @@ -441,20 +425,11 @@ describe('Verify RDS Model level Auth rules on mutations:', () => { additionalAuthenticationProviders: [], }; - const modelToDatasourceMap = new Map(); - ['PostPrivate', 'PostPublic'].forEach((model) => { - modelToDatasourceMap.set(model, { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - }); - const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new AuthTransformer(), new PrimaryKeyTransformer()], authConfig, - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(validSchema, mysqlStrategy), synthParameters: { identityPoolId: 'TEST_IDENTITY_POOL_ID', }, @@ -513,21 +488,12 @@ describe('Verify RDS Model level Auth rules on mutations:', () => { additionalAuthenticationProviders: [], }; - const modelToDatasourceMap = new Map(); - ['Post'].forEach((model) => { - modelToDatasourceMap.set(model, { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - }); - expect(() => testTransform({ schema: invalidSchema, transformers: [new ModelTransformer(), new AuthTransformer(), new PrimaryKeyTransformer()], authConfig, - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(invalidSchema, mysqlStrategy), }), ).toThrow( '@auth rules are not supported on fields on relational database models. Check field "title" on type "Post". Please use @auth on the type instead.', @@ -557,20 +523,11 @@ describe('Verify RDS Model level Auth rules on mutations:', () => { additionalAuthenticationProviders: [], }; - const modelToDatasourceMap = new Map(); - ['Post'].forEach((model) => { - modelToDatasourceMap.set(model, { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - }); - const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new AuthTransformer(), new PrimaryKeyTransformer()], authConfig, - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(validSchema, mysqlStrategy), }); expect(out).toBeDefined(); @@ -587,17 +544,9 @@ describe('Verify RDS Model level Auth rules on mutations:', () => { createdAt: String updatedAt: String }`; - const modelToDatasourceMap = new Map(); - ['Post'].forEach((model) => { - modelToDatasourceMap.set(model, { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - }); const out = testTransform({ schema: validSchema, - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(validSchema, mysqlStrategy), authConfig: { defaultAuthentication: { authenticationType: 'API_KEY', diff --git a/packages/amplify-graphql-auth-transformer/src/__tests__/rds-query-auth.test.ts b/packages/amplify-graphql-auth-transformer/src/__tests__/rds-query-auth.test.ts index 48cb74df02..b7f70a225b 100644 --- a/packages/amplify-graphql-auth-transformer/src/__tests__/rds-query-auth.test.ts +++ b/packages/amplify-graphql-auth-transformer/src/__tests__/rds-query-auth.test.ts @@ -1,12 +1,29 @@ import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; -import { validateModelSchema } from '@aws-amplify/graphql-transformer-core'; +import { constructDataSourceStrategies, validateModelSchema } from '@aws-amplify/graphql-transformer-core'; import { testTransform } from '@aws-amplify/graphql-transformer-test-utils'; import { parse } from 'graphql'; -import { AppSyncAuthConfiguration, SQLLambdaModelProvisionStrategy } from '@aws-amplify/graphql-transformer-interfaces'; -import { AuthTransformer } from '../graphql-auth-transformer'; +import { + AppSyncAuthConfiguration, + SQLLambdaModelDataSourceStrategy, + SqlModelDataSourceDbConnectionConfig, +} from '@aws-amplify/graphql-transformer-interfaces'; import { PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer'; +import { AuthTransformer } from '../graphql-auth-transformer'; describe('Verify RDS Model level Auth rules on queries:', () => { + const dbConnectionConfig: SqlModelDataSourceDbConnectionConfig = { + hostnameSsmPath: '/test/hostname', + portSsmPath: '/test/port', + usernameSsmPath: '/test/username', + passwordSsmPath: '/test/password', + databaseNameSsmPath: '/test/databaseName', + }; + const mysqlStrategy: SQLLambdaModelDataSourceStrategy = { + name: 'mysqlStrategy', + dbType: 'MYSQL', + dbConnectionConfig, + }; + it('should successfully transform apiKey auth rule', async () => { const validSchema = ` type Post @model @@ -19,15 +36,7 @@ describe('Verify RDS Model level Auth rules on queries:', () => { const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new AuthTransformer(), new PrimaryKeyTransformer()], - modelToDatasourceMap: new Map( - Object.entries({ - Post: { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }, - }), - ), + dataSourceStrategies: constructDataSourceStrategies(validSchema, mysqlStrategy), synthParameters: { identityPoolId: 'TEST_IDENTITY_POOL_ID', }, @@ -110,20 +119,11 @@ describe('Verify RDS Model level Auth rules on queries:', () => { additionalAuthenticationProviders: [], }; - const modelToDatasourceMap = new Map(); - ['PostPrivate', 'PostSingleOwner', 'PostOwners', 'PostStaticGroups', 'PostSingleGroup', 'PostGroups'].forEach((model) => { - modelToDatasourceMap.set(model, { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - }); - const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new AuthTransformer(), new PrimaryKeyTransformer()], authConfig, - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(validSchema, mysqlStrategy), synthParameters: { identityPoolId: 'TEST_IDENTITY_POOL_ID', }, @@ -238,20 +238,11 @@ describe('Verify RDS Model level Auth rules on queries:', () => { additionalAuthenticationProviders: [], }; - const modelToDatasourceMap = new Map(); - ['PostPrivate', 'PostSingleOwner', 'PostOwners', 'PostStaticGroups', 'PostSingleGroup', 'PostGroups'].forEach((model) => { - modelToDatasourceMap.set(model, { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - }); - const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new AuthTransformer(), new PrimaryKeyTransformer()], authConfig, - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(validSchema, mysqlStrategy), synthParameters: { identityPoolId: 'TEST_IDENTITY_POOL_ID', }, @@ -323,15 +314,7 @@ describe('Verify RDS Model level Auth rules on queries:', () => { schema: validSchema, transformers: [new ModelTransformer(), new AuthTransformer(), new PrimaryKeyTransformer()], authConfig, - modelToDatasourceMap: new Map( - Object.entries({ - Post: { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }, - }), - ), + dataSourceStrategies: constructDataSourceStrategies(validSchema, mysqlStrategy), synthParameters: { identityPoolId: 'TEST_IDENTITY_POOL_ID', }, @@ -376,20 +359,11 @@ describe('Verify RDS Model level Auth rules on queries:', () => { additionalAuthenticationProviders: [], }; - const modelToDatasourceMap = new Map(); - ['PostPrivate', 'PostPublic'].forEach((model) => { - modelToDatasourceMap.set(model, { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - }); - const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new AuthTransformer(), new PrimaryKeyTransformer()], authConfig, - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(validSchema, mysqlStrategy), synthParameters: { identityPoolId: 'TEST_IDENTITY_POOL_ID', }, @@ -441,20 +415,11 @@ describe('Verify RDS Model level Auth rules on queries:', () => { additionalAuthenticationProviders: [], }; - const modelToDatasourceMap = new Map(); - ['Post'].forEach((model) => { - modelToDatasourceMap.set(model, { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - }); - const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new AuthTransformer(), new PrimaryKeyTransformer()], authConfig, - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(validSchema, mysqlStrategy), }); expect(out).toBeDefined(); diff --git a/packages/amplify-graphql-auth-transformer/src/__tests__/rds-subscription-auth.test.ts b/packages/amplify-graphql-auth-transformer/src/__tests__/rds-subscription-auth.test.ts index 994c3ccf24..a14ef41a67 100644 --- a/packages/amplify-graphql-auth-transformer/src/__tests__/rds-subscription-auth.test.ts +++ b/packages/amplify-graphql-auth-transformer/src/__tests__/rds-subscription-auth.test.ts @@ -1,12 +1,30 @@ import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; -import { validateModelSchema } from '@aws-amplify/graphql-transformer-core'; +import { constructDataSourceStrategies, validateModelSchema } from '@aws-amplify/graphql-transformer-core'; import { testTransform } from '@aws-amplify/graphql-transformer-test-utils'; import { parse } from 'graphql'; -import { AppSyncAuthConfiguration, SQLLambdaModelProvisionStrategy } from '@aws-amplify/graphql-transformer-interfaces'; -import { AuthTransformer } from '../graphql-auth-transformer'; +import { + AppSyncAuthConfiguration, + SQLLambdaModelDataSourceStrategy, + SqlModelDataSourceDbConnectionConfig, +} from '@aws-amplify/graphql-transformer-interfaces'; import { PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer'; +import { AuthTransformer } from '../graphql-auth-transformer'; describe('Verify RDS Model level Auth rules on subscriptions:', () => { + const dbConnectionConfig: SqlModelDataSourceDbConnectionConfig = { + hostnameSsmPath: '/test/hostname', + portSsmPath: '/test/port', + usernameSsmPath: '/test/username', + passwordSsmPath: '/test/password', + databaseNameSsmPath: '/test/databaseName', + }; + + const mysqlStrategy: SQLLambdaModelDataSourceStrategy = { + name: 'mysqlStrategy', + dbType: 'MYSQL', + dbConnectionConfig, + }; + it('should successfully transform apiKey auth rule', async () => { const validSchema = ` type Post @model @@ -19,15 +37,7 @@ describe('Verify RDS Model level Auth rules on subscriptions:', () => { const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new AuthTransformer(), new PrimaryKeyTransformer()], - modelToDatasourceMap: new Map( - Object.entries({ - Post: { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }, - }), - ), + dataSourceStrategies: constructDataSourceStrategies(validSchema, mysqlStrategy), synthParameters: { identityPoolId: 'TEST_IDENTITY_POOL_ID', }, @@ -117,20 +127,11 @@ describe('Verify RDS Model level Auth rules on subscriptions:', () => { additionalAuthenticationProviders: [], }; - const modelToDatasourceMap = new Map(); - ['PostPrivate', 'PostSingleOwner', 'PostOwners', 'PostStaticGroups', 'PostSingleGroup', 'PostGroups'].forEach((model) => { - modelToDatasourceMap.set(model, { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - }); - const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new AuthTransformer(), new PrimaryKeyTransformer()], authConfig, - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(validSchema, mysqlStrategy), synthParameters: { identityPoolId: 'TEST_IDENTITY_POOL_ID', }, @@ -275,20 +276,11 @@ describe('Verify RDS Model level Auth rules on subscriptions:', () => { additionalAuthenticationProviders: [], }; - const modelToDatasourceMap = new Map(); - ['PostPrivate', 'PostSingleOwner', 'PostOwners', 'PostStaticGroups', 'PostSingleGroup', 'PostGroups'].forEach((model) => { - modelToDatasourceMap.set(model, { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - }); - const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new AuthTransformer(), new PrimaryKeyTransformer()], authConfig, - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(validSchema, mysqlStrategy), synthParameters: { identityPoolId: 'TEST_IDENTITY_POOL_ID', }, @@ -390,15 +382,7 @@ describe('Verify RDS Model level Auth rules on subscriptions:', () => { schema: validSchema, transformers: [new ModelTransformer(), new AuthTransformer(), new PrimaryKeyTransformer()], authConfig, - modelToDatasourceMap: new Map( - Object.entries({ - Post: { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }, - }), - ), + dataSourceStrategies: constructDataSourceStrategies(validSchema, mysqlStrategy), synthParameters: { identityPoolId: 'TEST_IDENTITY_POOL_ID', }, @@ -450,20 +434,11 @@ describe('Verify RDS Model level Auth rules on subscriptions:', () => { additionalAuthenticationProviders: [], }; - const modelToDatasourceMap = new Map(); - ['PostPrivate', 'PostPublic'].forEach((model) => { - modelToDatasourceMap.set(model, { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - }); - const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new AuthTransformer(), new PrimaryKeyTransformer()], authConfig, - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(validSchema, mysqlStrategy), synthParameters: { identityPoolId: 'TEST_IDENTITY_POOL_ID', }, @@ -525,20 +500,11 @@ describe('Verify RDS Model level Auth rules on subscriptions:', () => { additionalAuthenticationProviders: [], }; - const modelToDatasourceMap = new Map(); - ['Post'].forEach((model) => { - modelToDatasourceMap.set(model, { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - }); - const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new AuthTransformer(), new PrimaryKeyTransformer()], authConfig, - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(validSchema, mysqlStrategy), }); expect(out).toBeDefined(); diff --git a/packages/amplify-graphql-default-value-transformer/src/__tests__/amplify-grapphql-default-value-transformer.test.ts b/packages/amplify-graphql-default-value-transformer/src/__tests__/amplify-grapphql-default-value-transformer.test.ts index 641fd80d1e..c68b281a35 100644 --- a/packages/amplify-graphql-default-value-transformer/src/__tests__/amplify-grapphql-default-value-transformer.test.ts +++ b/packages/amplify-graphql-default-value-transformer/src/__tests__/amplify-grapphql-default-value-transformer.test.ts @@ -1,9 +1,9 @@ import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; -import { validateModelSchema } from '@aws-amplify/graphql-transformer-core'; +import { MYSQL_DB_TYPE, constructDataSourceStrategies, validateModelSchema } from '@aws-amplify/graphql-transformer-core'; import { parse } from 'graphql'; import { testTransform } from '@aws-amplify/graphql-transformer-test-utils'; import { PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer'; -import { DataSourceType, SQLLambdaModelProvisionStrategy } from '@aws-amplify/graphql-transformer-interfaces'; +import { SQLLambdaModelDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; import { DefaultValueTransformer } from '..'; describe('DefaultValueModelTransformer:', () => { @@ -314,7 +314,7 @@ describe('DefaultValueModelTransformer:', () => { validateModelSchema(schema); }); - it('default value type should not be validated for rds datasource', async () => { + it('default value type should not be validated for sql datasource', async () => { const validSchema = ` type Note @model { id: ID! @primaryKey @@ -323,16 +323,21 @@ describe('DefaultValueModelTransformer:', () => { } `; - const modelToDatasourceMap = new Map(); - modelToDatasourceMap.set('Note', { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); + const mySqlStrategy: SQLLambdaModelDataSourceStrategy = { + name: 'mySqlStrategy', + dbType: MYSQL_DB_TYPE, + dbConnectionConfig: { + databaseNameSsmPath: '/databaseNameSsmPath', + hostnameSsmPath: '/hostnameSsmPath', + passwordSsmPath: '/passwordSsmPath', + portSsmPath: '/portSsmPath', + usernameSsmPath: '/usernameSsmPath', + }, + }; const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new DefaultValueTransformer(), new PrimaryKeyTransformer()], - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(validSchema, mySqlStrategy), }); expect(out).toBeDefined(); diff --git a/packages/amplify-graphql-default-value-transformer/src/graphql-default-value-transformer.ts b/packages/amplify-graphql-default-value-transformer/src/graphql-default-value-transformer.ts index 061d994c40..bf0362c8ee 100644 --- a/packages/amplify-graphql-default-value-transformer/src/graphql-default-value-transformer.ts +++ b/packages/amplify-graphql-default-value-transformer/src/graphql-default-value-transformer.ts @@ -1,9 +1,9 @@ import { - DDB_DB_TYPE, DirectiveWrapper, generateGetArgumentsInput, InputObjectDefinitionWrapper, InvalidDirectiveError, + isDynamoDbModel, MappingTemplate, TransformerPluginBase, } from '@aws-amplify/graphql-transformer-core'; @@ -82,18 +82,13 @@ const validate = (ctx: TransformerSchemaVisitStepContextProvider, config: Defaul validateDirectiveArguments(config.directive); // Validate the default values only for the DynamoDB datasource. - // For RDS, the database determines and sets the default value. We will not validate the value in transformers. - const isDynamoDB = isDynamoDBDatasource(ctx, config.object.name.value); + // For SQL, the database determines and sets the default value. We will not validate the value in transformers. + const isDynamoDB = isDynamoDbModel(ctx, config.object.name.value); if (isDynamoDB) { validateDefaultValueType(ctx, config); } }; -const isDynamoDBDatasource = (ctx: TransformerSchemaVisitStepContextProvider, modelName: string): boolean => { - const isDynamoDB = (ctx.modelToDatasourceMap.get(modelName)?.dbType ?? DDB_DB_TYPE) === DDB_DB_TYPE; - return isDynamoDB; -}; - export class DefaultValueTransformer extends TransformerPluginBase { private directiveMap = new Map(); @@ -141,7 +136,7 @@ export class DefaultValueTransformer extends TransformerPluginBase { for (const typeName of this.directiveMap.keys()) { // Set the default value only for DDB datasource. For RDS, the database will set the value. - const isDynamoDB = isDynamoDBDatasource(ctx, typeName); + const isDynamoDB = isDynamoDbModel(ctx, typeName); if (!isDynamoDB) { continue; } diff --git a/packages/amplify-graphql-index-transformer/src/__tests__/__snapshots__/amplify-graphql-primary-key-transformer.test.ts.snap b/packages/amplify-graphql-index-transformer/src/__tests__/__snapshots__/amplify-graphql-primary-key-transformer.test.ts.snap index f265376770..4598e260f4 100644 --- a/packages/amplify-graphql-index-transformer/src/__tests__/__snapshots__/amplify-graphql-primary-key-transformer.test.ts.snap +++ b/packages/amplify-graphql-index-transformer/src/__tests__/__snapshots__/amplify-graphql-primary-key-transformer.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RDS primary key transformer tests a primary key with a composite sort key is properly configured 1`] = ` +exports[`SQL primary key transformer tests a primary key with a composite sort key is properly configured 1`] = ` Object { "Mutation.createTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) @@ -302,7 +302,7 @@ $util.toJson(null) } `; -exports[`RDS primary key transformer tests a primary key with a single sort key field is properly configured 1`] = ` +exports[`SQL primary key transformer tests a primary key with a single sort key field is properly configured 1`] = ` Object { "Mutation.createTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) @@ -599,7 +599,7 @@ $util.toJson(null) } `; -exports[`RDS primary key transformer tests a primary key with no sort key is properly configured 1`] = ` +exports[`SQL primary key transformer tests a primary key with no sort key is properly configured 1`] = ` Object { "Mutation.createTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) @@ -891,7 +891,7 @@ $util.toJson(null) } `; -exports[`RDS primary key transformer tests individual resolvers can be made null by @model 1`] = ` +exports[`SQL primary key transformer tests individual resolvers can be made null by @model 1`] = ` Object { "Mutation.createTest.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) @@ -1140,7 +1140,7 @@ $util.toJson(null) } `; -exports[`RDS primary key transformer tests resolvers can be renamed by @model 1`] = ` +exports[`SQL primary key transformer tests resolvers can be renamed by @model 1`] = ` Object { "Mutation.testCreate.init.1.req.vtl": "## [Start] Initialization default values. ** $util.qr($ctx.stash.put(\\"defaultValues\\", $util.defaultIfNull($ctx.stash.defaultValues, {}))) diff --git a/packages/amplify-graphql-index-transformer/src/__tests__/amplify-graphql-index-transformer.test.ts b/packages/amplify-graphql-index-transformer/src/__tests__/amplify-graphql-index-transformer.test.ts index f9fc1a0f5e..05cc86c5f3 100644 --- a/packages/amplify-graphql-index-transformer/src/__tests__/amplify-graphql-index-transformer.test.ts +++ b/packages/amplify-graphql-index-transformer/src/__tests__/amplify-graphql-index-transformer.test.ts @@ -1,9 +1,17 @@ import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; -import { ConflictHandlerType, DDB_DB_TYPE, MYSQL_DB_TYPE, validateModelSchema } from '@aws-amplify/graphql-transformer-core'; +import { + ConflictHandlerType, + DDB_DB_TYPE, + DDB_DEFAULT_DATASOURCE_STRATEGY, + MYSQL_DB_TYPE, + constructDataSourceStrategies, + validateModelSchema, +} from '@aws-amplify/graphql-transformer-core'; import { Template as AssertionTemplate } from 'aws-cdk-lib/assertions'; import { DocumentNode, parse } from 'graphql'; import { testTransform, Template, AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/graphql-transformer-test-utils'; import { Construct } from 'constructs'; +import { SQLLambdaModelDataSourceStrategy, SqlModelDataSourceDbConnectionConfig } from '@aws-amplify/graphql-transformer-interfaces'; import { IndexTransformer, PrimaryKeyTransformer } from '..'; import * as resolverUtils from '../resolvers/resolvers'; @@ -1327,6 +1335,28 @@ describe('automatic name generation', () => { }); describe('Index query resolver creation', () => { + const schema = /* GrahphQL */ ` + type Test @model { + category: String! @index(name: null) + } + `; + + const dbConnectionConfig: SqlModelDataSourceDbConnectionConfig = { + hostnameSsmPath: '/test/hostname', + portSsmPath: '/test/port', + usernameSsmPath: '/test/username', + passwordSsmPath: '/test/password', + databaseNameSsmPath: '/test/databaseName', + }; + + const mysqlStrategy: SQLLambdaModelDataSourceStrategy = { + name: 'mysqlStrategy', + dbType: 'MYSQL', + dbConnectionConfig, + }; + + const dataSourceStrategies = constructDataSourceStrategies(schema, mysqlStrategy); + const modelName = 'User'; const mockResolver = { addToSlot: jest.fn(), @@ -1349,10 +1379,7 @@ describe('Index query resolver creation', () => { }); it('sets appropriate resolver reference if field mappings are present for a RDS model', () => { - mockContext.modelToDatasourceMap.get.mockReturnValue({ - dbType: MYSQL_DB_TYPE, - provisionDB: false, - }); + mockContext.dataSourceStrategies = dataSourceStrategies; mockContext.api.host.getDataSource.mockReturnValue({}); mockModelFieldMap.getMappedFields.mockReturnValue([{ details: 'description' }]); resolverUtils.makeQueryResolver(mockConfig as any, mockContext as any, MYSQL_DB_TYPE); @@ -1366,10 +1393,7 @@ describe('Index query resolver creation', () => { }); it('does not set resolver reference if field mappings are not present for a RDS model', () => { - mockContext.modelToDatasourceMap.get.mockReturnValue({ - dbType: MYSQL_DB_TYPE, - provisionDB: false, - }); + mockContext.dataSourceStrategies = dataSourceStrategies; mockContext.api.host.getDataSource.mockReturnValue({}); mockModelFieldMap.getMappedFields.mockReturnValue([]); resolverUtils.makeQueryResolver(mockConfig as any, mockContext as any, MYSQL_DB_TYPE); @@ -1384,10 +1408,9 @@ describe('Index query resolver creation', () => { generateIndexQueryRequestTemplate: jest.fn().mockReturnValue('mock template'), generatePrimaryKeyVTL: jest.fn().mockReturnValue('mock template'), }); - mockContext.modelToDatasourceMap.get.mockReturnValueOnce({ - dbType: DDB_DB_TYPE, - provisionDB: true, - }); + mockContext.dataSourceStrategies = { + Test: DDB_DEFAULT_DATASOURCE_STRATEGY, + }; const mockTable = { stack: { node: { @@ -1410,9 +1433,6 @@ describe('Index query resolver creation', () => { const constructMockContext = () => { return { - modelToDatasourceMap: { - get: jest.fn(), - }, api: { host: { getDataSource: jest.fn(), diff --git a/packages/amplify-graphql-index-transformer/src/__tests__/amplify-graphql-primary-key-transformer.test.ts b/packages/amplify-graphql-index-transformer/src/__tests__/amplify-graphql-primary-key-transformer.test.ts index 7538085cbb..f9f86f7b2a 100644 --- a/packages/amplify-graphql-index-transformer/src/__tests__/amplify-graphql-primary-key-transformer.test.ts +++ b/packages/amplify-graphql-index-transformer/src/__tests__/amplify-graphql-primary-key-transformer.test.ts @@ -1,10 +1,10 @@ import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; -import { validateModelSchema } from '@aws-amplify/graphql-transformer-core'; +import { MYSQL_DB_TYPE, constructDataSourceStrategies, validateModelSchema } from '@aws-amplify/graphql-transformer-core'; import { Template } from 'aws-cdk-lib/assertions'; import { Kind, parse } from 'graphql'; import _ from 'lodash'; import { testTransform } from '@aws-amplify/graphql-transformer-test-utils'; -import { DataSourceType, DynamoDBProvisionStrategy } from '@aws-amplify/graphql-transformer-interfaces'; +import { SQLLambdaModelDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; import { PrimaryKeyTransformer } from '..'; test('throws if multiple primary keys are defined on an object', () => { @@ -726,13 +726,18 @@ test('lowercase model names generate the correct get/list query arguments', () = expect(listQuery.arguments[5].name.value).toEqual('sortDirection'); }); -describe('RDS primary key transformer tests', () => { - const modelToDatasourceMap = new Map(); - modelToDatasourceMap.set('Test', { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: DynamoDBProvisionStrategy.DEFAULT, // TODO: change this once the RDS stratety is added - }); +describe('SQL primary key transformer tests', () => { + const mySqlStrategy: SQLLambdaModelDataSourceStrategy = { + name: 'mySqlStrategy', + dbType: MYSQL_DB_TYPE, + dbConnectionConfig: { + databaseNameSsmPath: '/databaseNameSsmPath', + hostnameSsmPath: '/hostnameSsmPath', + passwordSsmPath: '/passwordSsmPath', + portSsmPath: '/portSsmPath', + usernameSsmPath: '/usernameSsmPath', + }, + }; it('a primary key with a single sort key field is properly configured', () => { const inputSchema = ` @@ -744,7 +749,7 @@ describe('RDS primary key transformer tests', () => { const out = testTransform({ schema: inputSchema, transformers: [new ModelTransformer(), new PrimaryKeyTransformer()], - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(inputSchema, mySqlStrategy), }); const schema = parse(out.schema); const stack = out.stacks.Test; @@ -772,7 +777,7 @@ describe('RDS primary key transformer tests', () => { const out = testTransform({ schema: inputSchema, transformers: [new ModelTransformer(), new PrimaryKeyTransformer()], - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(inputSchema, mySqlStrategy), }); const schema = parse(out.schema); const stack = out.stacks.Test; @@ -815,7 +820,7 @@ describe('RDS primary key transformer tests', () => { const out = testTransform({ schema: inputSchema, transformers: [new ModelTransformer(), new PrimaryKeyTransformer()], - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(inputSchema, mySqlStrategy), }); const schema = parse(out.schema); const stack = out.stacks.Test; @@ -880,7 +885,7 @@ describe('RDS primary key transformer tests', () => { const out = testTransform({ schema: inputSchema, transformers: [new ModelTransformer(), new PrimaryKeyTransformer()], - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(inputSchema, mySqlStrategy), }); const schema = parse(out.schema); @@ -918,7 +923,7 @@ describe('RDS primary key transformer tests', () => { const out = testTransform({ schema: inputSchema, transformers: [new ModelTransformer(), new PrimaryKeyTransformer()], - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(inputSchema, mySqlStrategy), }); const schema = parse(out.schema); @@ -950,7 +955,7 @@ describe('RDS primary key transformer tests', () => { const out = testTransform({ schema: inputSchema, transformers: [new ModelTransformer(), new PrimaryKeyTransformer()], - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(inputSchema, mySqlStrategy), }); const schema = parse(out.schema); @@ -994,7 +999,7 @@ describe('RDS primary key transformer tests', () => { const out = testTransform({ schema: inputSchema, transformers: [new ModelTransformer(), new PrimaryKeyTransformer()], - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(inputSchema, mySqlStrategy), }); const schema = parse(out.schema); diff --git a/packages/amplify-graphql-index-transformer/src/graphql-primary-key-transformer.ts b/packages/amplify-graphql-index-transformer/src/graphql-primary-key-transformer.ts index fee8b16805..fc32412e99 100644 --- a/packages/amplify-graphql-index-transformer/src/graphql-primary-key-transformer.ts +++ b/packages/amplify-graphql-index-transformer/src/graphql-primary-key-transformer.ts @@ -3,7 +3,8 @@ import { generateGetArgumentsInput, InvalidDirectiveError, TransformerPluginBase, - isImportedRDSType, + isSqlDbType, + getModelDataSourceStrategy, } from '@aws-amplify/graphql-transformer-core'; import { TransformerContextProvider, @@ -102,8 +103,8 @@ export class PrimaryKeyTransformer extends TransformerPluginBase { generateResolvers = (ctx: TransformerContextProvider): void => { for (const config of this.directiveList) { - const dbInfo = ctx.modelToDatasourceMap.get(config.object.name.value); - const vtlGenerator = getVTLGenerator(dbInfo); + const dbType = getModelDataSourceStrategy(ctx, config.object.name.value).dbType; + const vtlGenerator = getVTLGenerator(dbType); vtlGenerator.generatePrimaryKeyVTL(config, ctx, this.resolverMap); } }; @@ -190,9 +191,9 @@ export const updateListField = (config: PrimaryKeyDirectiveConfiguration, ctx: T let listField = query.fields!.find((field: FieldDefinitionNode) => field.name.value === resolverName) as FieldDefinitionNode; if (listField) { const args = [createHashField(config)]; - const dbType = ctx.modelToDatasourceMap.get(config.object.name.value); + const dbType = getModelDataSourceStrategy(ctx, config.object.name.value).dbType; - if (!dbType || !isImportedRDSType(dbType)) { + if (!dbType || !isSqlDbType(dbType)) { const sortField = tryAndCreateSortField(config, ctx); if (sortField) { args.push(sortField); diff --git a/packages/amplify-graphql-index-transformer/src/resolvers/resolvers.ts b/packages/amplify-graphql-index-transformer/src/resolvers/resolvers.ts index 0fcb9596f7..8b5434ccae 100644 --- a/packages/amplify-graphql-index-transformer/src/resolvers/resolvers.ts +++ b/packages/amplify-graphql-index-transformer/src/resolvers/resolvers.ts @@ -1,7 +1,8 @@ import { generateApplyDefaultsToInputTemplate } from '@aws-amplify/graphql-model-transformer'; import { DDB_DB_TYPE, - getDatasourceProvisionStrategy, + getModelDataSourceStrategy, + isAmplifyDynamoDbModelDataSourceStrategy, MappingTemplate, MYSQL_DB_TYPE, POSTGRES_DB_TYPE, @@ -10,8 +11,6 @@ import { DataSourceProvider, TransformerContextProvider, TransformerResolverProvider, - DynamoDBProvisionStrategy, - DataSourceType, ModelDataSourceStrategyDbType, } from '@aws-amplify/graphql-transformer-interfaces'; import { DynamoDbDataSource } from 'aws-cdk-lib/aws-appsync'; @@ -69,10 +68,8 @@ const API_KEY = 'API Key Authorization'; export const replaceDdbPrimaryKey = (config: PrimaryKeyDirectiveConfiguration, ctx: TransformerContextProvider): void => { // Replace the table's primary key with the value from @primaryKey const { field, object } = config; - const tableProvisionStrategy = getDatasourceProvisionStrategy(ctx, object.name.value); - const useAmplifyManagedTableResources: boolean = tableProvisionStrategy - ? tableProvisionStrategy === DynamoDBProvisionStrategy.AMPLIFY_TABLE - : false; + const strategy = getModelDataSourceStrategy(ctx, object.name.value); + const useAmplifyManagedTableResources = isAmplifyDynamoDbModelDataSourceStrategy(strategy); const table = getTable(ctx, object) as any; const cfnTable = useAmplifyManagedTableResources ? table.node.defaultChild.node.defaultChild : table.table; const tableAttrDefs = table.attributeDefinitions; @@ -439,10 +436,8 @@ export const appendSecondaryIndex = (config: IndexDirectiveConfiguration, ctx: T * @param indexInfo global secondary index properties */ export const overrideIndexAtCfnLevel = (ctx: TransformerContextProvider, typeName: string, table: any, indexInfo: any): void => { - const tableProvisionStrategy = getDatasourceProvisionStrategy(ctx, typeName); - const useAmplifyManagedTableResources: boolean = tableProvisionStrategy - ? tableProvisionStrategy === DynamoDBProvisionStrategy.AMPLIFY_TABLE - : false; + const strategy = getModelDataSourceStrategy(ctx, typeName); + const useAmplifyManagedTableResources = isAmplifyDynamoDbModelDataSourceStrategy(strategy); if (!useAmplifyManagedTableResources) { const cfnTable = table.table; @@ -480,7 +475,7 @@ export const updateResolversForIndex = ( const deleteResolver = getResolverObject(config, ctx, 'delete'); const syncResolver = getResolverObject(config, ctx, 'sync'); - const dbType = getDBType(ctx, object.name.value); + const dbType = getModelDataSourceStrategy(ctx, object.name.value).dbType; const isDynamoDB = isDynamoDbType(dbType); // Ensure any composite sort key values and validate update operations to @@ -530,7 +525,6 @@ export const makeQueryResolver = ( throw new Error('Expected name and queryField to be defined while generating resolver.'); } const modelName = object.name.value; - const dbInfo = getDBInfo(ctx, modelName); let dataSourceName = `${object.name.value}Table`; if (!isDynamoDB) { dataSourceName = SQLLambdaDataSourceLogicalID; @@ -555,7 +549,7 @@ export const makeQueryResolver = ( resolverResourceId, dataSource as DataSourceProvider, MappingTemplate.s3MappingTemplateFromString( - getVTLGenerator(dbInfo).generateIndexQueryRequestTemplate(config, ctx, modelName, queryField), + getVTLGenerator(dbType).generateIndexQueryRequestTemplate(config, ctx, modelName, queryField), `${queryTypeName}.${queryField}.req.vtl`, ), MappingTemplate.s3MappingTemplateFromString( @@ -920,21 +914,7 @@ export const generateAuthExpressionForSandboxMode = (enabled: boolean): string = ); }; -export const getDBInfo = (ctx: TransformerContextProvider, modelName: string): DataSourceType => { - const dbInfo = ctx.modelToDatasourceMap.get(modelName); - if (!dbInfo) { - throw new Error(`No datasource found for model ${modelName}`); - } - return dbInfo; -}; - -export const getDBType = (ctx: TransformerContextProvider, modelName: string): ModelDataSourceStrategyDbType => { - const dbInfo = getDBInfo(ctx, modelName); - return dbInfo.dbType; -}; - -export const getVTLGenerator = (dbInfo: DataSourceType | undefined): RDSIndexVTLGenerator | DynamoDBIndexVTLGenerator => { - const dbType = dbInfo ? dbInfo.dbType : DDB_DB_TYPE; +export const getVTLGenerator = (dbType: ModelDataSourceStrategyDbType | undefined): RDSIndexVTLGenerator | DynamoDBIndexVTLGenerator => { switch (dbType) { case DDB_DB_TYPE: return new DynamoDBIndexVTLGenerator(); diff --git a/packages/amplify-graphql-model-transformer/API.md b/packages/amplify-graphql-model-transformer/API.md index bfe1764e18..1e47418f6a 100644 --- a/packages/amplify-graphql-model-transformer/API.md +++ b/packages/amplify-graphql-model-transformer/API.md @@ -18,10 +18,10 @@ import * as iam from 'aws-cdk-lib/aws-iam'; import { InputObjectDefinitionWrapper } from '@aws-amplify/graphql-transformer-core'; import { InputObjectTypeDefinitionNode } from 'graphql'; import { InputValueDefinitionNode } from 'graphql'; -import { ModelDataSourceStrategySqlDbType } from '@aws-amplify/graphql-transformer-interfaces'; import { MutationFieldType } from '@aws-amplify/graphql-transformer-interfaces'; import { ObjectTypeDefinitionNode } from 'graphql'; import { QueryFieldType } from '@aws-amplify/graphql-transformer-interfaces'; +import { SQLLambdaModelDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; import { SubscriptionFieldType } from '@aws-amplify/graphql-transformer-interfaces'; import { SyncConfig } from '@aws-amplify/graphql-transformer-core'; import { TransformerBeforeStepContextProvider } from '@aws-amplify/graphql-transformer-interfaces'; @@ -320,7 +320,7 @@ export const propagateApiKeyToNestedTypes: (ctx: TransformerContextProvider, def // @public (undocumented) export class RdsModelResourceGenerator extends ModelResourceGenerator { // (undocumented) - generateResources(context: TransformerContextProvider, dbTypeOverride?: ModelDataSourceStrategySqlDbType): void; + generateResources(context: TransformerContextProvider, strategyOverride?: SQLLambdaModelDataSourceStrategy): void; // (undocumented) protected readonly generatorType = "RdsModelResourceGenerator"; // (undocumented) diff --git a/packages/amplify-graphql-model-transformer/src/__tests__/amplify-dynamodb-table-generator.test.ts b/packages/amplify-graphql-model-transformer/src/__tests__/amplify-dynamodb-table-generator.test.ts index e3573e5ef0..865c4bf2d3 100644 --- a/packages/amplify-graphql-model-transformer/src/__tests__/amplify-dynamodb-table-generator.test.ts +++ b/packages/amplify-graphql-model-transformer/src/__tests__/amplify-dynamodb-table-generator.test.ts @@ -1,8 +1,7 @@ import { testTransform } from '@aws-amplify/graphql-transformer-test-utils'; import { - DDB_AMPLIFY_MANAGED_DATASOURCE_TYPE, - DDB_DEFAULT_DATASOURCE_TYPE, - constructDataSourceMap, + DDB_AMPLIFY_MANAGED_DATASOURCE_STRATEGY, + DDB_DEFAULT_DATASOURCE_STRATEGY, validateModelSchema, } from '@aws-amplify/graphql-transformer-core'; import { parse } from 'graphql'; @@ -22,12 +21,13 @@ describe('ModelTransformer:', () => { content: String } `; - const modelToDatasourceMap = constructDataSourceMap(validSchema, DDB_DEFAULT_DATASOURCE_TYPE); - modelToDatasourceMap.set('Post', DDB_AMPLIFY_MANAGED_DATASOURCE_TYPE); const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer()], - modelToDatasourceMap, + dataSourceStrategies: { + Comment: DDB_DEFAULT_DATASOURCE_STRATEGY, + Post: DDB_AMPLIFY_MANAGED_DATASOURCE_STRATEGY, + }, }); expect(out).toBeDefined(); const amplifyTableManagerStack = out.stacks[ITERATIVE_TABLE_STACK_NAME]; diff --git a/packages/amplify-graphql-model-transformer/src/__tests__/amplify-sql-resource-generator.test.ts b/packages/amplify-graphql-model-transformer/src/__tests__/amplify-sql-resource-generator.test.ts new file mode 100644 index 0000000000..e8373d20d2 --- /dev/null +++ b/packages/amplify-graphql-model-transformer/src/__tests__/amplify-sql-resource-generator.test.ts @@ -0,0 +1,46 @@ +import { testTransform } from '@aws-amplify/graphql-transformer-test-utils'; +import { MYSQL_DB_TYPE, constructDataSourceStrategies, validateModelSchema } from '@aws-amplify/graphql-transformer-core'; +import { parse } from 'graphql'; +import { SQLLambdaModelDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; +import { PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer'; +import { ResourceConstants } from 'graphql-transformer-common'; +import { ModelTransformer } from '../graphql-model-transformer'; + +describe('ModelTransformer with SQL data sources:', () => { + const mysqlStrategy: SQLLambdaModelDataSourceStrategy = { + name: 'mysqlStrategy', + dbType: MYSQL_DB_TYPE, + dbConnectionConfig: { + hostnameSsmPath: '/test/hostname', + portSsmPath: '/test/port', + usernameSsmPath: '/test/username', + passwordSsmPath: '/test/password', + databaseNameSsmPath: '/test/databaseName', + }, + }; + + it('should successfully transform simple valid schema', async () => { + const validSchema = ` + type Post @model { + id: ID! @primaryKey + title: String! + } + type Comment @model { + id: ID! @primaryKey + content: String + } + `; + + const out = testTransform({ + schema: validSchema, + transformers: [new ModelTransformer(), new PrimaryKeyTransformer()], + dataSourceStrategies: constructDataSourceStrategies(validSchema, mysqlStrategy), + }); + expect(out).toBeDefined(); + const sqlApiStack = out.stacks[ResourceConstants.RESOURCES.SQLStackName]; + expect(sqlApiStack).toBeDefined(); + expect(out.functions[`${ResourceConstants.RESOURCES.SQLLambdaLogicalID}.zip`]).toBeDefined(); + expect(out.functions[`${ResourceConstants.RESOURCES.SQLPatchingLambdaLogicalID}.zip`]).toBeDefined(); + validateModelSchema(parse(out.schema)); + }); +}); diff --git a/packages/amplify-graphql-model-transformer/src/__tests__/model-transformer.test.ts b/packages/amplify-graphql-model-transformer/src/__tests__/model-transformer.test.ts index a0dfb31582..6b68e496ab 100644 --- a/packages/amplify-graphql-model-transformer/src/__tests__/model-transformer.test.ts +++ b/packages/amplify-graphql-model-transformer/src/__tests__/model-transformer.test.ts @@ -1,15 +1,21 @@ import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; -import { ConflictHandlerType, validateModelSchema, MYSQL_DB_TYPE, POSTGRES_DB_TYPE } from '@aws-amplify/graphql-transformer-core'; +import { + ConflictHandlerType, + validateModelSchema, + MYSQL_DB_TYPE, + POSTGRES_DB_TYPE, + constructDataSourceStrategies, +} from '@aws-amplify/graphql-transformer-core'; import { InputObjectTypeDefinitionNode, InputValueDefinitionNode, NamedTypeNode, parse } from 'graphql'; import { getBaseType } from 'graphql-transformer-common'; import { Template } from 'aws-cdk-lib/assertions'; import { testTransform } from '@aws-amplify/graphql-transformer-test-utils'; import { PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer'; import { - DataSourceType, VpcConfig, - SQLLambdaModelProvisionStrategy, ModelDataSourceStrategySqlDbType, + SQLLambdaModelDataSourceStrategy, + ModelDataSourceStrategy, } from '@aws-amplify/graphql-transformer-interfaces'; import { doNotExpectFields, @@ -24,7 +30,19 @@ import { } from './test-utils/helpers'; describe('ModelTransformer:', () => { - const rdsDatasources: ModelDataSourceStrategySqlDbType[] = [MYSQL_DB_TYPE, POSTGRES_DB_TYPE]; + const sqlDatasources: ModelDataSourceStrategySqlDbType[] = [MYSQL_DB_TYPE, POSTGRES_DB_TYPE]; + + const makeStrategy = (dbType: ModelDataSourceStrategySqlDbType): SQLLambdaModelDataSourceStrategy => ({ + name: `${dbType}Strategy`, + dbType, + dbConnectionConfig: { + databaseNameSsmPath: '/databaseNameSsmPath', + hostnameSsmPath: '/hostnameSsmPath', + passwordSsmPath: '/passwordSsmPath', + portSsmPath: '/portSsmPath', + usernameSsmPath: '/usernameSsmPath', + }, + }); it('should successfully transform simple valid schema', async () => { const validSchema = ` @@ -1520,7 +1538,7 @@ describe('ModelTransformer:', () => { expect(updateTodoIdField.type.kind).toBe('NonNullType'); }); - rdsDatasources.forEach((dbType) => { + sqlDatasources.forEach((dbType) => { it('should successfully transform simple rds valid schema', async () => { const validSchema = ` type Post @model { @@ -1532,15 +1550,7 @@ describe('ModelTransformer:', () => { const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new PrimaryKeyTransformer()], - modelToDatasourceMap: new Map( - Object.entries({ - Post: { - dbType: dbType, - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }, - }), - ), + dataSourceStrategies: constructDataSourceStrategies(validSchema, makeStrategy(dbType)), }); expect(out).toBeDefined(); @@ -1563,16 +1573,10 @@ describe('ModelTransformer:', () => { } `; - const modelToDatasourceMap = new Map(); - modelToDatasourceMap.set('Note', { - dbType: dbType, - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new PrimaryKeyTransformer()], - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(validSchema, makeStrategy(dbType)), }); expect(out).toBeDefined(); @@ -1590,13 +1594,7 @@ describe('ModelTransformer:', () => { } `; - const modelToDatasourceMap = new Map(); - modelToDatasourceMap.set('Note', { - dbType: dbType, - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - const sqlLambdaVpcConfig: VpcConfig = { + const vpcConfiguration: VpcConfig = { vpcId: 'vpc-123', securityGroupIds: ['sg-123'], subnetAvailabilityZoneConfig: [ @@ -1610,11 +1608,14 @@ describe('ModelTransformer:', () => { }, ], }; + const vpcStrategy: SQLLambdaModelDataSourceStrategy = { + ...makeStrategy(dbType), + vpcConfiguration, + }; const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new PrimaryKeyTransformer()], - modelToDatasourceMap, - sqlLambdaVpcConfig, + dataSourceStrategies: constructDataSourceStrategies(validSchema, vpcStrategy), }); expect(out).toBeDefined(); @@ -1633,7 +1634,7 @@ describe('ModelTransformer:', () => { expect(sqlLambda.Properties?.VpcConfig?.SecurityGroupIds).toEqual(expect.arrayContaining(['sg-123'])); }); - it('should fail if RDS model has no primary key defined', async () => { + it('should fail if SQL model has no primary key defined', async () => { const invalidSchema = ` type Note @model { id: ID! @@ -1641,19 +1642,13 @@ describe('ModelTransformer:', () => { } `; - const modelToDatasourceMap = new Map(); - modelToDatasourceMap.set('Note', { - dbType: dbType, - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); expect(() => testTransform({ schema: invalidSchema, transformers: [new ModelTransformer()], - modelToDatasourceMap: modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(invalidSchema, makeStrategy(dbType)), }), - ).toThrowError('RDS model "Note" must contain a primary key field'); + ).toThrowError('SQL model "Note" must contain a primary key field'); }); it('should compute and render the array fields correctly in the resolver', () => { @@ -1667,16 +1662,10 @@ describe('ModelTransformer:', () => { name: String } `; - const modelToDatasourceMap = new Map(); - modelToDatasourceMap.set('Post', { - dbType: dbType, - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); const out = testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new PrimaryKeyTransformer()], - modelToDatasourceMap: modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(validSchema, makeStrategy(dbType)), }); const expectedSnippets = [ '#set( $lambdaInput.args.metadata.nonScalarFields = ["info", "tags"] )', @@ -1693,7 +1682,7 @@ describe('ModelTransformer:', () => { }); }); - it('should fail if multiple RDS engine types are used', () => { + it('should fail if multiple SQL engine types are used', () => { const invalidSchema = ` type Note @model { id: ID! @primaryKey @@ -1705,22 +1694,16 @@ describe('ModelTransformer:', () => { } `; - const modelToDatasourceMap = new Map(); - modelToDatasourceMap.set('Note', { - dbType: rdsDatasources[0], - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - modelToDatasourceMap.set('Post', { - dbType: rdsDatasources[1], - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); + const dataSourceStrategies: Record = { + Note: makeStrategy(MYSQL_DB_TYPE), + Post: makeStrategy(POSTGRES_DB_TYPE), + }; + expect(() => testTransform({ schema: invalidSchema, transformers: [new ModelTransformer(), new PrimaryKeyTransformer()], - modelToDatasourceMap: modelToDatasourceMap, + dataSourceStrategies, }), ).toThrowError(); }); diff --git a/packages/amplify-graphql-model-transformer/src/graphql-model-transformer.ts b/packages/amplify-graphql-model-transformer/src/graphql-model-transformer.ts index f144923c1b..95ff60bab1 100644 --- a/packages/amplify-graphql-model-transformer/src/graphql-model-transformer.ts +++ b/packages/amplify-graphql-model-transformer/src/graphql-model-transformer.ts @@ -1,21 +1,26 @@ import { DDB_DB_TYPE, - MYSQL_DB_TYPE, - POSTGRES_DB_TYPE, DirectiveWrapper, FieldWrapper, generateGetArgumentsInput, getFieldNameFor, + getModelDataSourceStrategy, InputObjectDefinitionWrapper, InvalidDirectiveError, + isAmplifyDynamoDbModelDataSourceStrategy, + isDefaultDynamoDbModelDataSourceStrategy, + isDynamoDbModel, + MYSQL_DB_TYPE, ObjectDefinitionWrapper, + POSTGRES_DB_TYPE, SyncUtils, TransformerModelBase, } from '@aws-amplify/graphql-transformer-core'; import { AppSyncDataSourceType, DataSourceInstance, - DataSourceProvider, + ModelDataSourceStrategy, + ModelDataSourceStrategyDbType, MutationFieldType, QueryFieldType, SubscriptionFieldType, @@ -27,8 +32,7 @@ import { TransformerSchemaVisitStepContextProvider, TransformerTransformSchemaStepContextProvider, TransformerValidationStepContextProvider, - DataSourceType, - DynamoDBProvisionStrategy, + DataSourceStrategiesProvider, } from '@aws-amplify/graphql-transformer-interfaces'; import { ITable } from 'aws-cdk-lib/aws-dynamodb'; import * as iam from 'aws-cdk-lib/aws-iam'; @@ -114,12 +118,7 @@ export const directiveDefinition = /* GraphQl */ ` } `; -const DDB_DATASOURCE_TYPE: DataSourceType = { - dbType: DDB_DB_TYPE, - provisionDB: true, - provisionStrategy: DynamoDBProvisionStrategy.DEFAULT, -}; - +// Key for the resource generator map to reference the generator for iterative table deployments const CUSTOM_DDB_DB_TYPE = 'AmplifyDDB'; /** @@ -128,17 +127,13 @@ const CUSTOM_DDB_DB_TYPE = 'AmplifyDDB'; export class ModelTransformer extends TransformerModelBase implements TransformerModelProvider { private options: ModelTransformerOptions; - private datasourceMap: Record = {}; - private ddbTableMap: Record = {}; - private resolverMap: Record = {}; - private typesWithModelDirective: Set = new Set(); private resourceGeneratorMap: Map = new Map(); - private modelToDatasourceProvisionTypeMap: Map = new Map(); + private dataSourceStrategiesProvider: DataSourceStrategiesProvider = { dataSourceStrategies: {} }; /** * A Map to hold the directive configuration @@ -155,53 +150,45 @@ export class ModelTransformer extends TransformerModelBase implements Transforme } before = (ctx: TransformerBeforeStepContextProvider): void => { - // We only store this in the model because some of the required override methods need to pass through to the - // Resource generators, but do not have access to the context - this.modelToDatasourceProvisionTypeMap = ctx.modelToDatasourceMap; - - const datasourceMapValues: Array = Array.from(this.modelToDatasourceProvisionTypeMap.values()); - if ( - datasourceMapValues.some( - (value) => value.dbType === DDB_DB_TYPE && value.provisionDB && value.provisionStrategy === DynamoDBProvisionStrategy.DEFAULT, - ) - ) { + // We only store this the model transformer because some of the required override methods need to pass through to the Resource + // generators, but do not have access to the context + const { dataSourceStrategies, sqlDirectiveDataSourceStrategies } = ctx; + this.dataSourceStrategiesProvider = { dataSourceStrategies, sqlDirectiveDataSourceStrategies }; + + const hasDbType = (dbType: ModelDataSourceStrategyDbType): ((strategy: ModelDataSourceStrategy) => boolean) => { + return (strategy: ModelDataSourceStrategy) => strategy.dbType === dbType; + }; + + const modelDataSources = Object.values(dataSourceStrategies); + const customSqlDataSources = sqlDirectiveDataSourceStrategies?.map((dss) => dss.strategy) ?? []; + if (modelDataSources.some(isDefaultDynamoDbModelDataSourceStrategy)) { this.resourceGeneratorMap.get(DDB_DB_TYPE)?.enableGenerator(); this.resourceGeneratorMap.get(DDB_DB_TYPE)?.enableProvisioned(); } - if (datasourceMapValues.some((value) => value.dbType === MYSQL_DB_TYPE && !value.provisionDB)) { + if (modelDataSources.some(hasDbType(MYSQL_DB_TYPE)) || customSqlDataSources.some(hasDbType(MYSQL_DB_TYPE))) { this.resourceGeneratorMap.get(MYSQL_DB_TYPE)?.enableGenerator(); this.resourceGeneratorMap.get(MYSQL_DB_TYPE)?.enableUnprovisioned(); } - if (datasourceMapValues.some((value) => value.dbType === POSTGRES_DB_TYPE && !value.provisionDB)) { + if (modelDataSources.some(hasDbType(POSTGRES_DB_TYPE)) || customSqlDataSources.some(hasDbType(POSTGRES_DB_TYPE))) { this.resourceGeneratorMap.get(POSTGRES_DB_TYPE)?.enableGenerator(); this.resourceGeneratorMap.get(POSTGRES_DB_TYPE)?.enableUnprovisioned(); } - if ( - datasourceMapValues.some( - (value) => value.dbType === DDB_DB_TYPE && value.provisionDB && value.provisionStrategy === DynamoDBProvisionStrategy.AMPLIFY_TABLE, - ) - ) { + if (modelDataSources.some(isAmplifyDynamoDbModelDataSourceStrategy)) { this.resourceGeneratorMap.get(CUSTOM_DDB_DB_TYPE)?.enableGenerator(); this.resourceGeneratorMap.get(CUSTOM_DDB_DB_TYPE)?.enableProvisioned(); } - if (datasourceMapValues.length === 0) { - // Just enable DynamoDB provisioned, legacy use - this.resourceGeneratorMap.get(DDB_DB_TYPE)?.enableGenerator(); - this.resourceGeneratorMap.get(DDB_DB_TYPE)?.enableProvisioned(); - } }; object = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerSchemaVisitStepContextProvider): void => { + const typeName = definition.name.value; const isTypeNameReserved = - definition.name.value === ctx.output.getQueryTypeName() || - definition.name.value === ctx.output.getMutationTypeName() || - definition.name.value === ctx.output.getSubscriptionTypeName(); + typeName === ctx.output.getQueryTypeName() || + typeName === ctx.output.getMutationTypeName() || + typeName === ctx.output.getSubscriptionTypeName(); - const isDynamoDB = (ctx.modelToDatasourceMap.get(definition.name.value) ?? DDB_DATASOURCE_TYPE).dbType === DDB_DB_TYPE; + const isDynamoDB = isDynamoDbModel(ctx, typeName); if (isTypeNameReserved) { - throw new InvalidDirectiveError( - `'${definition.name.value}' is a reserved type name and currently in use within the default schema element.`, - ); + throw new InvalidDirectiveError(`'${typeName}' is a reserved type name and currently in use within the default schema element.`); } if (!isDynamoDB) { @@ -209,13 +196,10 @@ export class ModelTransformer extends TransformerModelBase implements Transforme return field?.directives?.some((dir) => dir.name.value === 'primaryKey'); }); if (!containsPrimaryKey) { - throw new InvalidDirectiveError(`RDS model "${definition.name.value}" must contain a primary key field`); + throw new InvalidDirectiveError(`SQL model "${typeName}" must contain a primary key field`); } } - // todo: get model configuration with default values and store it in the map - const typeName = definition.name.value; - if (ctx.isProjectUsingDataStore()) { SyncUtils.validateResolverConfigForType(ctx, typeName); } @@ -293,7 +277,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme this.modelDirectiveConfig.set(typeName, options); this.typesWithModelDirective.add(typeName); - const resourceGenerator = this.getResourceGenerator(typeName); + const resourceGenerator = this.getResourceGenerator(ctx, typeName); if (resourceGenerator) { resourceGenerator.addModelDefinition(definition, options); } else { @@ -329,7 +313,8 @@ export class ModelTransformer extends TransformerModelBase implements Transforme const subscriptionsFields = this.createSubscriptionFields(ctx, def!); ctx.output.addSubscriptionFields(subscriptionsFields); - if ((ctx.modelToDatasourceMap.get(def.name.value) ?? DDB_DATASOURCE_TYPE).dbType === DDB_DB_TYPE) { + const typeName = def.name.value; + if (isDynamoDbModel(ctx, typeName)) { // Update the field with auto generatable Fields this.addAutoGeneratableFields(ctx, type); @@ -371,7 +356,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme fieldName: string, resolverLogicalId: string, ): TransformerResolverProvider => { - const resourceGenerator = this.getResourceGenerator(type.name.value); + const resourceGenerator = this.getResourceGenerator(ctx, type.name.value); if (resourceGenerator) { return resourceGenerator.generateGetResolver(ctx, type, typeName, fieldName, resolverLogicalId); } @@ -385,7 +370,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme fieldName: string, resolverLogicalId: string, ): TransformerResolverProvider => { - const resourceGenerator = this.getResourceGenerator(type.name.value); + const resourceGenerator = this.getResourceGenerator(ctx, type.name.value); if (resourceGenerator) { // Coercing this into being defined as we're running a check on it first return resourceGenerator.generateListResolver(ctx, type, typeName, fieldName, resolverLogicalId); @@ -400,7 +385,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme fieldName: string, resolverLogicalId: string, ): TransformerResolverProvider => { - const resourceGenerator = this.getResourceGenerator(type.name.value); + const resourceGenerator = this.getResourceGenerator(ctx, type.name.value); if (resourceGenerator) { return resourceGenerator.generateCreateResolver(ctx, type, typeName, fieldName, resolverLogicalId); } @@ -415,7 +400,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme resolverLogicalId: string, ): TransformerResolverProvider => { const modelDirectiveConfig = this.modelDirectiveConfig.get(type.name.value)!; - const resourceGenerator = this.getResourceGenerator(type.name.value); + const resourceGenerator = this.getResourceGenerator(ctx, type.name.value); if (resourceGenerator) { return resourceGenerator.generateUpdateResolver(ctx, type, modelDirectiveConfig, typeName, fieldName, resolverLogicalId); } @@ -429,7 +414,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme fieldName: string, resolverLogicalId: string, ): TransformerResolverProvider => { - const resourceGenerator = this.getResourceGenerator(type.name.value); + const resourceGenerator = this.getResourceGenerator(ctx, type.name.value); if (resourceGenerator) { return resourceGenerator.generateDeleteResolver(ctx, type, typeName, fieldName, resolverLogicalId); } @@ -482,7 +467,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme fieldName: string, resolverLogicalId: string, ): TransformerResolverProvider => { - const resourceGenerator = this.getResourceGenerator(type.name.value); + const resourceGenerator = this.getResourceGenerator(ctx, type.name.value); if (resourceGenerator) { return resourceGenerator.generateSyncResolver(ctx, type, typeName, fieldName, resolverLogicalId); } @@ -579,7 +564,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme getQueryFieldNames = ( type: ObjectTypeDefinitionNode, ): Set<{ fieldName: string; typeName: string; type: QueryFieldType; resolverLogicalId: string }> => { - const resourceGenerator = this.getResourceGenerator(type.name.value); + const resourceGenerator = this.getResourceGenerator(this.dataSourceStrategiesProvider, type.name.value); if (resourceGenerator) { return resourceGenerator.getQueryFieldNames(type); } @@ -589,7 +574,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme getMutationFieldNames = ( type: ObjectTypeDefinitionNode, ): Set<{ fieldName: string; typeName: string; type: MutationFieldType; resolverLogicalId: string }> => { - const resourceGenerator = this.getResourceGenerator(type.name.value); + const resourceGenerator = this.getResourceGenerator(this.dataSourceStrategiesProvider, type.name.value); if (resourceGenerator) { return resourceGenerator.getMutationFieldNames(type); } @@ -604,7 +589,7 @@ export class ModelTransformer extends TransformerModelBase implements Transforme type: SubscriptionFieldType; resolverLogicalId: string; }> => { - const resourceGenerator = this.getResourceGenerator(type.name.value); + const resourceGenerator = this.getResourceGenerator(this.dataSourceStrategiesProvider, type.name.value); if (resourceGenerator) { return resourceGenerator.getSubscriptionFieldNames(type); } @@ -896,17 +881,22 @@ export class ModelTransformer extends TransformerModelBase implements Transforme /** * Get the resource generator based on the type name definition of model directive - * The project level strategy will be returned if type name is not found in the map - * @param typeName type name definition of model directive - * @returns datasource provision type from map. + * Throws an error if the appropriate resource type name is not found in the map */ - private getResourceGenerator = (typeName: string): ModelResourceGenerator | undefined => { - const datasourceType = this.modelToDatasourceProvisionTypeMap.get(typeName) ?? DDB_DATASOURCE_TYPE; - if (datasourceType.dbType === DDB_DB_TYPE && datasourceType.provisionStrategy === DynamoDBProvisionStrategy.DEFAULT) { - return this.resourceGeneratorMap.get(DDB_DB_TYPE); - } else if (datasourceType.dbType === DDB_DB_TYPE && datasourceType.provisionStrategy === DynamoDBProvisionStrategy.AMPLIFY_TABLE) { - return this.resourceGeneratorMap.get(CUSTOM_DDB_DB_TYPE); - } - return this.resourceGeneratorMap.get(datasourceType.dbType); + private getResourceGenerator = (ctx: DataSourceStrategiesProvider, typeName: string): ModelResourceGenerator => { + const strategy = getModelDataSourceStrategy(ctx, typeName); + let generator: ModelResourceGenerator | undefined; + if (isDefaultDynamoDbModelDataSourceStrategy(strategy)) { + generator = this.resourceGeneratorMap.get(DDB_DB_TYPE); + } else if (isAmplifyDynamoDbModelDataSourceStrategy(strategy)) { + generator = this.resourceGeneratorMap.get(CUSTOM_DDB_DB_TYPE); + } else { + generator = this.resourceGeneratorMap.get(strategy.dbType); + } + + if (!generator) { + throw new Error(`No resource generator assigned for ${typeName} with dbType ${strategy.dbType}`); + } + return generator; }; } diff --git a/packages/amplify-graphql-model-transformer/src/resolvers/rds/resolver.ts b/packages/amplify-graphql-model-transformer/src/resolvers/rds/resolver.ts index bf7aa19e3f..2f8a810540 100644 --- a/packages/amplify-graphql-model-transformer/src/resolvers/rds/resolver.ts +++ b/packages/amplify-graphql-model-transformer/src/resolvers/rds/resolver.ts @@ -17,7 +17,7 @@ import { toJson, } from 'graphql-mapping-template'; import { ResourceConstants, isArrayOrObject, isListType } from 'graphql-transformer-common'; -import { RDSConnectionSecrets, setResourceName } from '@aws-amplify/graphql-transformer-core'; +import { setResourceName } from '@aws-amplify/graphql-transformer-core'; import { GraphQLAPIProvider, RDSLayerMapping, @@ -25,6 +25,7 @@ import { TransformerContextProvider, VpcConfig, ProvisionedConcurrencyConfig, + SqlModelDataSourceDbConnectionConfig, } from '@aws-amplify/graphql-transformer-interfaces'; import { Effect, IRole, Policy, PolicyStatement, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam'; import { IFunction, LayerVersion, Runtime, Alias, Function as LambdaFunction } from 'aws-cdk-lib/aws-lambda'; @@ -227,7 +228,7 @@ export const createRdsPatchingLambda = ( * @param scope Construct * @param secretEntry RDSConnectionSecrets */ -export const createRdsLambdaRole = (roleName: string, scope: Construct, secretEntry: RDSConnectionSecrets): IRole => { +export const createRdsLambdaRole = (roleName: string, scope: Construct, secretEntry: SqlModelDataSourceDbConnectionConfig): IRole => { const { SQLLambdaIAMRoleLogicalID, SQLLambdaLogAccessPolicy } = ResourceConstants.RESOURCES; const role = new Role(scope, SQLLambdaIAMRoleLogicalID, { assumedBy: new ServicePrincipal('lambda.amazonaws.com'), @@ -247,11 +248,11 @@ export const createRdsLambdaRole = (roleName: string, scope: Construct, secretEn actions: ['ssm:GetParameter', 'ssm:GetParameters'], effect: Effect.ALLOW, resources: [ - `arn:aws:ssm:*:*:parameter${secretEntry.username}`, - `arn:aws:ssm:*:*:parameter${secretEntry.password}`, - `arn:aws:ssm:*:*:parameter${secretEntry.host}`, - `arn:aws:ssm:*:*:parameter${secretEntry.database}`, - `arn:aws:ssm:*:*:parameter${secretEntry.port}`, + `arn:aws:ssm:*:*:parameter${secretEntry.usernameSsmPath}`, + `arn:aws:ssm:*:*:parameter${secretEntry.passwordSsmPath}`, + `arn:aws:ssm:*:*:parameter${secretEntry.hostnameSsmPath}`, + `arn:aws:ssm:*:*:parameter${secretEntry.databaseNameSsmPath}`, + `arn:aws:ssm:*:*:parameter${secretEntry.portSsmPath}`, ], }), ); diff --git a/packages/amplify-graphql-model-transformer/src/resources/rds-model-resource-generator.ts b/packages/amplify-graphql-model-transformer/src/resources/rds-model-resource-generator.ts index 79937d56f1..ad2af78281 100644 --- a/packages/amplify-graphql-model-transformer/src/resources/rds-model-resource-generator.ts +++ b/packages/amplify-graphql-model-transformer/src/resources/rds-model-resource-generator.ts @@ -2,8 +2,8 @@ import { Fn } from 'aws-cdk-lib'; import { Topic, SubscriptionFilter } from 'aws-cdk-lib/aws-sns'; import { LambdaSubscription } from 'aws-cdk-lib/aws-sns-subscriptions'; import { Construct } from 'constructs'; -import { RDSConnectionSecrets, getImportedRDSType, getEngineFromDBType } from '@aws-amplify/graphql-transformer-core'; -import { ModelDataSourceStrategySqlDbType, QueryFieldType, TransformerContextProvider } from '@aws-amplify/graphql-transformer-interfaces'; +import { getImportedRDSTypeFromStrategyDbType, isSqlStrategy } from '@aws-amplify/graphql-transformer-core'; +import { QueryFieldType, SQLLambdaModelDataSourceStrategy, TransformerContextProvider } from '@aws-amplify/graphql-transformer-interfaces'; import { ResourceConstants } from 'graphql-transformer-common'; import { ModelVTLGenerator, RDSModelVTLGenerator } from '../resolvers'; import { @@ -23,92 +23,124 @@ import { ModelResourceGenerator } from './model-resource-generator'; export class RdsModelResourceGenerator extends ModelResourceGenerator { protected readonly generatorType = 'RdsModelResourceGenerator'; - generateResources(context: TransformerContextProvider, dbTypeOverride?: ModelDataSourceStrategySqlDbType): void { - if (this.isEnabled()) { - const dbType = dbTypeOverride ?? getImportedRDSType(context.modelToDatasourceMap); - const engine = getEngineFromDBType(dbType); - const secretEntry = context.datasourceSecretParameterLocations.get(dbType); - const { - AmplifySQLLayerNotificationTopicAccount, - AmplifySQLLayerNotificationTopicName, - SQLLambdaDataSourceLogicalID, - SQLLambdaIAMRoleLogicalID, - SQLLambdaLogicalID, - SQLPatchingLambdaIAMRoleLogicalID, - SQLPatchingLambdaLogicalID, - SQLPatchingSubscriptionLogicalID, - SQLPatchingTopicLogicalID, - SQLStackName, - } = ResourceConstants.RESOURCES; - const lambdaRoleScope = context.stackManager.getScopeFor(SQLLambdaIAMRoleLogicalID, SQLStackName); - const lambdaScope = context.stackManager.getScopeFor(SQLLambdaLogicalID, SQLStackName); - - const layerVersionArn = resolveLayerVersion(lambdaScope, context); - - const role = createRdsLambdaRole( - context.resourceHelper.generateIAMRoleName(SQLLambdaIAMRoleLogicalID), - lambdaRoleScope, - secretEntry as RDSConnectionSecrets, - ); - - const lambda = createRdsLambda( - lambdaScope, - context.api, - role, - layerVersionArn, - { - engine: engine, - username: secretEntry?.username ?? '', - password: secretEntry?.password ?? '', - host: secretEntry?.host ?? '', - port: secretEntry?.port ?? '', - database: secretEntry?.database ?? '', - }, - context.sqlLambdaVpcConfig, - context.sqlLambdaProvisionedConcurrencyConfig, - ); - - const patchingLambdaRoleScope = context.stackManager.getScopeFor(SQLPatchingLambdaIAMRoleLogicalID, SQLStackName); - const patchingLambdaScope = context.stackManager.getScopeFor(SQLPatchingLambdaLogicalID, SQLStackName); - const patchingLambdaRole = createRdsPatchingLambdaRole( - context.resourceHelper.generateIAMRoleName(SQLPatchingLambdaIAMRoleLogicalID), - patchingLambdaRoleScope, - lambda.functionArn, - ); - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const patchingLambda = createRdsPatchingLambda(patchingLambdaScope, context.api, patchingLambdaRole, { - LAMBDA_FUNCTION_ARN: lambda.functionArn, - }); + /** + * Generates the AWS resources required for the data source. By default, this will generate resources for SQL data source(s) in the + * context's `dataSourceStrategies` and `customSqlDataSourceStrategies`, but the generator can be invoked independently to support schemas + * with custom SQL directives but no models. + * @param context the TransformerContextProvider + * @param strategyOverride an optional override for the SQL database strategy to generate resources for. + */ + generateResources(context: TransformerContextProvider, strategyOverride?: SQLLambdaModelDataSourceStrategy): void { + if (!this.isEnabled()) { + this.generateResolvers(context); + this.setFieldMappingResolverReferences(context); + return; + } - // Add SNS subscription for patching notifications - const topicArn = Fn.join(':', [ - 'arn', - 'aws', - 'sns', - Fn.ref('AWS::Region'), - AmplifySQLLayerNotificationTopicAccount, - AmplifySQLLayerNotificationTopicName, - ]); - - const patchingSubscriptionScope = context.stackManager.getScopeFor(SQLPatchingSubscriptionLogicalID, SQLStackName); - const snsTopic = Topic.fromTopicArn(patchingSubscriptionScope, SQLPatchingTopicLogicalID, topicArn); - const subscription = new LambdaSubscription(patchingLambda, { - filterPolicy: { - Region: SubscriptionFilter.stringFilter({ - allowlist: [Fn.ref('AWS::Region')], - }), - }, - }); - snsTopic.addSubscription(subscription); + const strategies: Record = {}; + if (strategyOverride) { + strategies[strategyOverride.name] = strategyOverride; + } else { + const dataSourceStrategies = Object.values(context.dataSourceStrategies).filter(isSqlStrategy); + dataSourceStrategies.forEach((strategy) => (strategies[strategy.name] = strategy)); + const sqlDirectiveDataSourceStrategies = context.sqlDirectiveDataSourceStrategies?.map((dss) => dss.strategy) ?? []; + sqlDirectiveDataSourceStrategies.forEach((strategy) => (strategies[strategy.name] = strategy)); + } - const lambdaDataSourceScope = context.stackManager.getScopeFor(SQLLambdaDataSourceLogicalID, SQLStackName); - const rdsDatasource = context.api.host.addLambdaDataSource(`${SQLLambdaDataSourceLogicalID}`, lambda, {}, lambdaDataSourceScope); - this.models.forEach((model) => { - context.dataSources.add(model, rdsDatasource); - this.datasourceMap[model.name.value] = rdsDatasource; - }); + // Unexpected, since we invoke the generateResources in response to generators that are initialized during a scan of models and custom + // SQL, but we'll be defensive here. + if (Object.keys(strategies).length === 0) { + throw new Error('No SQL datasource types are detected. This is an unexpected error.'); } + + // TODO: Remove this once we implement `combine`. For now, we only support one SQL engine + if (Object.keys(strategies).length > 1) { + throw new Error('Multiple imported SQL datasource types are detected. Only one type is supported.'); + } + + const strategy: SQLLambdaModelDataSourceStrategy = Object.values(strategies)[0]; + const dbType = strategy.dbType; + const engine = getImportedRDSTypeFromStrategyDbType(dbType); + const secretEntry = strategy.dbConnectionConfig; + const { + AmplifySQLLayerNotificationTopicAccount, + AmplifySQLLayerNotificationTopicName, + SQLLambdaDataSourceLogicalID, + SQLLambdaIAMRoleLogicalID, + SQLLambdaLogicalID, + SQLPatchingLambdaIAMRoleLogicalID, + SQLPatchingLambdaLogicalID, + SQLPatchingSubscriptionLogicalID, + SQLPatchingTopicLogicalID, + SQLStackName, + } = ResourceConstants.RESOURCES; + const lambdaRoleScope = context.stackManager.getScopeFor(SQLLambdaIAMRoleLogicalID, SQLStackName); + const lambdaScope = context.stackManager.getScopeFor(SQLLambdaLogicalID, SQLStackName); + + const layerVersionArn = resolveLayerVersion(lambdaScope, context); + + const role = createRdsLambdaRole(context.resourceHelper.generateIAMRoleName(SQLLambdaIAMRoleLogicalID), lambdaRoleScope, secretEntry); + + const environment = { + engine: engine, + username: secretEntry.usernameSsmPath, + password: secretEntry.passwordSsmPath, + host: secretEntry.hostnameSsmPath, + port: secretEntry.portSsmPath, + database: secretEntry.databaseNameSsmPath, + }; + + const lambda = createRdsLambda( + lambdaScope, + context.api, + role, + layerVersionArn, + environment, + strategy.vpcConfiguration, + strategy.sqlLambdaProvisionedConcurrencyConfig, + ); + + const patchingLambdaRoleScope = context.stackManager.getScopeFor(SQLPatchingLambdaIAMRoleLogicalID, SQLStackName); + const patchingLambdaScope = context.stackManager.getScopeFor(SQLPatchingLambdaLogicalID, SQLStackName); + const patchingLambdaRole = createRdsPatchingLambdaRole( + context.resourceHelper.generateIAMRoleName(SQLPatchingLambdaIAMRoleLogicalID), + patchingLambdaRoleScope, + lambda.functionArn, + ); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const patchingLambda = createRdsPatchingLambda(patchingLambdaScope, context.api, patchingLambdaRole, { + LAMBDA_FUNCTION_ARN: lambda.functionArn, + }); + + // Add SNS subscription for patching notifications + const topicArn = Fn.join(':', [ + 'arn', + 'aws', + 'sns', + Fn.ref('AWS::Region'), + AmplifySQLLayerNotificationTopicAccount, + AmplifySQLLayerNotificationTopicName, + ]); + + const patchingSubscriptionScope = context.stackManager.getScopeFor(SQLPatchingSubscriptionLogicalID, SQLStackName); + const snsTopic = Topic.fromTopicArn(patchingSubscriptionScope, SQLPatchingTopicLogicalID, topicArn); + const subscription = new LambdaSubscription(patchingLambda, { + filterPolicy: { + Region: SubscriptionFilter.stringFilter({ + allowlist: [Fn.ref('AWS::Region')], + }), + }, + }); + snsTopic.addSubscription(subscription); + + const lambdaDataSourceScope = context.stackManager.getScopeFor(SQLLambdaDataSourceLogicalID, SQLStackName); + const rdsDatasource = context.api.host.addLambdaDataSource(`${SQLLambdaDataSourceLogicalID}`, lambda, {}, lambdaDataSourceScope); + this.models.forEach((model) => { + context.dataSources.add(model, rdsDatasource); + this.datasourceMap[model.name.value] = rdsDatasource; + }); + this.generateResolvers(context); this.setFieldMappingResolverReferences(context); } @@ -150,6 +182,8 @@ export class RdsModelResourceGenerator extends ModelResourceGenerator { * Note that in either case, the returned value is not actually the literal layer ARN, but rather a reference to be resolved at deploy time: * in the CLI case, it's the resolution of the SQLLayerMapping; in the CDK case, it's the 'Body' response field from the AwsCustomResource's * invocation of s3::GetObject. + * + * TODO: Remove this once we remove SQL imports from Gen1 CLI. */ const resolveLayerVersion = (scope: Construct, context: TransformerContextProvider): string => { let layerVersionArn: string; diff --git a/packages/amplify-graphql-name-mapping-transformer/src/__tests__/__integ__/with-has-many.test.ts b/packages/amplify-graphql-name-mapping-transformer/src/__tests__/__integ__/with-has-many.test.ts index 076318a198..3f82ece6a5 100644 --- a/packages/amplify-graphql-name-mapping-transformer/src/__tests__/__integ__/with-has-many.test.ts +++ b/packages/amplify-graphql-name-mapping-transformer/src/__tests__/__integ__/with-has-many.test.ts @@ -1,11 +1,11 @@ import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; import { DeploymentResources, testTransform } from '@aws-amplify/graphql-transformer-test-utils'; import { BelongsToTransformer, HasManyTransformer } from '@aws-amplify/graphql-relational-transformer'; -import { DDB_DB_TYPE, MYSQL_DB_TYPE, constructDataSourceMap, isDynamoDbType } from '@aws-amplify/graphql-transformer-core'; +import { DDB_DEFAULT_DATASOURCE_STRATEGY, constructDataSourceStrategies } from '@aws-amplify/graphql-transformer-core'; import { - DynamoDBProvisionStrategy, - ModelDataSourceStrategyDbType, - SQLLambdaModelProvisionStrategy, + ModelDataSourceStrategy, + SQLLambdaModelDataSourceStrategy, + SqlModelDataSourceDbConnectionConfig, } from '@aws-amplify/graphql-transformer-interfaces'; import { PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer'; import { RefersToTransformer } from '../../graphql-refers-to-transformer'; @@ -40,15 +40,10 @@ const refersToHasMany = /* GraphQL */ ` const transformSchema = ( schema: string, - dbType: ModelDataSourceStrategyDbType, + strategy: ModelDataSourceStrategy, ): DeploymentResources & { logs: any[]; } => { - const modelToDatasourceMap = constructDataSourceMap(schema, { - dbType, - provisionDB: isDynamoDbType(dbType), - provisionStrategy: isDynamoDbType(dbType) ? DynamoDBProvisionStrategy.DEFAULT : SQLLambdaModelProvisionStrategy.DEFAULT, - }); return testTransform({ schema, transformers: [ @@ -59,7 +54,7 @@ const transformSchema = ( new MapsToTransformer(), new RefersToTransformer(), ], - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), transformParameters: { sandboxModeEnabled: true, }, @@ -68,7 +63,7 @@ const transformSchema = ( describe('@mapsTo with @hasMany', () => { it('adds CRUD input and output mappings on related type and maps related type in hasMany field resolver', () => { - const out = transformSchema(mappedHasMany, DDB_DB_TYPE); + const out = transformSchema(mappedHasMany, DDB_DEFAULT_DATASOURCE_STRATEGY); const expectedResolvers: string[] = expectedResolversForModelWithRenamedField('Task').concat('Employee.tasks.postDataLoad.1.res.vtl'); expectedResolvers.forEach((resolver) => { expect(out.resolvers[resolver]).toMatchSnapshot(); @@ -78,7 +73,20 @@ describe('@mapsTo with @hasMany', () => { describe('@refersTo with @hasMany for RDS Models', () => { it('model table names are mapped', () => { - const out = transformSchema(refersToHasMany, MYSQL_DB_TYPE); + const dbConnectionConfig: SqlModelDataSourceDbConnectionConfig = { + hostnameSsmPath: '/test/hostname', + portSsmPath: '/test/port', + usernameSsmPath: '/test/username', + passwordSsmPath: '/test/password', + databaseNameSsmPath: '/test/databaseName', + }; + const mysqlStrategy: SQLLambdaModelDataSourceStrategy = { + name: 'mysqlStrategy', + dbType: 'MYSQL', + dbConnectionConfig, + }; + + const out = transformSchema(refersToHasMany, mysqlStrategy); testTableNameMapping('Employee', 'Person', out); testTableNameMapping('Task', 'Todo', out); testRelationalFieldMapping('Employee.tasks.req.vtl', 'Todo', out); diff --git a/packages/amplify-graphql-name-mapping-transformer/src/__tests__/__integ__/with-has-one.test.ts b/packages/amplify-graphql-name-mapping-transformer/src/__tests__/__integ__/with-has-one.test.ts index 853fff767d..c09a617484 100644 --- a/packages/amplify-graphql-name-mapping-transformer/src/__tests__/__integ__/with-has-one.test.ts +++ b/packages/amplify-graphql-name-mapping-transformer/src/__tests__/__integ__/with-has-one.test.ts @@ -1,9 +1,9 @@ import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; import { DeploymentResources, testTransform } from '@aws-amplify/graphql-transformer-test-utils'; import { BelongsToTransformer, HasOneTransformer } from '@aws-amplify/graphql-relational-transformer'; -import { DDB_DEFAULT_DATASOURCE_TYPE, MYSQL_DB_TYPE, constructDataSourceMap } from '@aws-amplify/graphql-transformer-core'; -import { DataSourceType, SQLLambdaModelProvisionStrategy } from '@aws-amplify/graphql-transformer-interfaces'; +import { DDB_DEFAULT_DATASOURCE_STRATEGY, MYSQL_DB_TYPE, constructDataSourceStrategies } from '@aws-amplify/graphql-transformer-core'; import { PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer'; +import { ModelDataSourceStrategy, SQLLambdaModelDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; import { RefersToTransformer } from '../../graphql-refers-to-transformer'; import { MapsToTransformer } from '../../graphql-maps-to-transformer'; import { expectedResolversForModelWithRenamedField, testTableNameMapping, testRelationalFieldMapping } from './common'; @@ -62,7 +62,7 @@ const refersToHasOne = /* GraphQL */ ` const transformSchema = ( schema: string, - dataSourceType: DataSourceType, + strategy: ModelDataSourceStrategy, ): DeploymentResources & { logs: any[]; } => { @@ -76,7 +76,7 @@ const transformSchema = ( new MapsToTransformer(), new RefersToTransformer(), ], - modelToDatasourceMap: constructDataSourceMap(schema, dataSourceType), + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), transformParameters: { sandboxModeEnabled: true, }, @@ -85,7 +85,7 @@ const transformSchema = ( describe('@mapsTo with @hasOne', () => { it('adds CRUD input and output mappings on hasOne type', () => { - const out = transformSchema(mappedHasOne, DDB_DEFAULT_DATASOURCE_TYPE); + const out = transformSchema(mappedHasOne, DDB_DEFAULT_DATASOURCE_STRATEGY); const expectedResolvers: string[] = expectedResolversForModelWithRenamedField('Employee'); expectedResolvers.forEach((resolver) => { expect(out.resolvers[resolver]).toMatchSnapshot(); @@ -93,7 +93,7 @@ describe('@mapsTo with @hasOne', () => { }); it('if belongsTo related type is renamed, adds mappings when fetching related type through hasOne field', () => { - const out = transformSchema(mappedBelongsTo, DDB_DEFAULT_DATASOURCE_TYPE); + const out = transformSchema(mappedBelongsTo, DDB_DEFAULT_DATASOURCE_STRATEGY); expect(out.resolvers['Employee.task.postDataLoad.1.res.vtl']).toMatchInlineSnapshot(` "$util.qr($ctx.prev.result.put(\\"taskEmployeeId\\", $ctx.prev.result.todoEmployeeId)) $util.qr($ctx.prev.result.remove(\\"todoEmployeeId\\")) @@ -102,7 +102,7 @@ describe('@mapsTo with @hasOne', () => { }); it('if bi-di hasOne, remaps foreign key in both types', () => { - const out = transformSchema(biDiHasOneMapped, DDB_DEFAULT_DATASOURCE_TYPE); + const out = transformSchema(biDiHasOneMapped, DDB_DEFAULT_DATASOURCE_STRATEGY); expect(out.resolvers['Employee.task.postDataLoad.1.res.vtl']).toMatchInlineSnapshot(` "$util.qr($ctx.prev.result.put(\\"taskEmployeeId\\", $ctx.prev.result.todoEmployeeId)) $util.qr($ctx.prev.result.remove(\\"todoEmployeeId\\")) @@ -118,11 +118,19 @@ describe('@mapsTo with @hasOne', () => { describe('@refersTo with @hasOne for RDS Models', () => { it('model table names are mapped', () => { - const out = transformSchema(refersToHasOne, { + const mySqlStrategy: SQLLambdaModelDataSourceStrategy = { + name: 'mySqlStrategy', dbType: MYSQL_DB_TYPE, - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); + dbConnectionConfig: { + databaseNameSsmPath: '/databaseNameSsmPath', + hostnameSsmPath: '/hostnameSsmPath', + passwordSsmPath: '/passwordSsmPath', + portSsmPath: '/portSsmPath', + usernameSsmPath: '/usernameSsmPath', + }, + }; + + const out = transformSchema(refersToHasOne, mySqlStrategy); testTableNameMapping('Employee', 'Person', out); testTableNameMapping('Task', 'Todo', out); testRelationalFieldMapping('Employee.task.req.vtl', 'Todo', out); diff --git a/packages/amplify-graphql-name-mapping-transformer/src/__tests__/__integ__/with-model.test.ts b/packages/amplify-graphql-name-mapping-transformer/src/__tests__/__integ__/with-model.test.ts index b57a9ca05e..5fa33381a0 100644 --- a/packages/amplify-graphql-name-mapping-transformer/src/__tests__/__integ__/with-model.test.ts +++ b/packages/amplify-graphql-name-mapping-transformer/src/__tests__/__integ__/with-model.test.ts @@ -1,22 +1,34 @@ import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; import { DeploymentResources, testTransform } from '@aws-amplify/graphql-transformer-test-utils'; import { MapsToTransformer } from '@aws-amplify/graphql-maps-to-transformer'; -import { DDB_DEFAULT_DATASOURCE_TYPE, MYSQL_DB_TYPE, constructDataSourceMap } from '@aws-amplify/graphql-transformer-core'; import { PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer'; -import { DataSourceType, SQLLambdaModelProvisionStrategy } from '@aws-amplify/graphql-transformer-interfaces'; +import { DDB_DEFAULT_DATASOURCE_STRATEGY, MYSQL_DB_TYPE, constructDataSourceStrategies } from '@aws-amplify/graphql-transformer-core'; +import { ModelDataSourceStrategy, SQLLambdaModelDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; import { RefersToTransformer } from '../../graphql-refers-to-transformer'; import { testTableNameMapping, testColumnNameMapping } from './common'; +const mySqlStrategy: SQLLambdaModelDataSourceStrategy = { + name: 'mySqlStrategy', + dbType: MYSQL_DB_TYPE, + dbConnectionConfig: { + databaseNameSsmPath: '/databaseNameSsmPath', + hostnameSsmPath: '/hostnameSsmPath', + passwordSsmPath: '/passwordSsmPath', + portSsmPath: '/portSsmPath', + usernameSsmPath: '/usernameSsmPath', + }, +}; + const transformSchema = ( schema: string, - dataSourceType: DataSourceType, + strategy: ModelDataSourceStrategy, ): DeploymentResources & { logs: any[]; } => { return testTransform({ schema, transformers: [new ModelTransformer(), new PrimaryKeyTransformer(), new MapsToTransformer(), new RefersToTransformer()], - modelToDatasourceMap: constructDataSourceMap(schema, dataSourceType), + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), transformParameters: { sandboxModeEnabled: true, }, @@ -31,7 +43,7 @@ describe('@mapsTo directive on model type', () => { title: String! } `; - const out = transformSchema(basicSchema, DDB_DEFAULT_DATASOURCE_TYPE); + const out = transformSchema(basicSchema, DDB_DEFAULT_DATASOURCE_STRATEGY); expect(out.stacks.Task.Resources!.TaskTable!.Properties.TableName).toMatchInlineSnapshot(` Object { "Fn::Join": Array [ @@ -81,11 +93,7 @@ describe('@refersTo with SQL Models', () => { title: String! } `; - const out = transformSchema(basicSchema, { - dbType: MYSQL_DB_TYPE, - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); + const out = transformSchema(basicSchema, mySqlStrategy); testTableNameMapping('Todo', 'Task', out); }); @@ -96,11 +104,7 @@ describe('@refersTo with SQL Models', () => { title: String! @refersTo(name: "description") } `; - const out = transformSchema(basicSchema, { - dbType: MYSQL_DB_TYPE, - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); + const out = transformSchema(basicSchema, mySqlStrategy); testColumnNameMapping('Todo', out); }); }); diff --git a/packages/amplify-graphql-name-mapping-transformer/src/__tests__/graphql-maps-to-transformer.test.ts b/packages/amplify-graphql-name-mapping-transformer/src/__tests__/graphql-maps-to-transformer.test.ts index 9a6021ee89..825caf1ca7 100644 --- a/packages/amplify-graphql-name-mapping-transformer/src/__tests__/graphql-maps-to-transformer.test.ts +++ b/packages/amplify-graphql-name-mapping-transformer/src/__tests__/graphql-maps-to-transformer.test.ts @@ -1,6 +1,5 @@ import { DirectiveNode, Kind, ObjectTypeDefinitionNode, parse } from 'graphql'; import { - DynamoDBProvisionStrategy, FieldMapEntry, ModelFieldMap, TransformerContextProvider, @@ -8,7 +7,7 @@ import { TransformerSchemaVisitStepContextProvider, } from '@aws-amplify/graphql-transformer-interfaces'; import { LambdaDataSource } from 'aws-cdk-lib/aws-appsync'; -import { DDB_DB_TYPE, constructDataSourceMap } from '@aws-amplify/graphql-transformer-core'; +import { DDB_DEFAULT_DATASOURCE_STRATEGY, constructDataSourceStrategies } from '@aws-amplify/graphql-transformer-core'; import { MapsToTransformer } from '../graphql-maps-to-transformer'; import { attachInputMappingSlot, attachResponseMappingSlot, attachFilterAndConditionInputMappingSlot } from '../field-mapping-resolvers'; import { createMappingLambda } from '../field-mapping-lambda'; @@ -87,11 +86,7 @@ describe('@mapsTo directive', () => { { ...stubTransformerContextBase, inputDocument: ast, - modelToDatasourceMap: constructDataSourceMap(schema, { - dbType: DDB_DB_TYPE, - provisionDB: true, - provisionStrategy: DynamoDBProvisionStrategy.DEFAULT, - }), + dataSourceStrategies: constructDataSourceStrategies(schema, DDB_DEFAULT_DATASOURCE_STRATEGY), } as unknown as TransformerSchemaVisitStepContextProvider, ] as const; }; @@ -158,11 +153,7 @@ describe('@mapsTo directive', () => { const transformerContext = { ...stubTransformerContextBase, - modelToDatasourceMap: constructDataSourceMap(simpleSchema, { - dbType: DDB_DB_TYPE, - provisionDB: true, - provisionStrategy: DynamoDBProvisionStrategy.DEFAULT, - }), + dataSourceStrategies: constructDataSourceStrategies(simpleSchema, DDB_DEFAULT_DATASOURCE_STRATEGY), }; // test @@ -223,11 +214,7 @@ describe('@mapsTo directive', () => { const transformerContext = { ...stubTransformerContextBase, - modelToDatasourceMap: constructDataSourceMap(simpleSchema, { - dbType: DDB_DB_TYPE, - provisionDB: true, - provisionStrategy: DynamoDBProvisionStrategy.DEFAULT, - }), + dataSourceStrategies: constructDataSourceStrategies(simpleSchema, DDB_DEFAULT_DATASOURCE_STRATEGY), }; // test @@ -279,11 +266,7 @@ describe('@mapsTo directive', () => { const transformerContext = { ...stubTransformerContextBase, - modelToDatasourceMap: constructDataSourceMap(simpleSchema, { - dbType: DDB_DB_TYPE, - provisionDB: true, - provisionStrategy: DynamoDBProvisionStrategy.DEFAULT, - }), + dataSourceStrategies: constructDataSourceStrategies(simpleSchema, DDB_DEFAULT_DATASOURCE_STRATEGY), }; // test @@ -313,11 +296,7 @@ describe('@mapsTo directive', () => { const transformerContext = { ...stubTransformerContextBase, - modelToDatasourceMap: constructDataSourceMap(simpleSchema, { - dbType: DDB_DB_TYPE, - provisionDB: true, - provisionStrategy: DynamoDBProvisionStrategy.DEFAULT, - }), + dataSourceStrategies: constructDataSourceStrategies(simpleSchema, DDB_DEFAULT_DATASOURCE_STRATEGY), }; // test diff --git a/packages/amplify-graphql-name-mapping-transformer/src/__tests__/graphql-refers-to-transformer.test.ts b/packages/amplify-graphql-name-mapping-transformer/src/__tests__/graphql-refers-to-transformer.test.ts index 30f64e60bf..239191b992 100644 --- a/packages/amplify-graphql-name-mapping-transformer/src/__tests__/graphql-refers-to-transformer.test.ts +++ b/packages/amplify-graphql-name-mapping-transformer/src/__tests__/graphql-refers-to-transformer.test.ts @@ -1,13 +1,11 @@ import { DirectiveNode, FieldDefinitionNode, Kind, ObjectTypeDefinitionNode, parse } from 'graphql'; import { - DataSourceType, - DynamoDBProvisionStrategy, - SQLLambdaModelProvisionStrategy, + SQLLambdaModelDataSourceStrategy, TransformerContextProvider, TransformerPreProcessContextProvider, TransformerSchemaVisitStepContextProvider, } from '@aws-amplify/graphql-transformer-interfaces'; -import { DDB_DEFAULT_DATASOURCE_TYPE, MYSQL_DB_TYPE, constructDataSourceMap } from '@aws-amplify/graphql-transformer-core'; +import { DDB_DEFAULT_DATASOURCE_STRATEGY, MYSQL_DB_TYPE, constructDataSourceStrategies } from '@aws-amplify/graphql-transformer-core'; import { RefersToTransformer } from '../graphql-refers-to-transformer'; import { attachFieldMappingSlot } from '../field-mapping-resolvers'; @@ -17,10 +15,16 @@ type DeepWriteable = { -readonly [P in keyof T]: DeepWriteable }; const refersToTransformer = new RefersToTransformer(); -const MYSQL_DATASOURCE_TYPE: DataSourceType = { +const mySqlStrategy: SQLLambdaModelDataSourceStrategy = { + name: 'mySqlStrategy', dbType: MYSQL_DB_TYPE, - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, + dbConnectionConfig: { + databaseNameSsmPath: '/databaseNameSsmPath', + hostnameSsmPath: '/hostnameSsmPath', + passwordSsmPath: '/passwordSsmPath', + portSsmPath: '/portSsmPath', + usernameSsmPath: '/usernameSsmPath', + }, }; describe('@refersTo directive on models', () => { @@ -89,7 +93,7 @@ describe('@refersTo directive on models', () => { { ...stubTransformerContextBase, inputDocument: ast, - modelToDatasourceMap: constructDataSourceMap(schema, MYSQL_DATASOURCE_TYPE), + dataSourceStrategies: constructDataSourceStrategies(schema, mySqlStrategy), } as unknown as TransformerSchemaVisitStepContextProvider, ] as const; }; @@ -118,7 +122,7 @@ describe('@refersTo directive on models', () => { } `; const [stubDefinition, stubDirective, stubTransformerContext] = getTransformerInputsFromSchema(schema, 'DDBModel'); - stubTransformerContext.modelToDatasourceMap.set('DDBModel', DDB_DEFAULT_DATASOURCE_TYPE); + stubTransformerContext.dataSourceStrategies.DDBModel = DDB_DEFAULT_DATASOURCE_STRATEGY; stubDirective.arguments = []; expect(() => refersToTransformer.object(stubDefinition as ObjectTypeDefinitionNode, stubDirective as DirectiveNode, stubTransformerContext), @@ -248,7 +252,7 @@ describe('@refersTo directive on fields', () => { { ...stubTransformerContextBase, inputDocument: ast, - modelToDatasourceMap: constructDataSourceMap(schema, MYSQL_DATASOURCE_TYPE), + dataSourceStrategies: constructDataSourceStrategies(schema, mySqlStrategy), } as unknown as TransformerSchemaVisitStepContextProvider, ] as const; }; @@ -279,7 +283,7 @@ describe('@refersTo directive on fields', () => { } `; const [parent, field, directive, context] = getTransformerInputsFromSchema(schema, modelName); - context.modelToDatasourceMap.set(modelName, DDB_DEFAULT_DATASOURCE_TYPE); + context.dataSourceStrategies[modelName] = DDB_DEFAULT_DATASOURCE_STRATEGY; expect(() => refersToTransformer.field(parent as ObjectTypeDefinitionNode, field as FieldDefinitionNode, directive as DirectiveNode, context), ).toThrowErrorMatchingInlineSnapshot('"@refersTo is only supported on RDS models. DDBModel is not an RDS model."'); @@ -374,7 +378,7 @@ describe('@refersTo directive on fields', () => { it('does not attach resolver slot even if field mapping exists for DDB Model', () => { const [parent, field, directive, context] = getTransformerInputsFromSchema(simpleSchema, modelName); - context.modelToDatasourceMap.set(modelName, DDB_DEFAULT_DATASOURCE_TYPE); + context.dataSourceStrategies[modelName] = DDB_DEFAULT_DATASOURCE_STRATEGY; expect(attachFieldMappingSlot_mock).toBeCalledTimes(0); }); diff --git a/packages/amplify-graphql-name-mapping-transformer/src/graphql-maps-to-transformer.ts b/packages/amplify-graphql-name-mapping-transformer/src/graphql-maps-to-transformer.ts index 948345a6b2..4223ce9b8c 100644 --- a/packages/amplify-graphql-name-mapping-transformer/src/graphql-maps-to-transformer.ts +++ b/packages/amplify-graphql-name-mapping-transformer/src/graphql-maps-to-transformer.ts @@ -1,4 +1,4 @@ -import { TransformerPluginBase, DDB_DB_TYPE, isSqlModel } from '@aws-amplify/graphql-transformer-core'; +import { TransformerPluginBase, isSqlModel, isDynamoDbModel } from '@aws-amplify/graphql-transformer-core'; import { TransformerContextProvider, TransformerPluginType, @@ -18,13 +18,13 @@ const directiveDefinition = ` export class MapsToTransformer extends TransformerPluginBase { constructor() { - super(`amplify-maps-to-transformer`, directiveDefinition, TransformerPluginType.GENERIC); + super('amplify-maps-to-transformer', directiveDefinition, TransformerPluginType.GENERIC); } /** * During the AST tree walking, the mapsTo transformer registers any renamed models with the ctx.resourceHelper. */ - object = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerSchemaVisitStepContextProvider) => { + object = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerSchemaVisitStepContextProvider): void => { shouldBeAppliedToModel(definition, directiveName); shouldBeAppliedToDDBModels(definition, ctx as TransformerContextProvider); const modelName = definition.name.value; @@ -36,7 +36,7 @@ export class MapsToTransformer extends TransformerPluginBase { * Run pre-mutation steps on the schema to support mapsTo * @param context The pre-processing context for the transformer, used to store type mappings */ - preMutateSchema = (context: TransformerPreProcessContextProvider) => { + preMutateSchema = (context: TransformerPreProcessContextProvider): void => { setTypeMappingInSchema(context, directiveName); }; @@ -44,7 +44,7 @@ export class MapsToTransformer extends TransformerPluginBase { * During the generateResolvers step, the mapsTo transformer reads all of the model field mappings from the resourceHelper and generates * VTL to map the current field names to the original field names */ - after = (context: TransformerContextProvider) => { + after = (context: TransformerContextProvider): void => { context.resourceHelper.getModelFieldMapKeys().forEach((modelName) => { if (isSqlModel(context, modelName)) { return; @@ -108,10 +108,9 @@ export class MapsToTransformer extends TransformerPluginBase { export const shouldBeAppliedToDDBModels = ( definition: ObjectTypeDefinitionNode | ObjectTypeExtensionNode, ctx: TransformerContextProvider, -) => { +): void => { const modelName = definition.name.value; - const dbInfo = ctx.modelToDatasourceMap.get(modelName); - if (!(dbInfo?.dbType === DDB_DB_TYPE)) { + if (!isDynamoDbModel(ctx, modelName)) { throw new Error(`${directiveName} is only supported on DynamoDB models. ${modelName} is not a DDB model.`); } }; diff --git a/packages/amplify-graphql-name-mapping-transformer/src/graphql-refers-to-transformer.ts b/packages/amplify-graphql-name-mapping-transformer/src/graphql-refers-to-transformer.ts index c71472b35b..7001ff08b7 100644 --- a/packages/amplify-graphql-name-mapping-transformer/src/graphql-refers-to-transformer.ts +++ b/packages/amplify-graphql-name-mapping-transformer/src/graphql-refers-to-transformer.ts @@ -1,4 +1,4 @@ -import { TransformerPluginBase, isSqlModel, getFieldNameFor, InvalidDirectiveError } from '@aws-amplify/graphql-transformer-core'; +import { TransformerPluginBase, isSqlModel, InvalidDirectiveError } from '@aws-amplify/graphql-transformer-core'; import { TransformerContextProvider, TransformerPluginType, @@ -31,13 +31,13 @@ const directiveDefinition = ` export class RefersToTransformer extends TransformerPluginBase { constructor() { - super(`amplify-refers-to-transformer`, directiveDefinition, TransformerPluginType.GENERIC); + super('amplify-refers-to-transformer', directiveDefinition, TransformerPluginType.GENERIC); } /** * Register any renamed models with the ctx.resourceHelper. */ - object = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerSchemaVisitStepContextProvider) => { + object = (definition: ObjectTypeDefinitionNode, directive: DirectiveNode, ctx: TransformerSchemaVisitStepContextProvider): void => { const context = ctx as TransformerContextProvider; shouldBeAppliedToModel(definition, directiveName); shouldBeAppliedToRDSModels(definition, context); @@ -54,7 +54,7 @@ export class RefersToTransformer extends TransformerPluginBase { definition: FieldDefinitionNode, directive: DirectiveNode, ctx: TransformerSchemaVisitStepContextProvider, - ) => { + ): void => { if (parent.kind === Kind.INTERFACE_TYPE_DEFINITION) { throw new InvalidDirectiveError( `@refersTo directive cannot be placed on "${parent?.name?.value}" interface's ${definition?.name?.value} field.`, @@ -73,7 +73,7 @@ export class RefersToTransformer extends TransformerPluginBase { * During the generateResolvers step, the refersTo transformer reads all of the model field mappings from the resourceHelper and generates * VTL to store the field mappings in the resolver context stash */ - after = (context: TransformerContextProvider) => { + after = (context: TransformerContextProvider): void => { context.resourceHelper.getModelFieldMapKeys().forEach((modelName) => { if (!isSqlModel(context, modelName)) { return; @@ -102,7 +102,7 @@ export class RefersToTransformer extends TransformerPluginBase { * Run pre-mutation steps on the schema to support refersTo * @param context The pre-processing context for the transformer, used to store type mappings */ - preMutateSchema = (context: TransformerPreProcessContextProvider) => { + preMutateSchema = (context: TransformerPreProcessContextProvider): void => { setTypeMappingInSchema(context, directiveName); }; } @@ -110,14 +110,14 @@ export class RefersToTransformer extends TransformerPluginBase { export const shouldBeAppliedToRDSModels = ( definition: ObjectTypeDefinitionNode | ObjectTypeExtensionNode, ctx: TransformerContextProvider, -) => { +): void => { const modelName = definition.name.value; if (!isSqlModel(ctx, modelName)) { throw new Error(`@${directiveName} is only supported on RDS models. ${modelName} is not an RDS model.`); } }; -export const shouldNotBeOnRelationalField = (definition: FieldDefinitionNode, modelName: string) => { +export const shouldNotBeOnRelationalField = (definition: FieldDefinitionNode, modelName: string): void => { const relationalDirectives = ['hasOne', 'hasMany', 'belongsTo', 'manyToMany']; if (definition?.directives?.some((directive) => relationalDirectives.includes(directive?.name?.value))) { throw new Error(`@${directiveName} is not supported on "${definition?.name?.value}" relational field in "${modelName}" model.`); diff --git a/packages/amplify-graphql-relational-transformer/src/__tests__/amplify-graphql-belongs-to-transformer.test.ts b/packages/amplify-graphql-relational-transformer/src/__tests__/amplify-graphql-belongs-to-transformer.test.ts index b6e0a83686..0323b5ff3d 100644 --- a/packages/amplify-graphql-relational-transformer/src/__tests__/amplify-graphql-belongs-to-transformer.test.ts +++ b/packages/amplify-graphql-relational-transformer/src/__tests__/amplify-graphql-belongs-to-transformer.test.ts @@ -1,7 +1,7 @@ import { IndexTransformer, PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer'; import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; -import { DDB_DB_TYPE, GraphQLTransform, validateModelSchema } from '@aws-amplify/graphql-transformer-core'; -import { DataSourceType, DynamoDBProvisionStrategy, SQLLambdaModelProvisionStrategy } from '@aws-amplify/graphql-transformer-interfaces'; +import { GraphQLTransform, MYSQL_DB_TYPE, constructDataSourceStrategies, validateModelSchema } from '@aws-amplify/graphql-transformer-core'; +import { SQLLambdaModelDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; import { DocumentNode, Kind, parse } from 'graphql'; import { testTransform } from '@aws-amplify/graphql-transformer-test-utils'; import { BelongsToTransformer, HasManyTransformer, HasOneTransformer } from '..'; @@ -742,19 +742,19 @@ describe('@belongsTo connection field nullability tests', () => { }); describe('@belongsTo directive with RDS datasource', () => { - test('happy case should generate correct resolvers', () => { - const modelToDatasourceMap = new Map(); - modelToDatasourceMap.set('User', { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - modelToDatasourceMap.set('Profile', { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); + const mySqlStrategy: SQLLambdaModelDataSourceStrategy = { + name: 'mySqlStrategy', + dbType: MYSQL_DB_TYPE, + dbConnectionConfig: { + databaseNameSsmPath: '/databaseNameSsmPath', + hostnameSsmPath: '/hostnameSsmPath', + passwordSsmPath: '/passwordSsmPath', + portSsmPath: '/portSsmPath', + usernameSsmPath: '/usernameSsmPath', + }, + }; + test('happy case should generate correct resolvers', () => { const inputSchema = ` type User @model { id: String! @primaryKey @@ -772,7 +772,7 @@ describe('@belongsTo directive with RDS datasource', () => { const out = testTransform({ schema: inputSchema, transformers: [new ModelTransformer(), new PrimaryKeyTransformer(), new HasOneTransformer(), new BelongsToTransformer()], - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(inputSchema, mySqlStrategy), }); expect(out).toBeDefined(); const schema = parse(out.schema); @@ -785,18 +785,6 @@ describe('@belongsTo directive with RDS datasource', () => { }); test('composite key should generate correct resolvers', () => { - const modelToDatasourceMap = new Map(); - modelToDatasourceMap.set('User', { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - modelToDatasourceMap.set('Profile', { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - const inputSchema = ` type User @model { firstName: String! @primaryKey(sortKeyFields: ["lastName"]) @@ -814,7 +802,7 @@ describe('@belongsTo directive with RDS datasource', () => { const out = testTransform({ schema: inputSchema, transformers: [new ModelTransformer(), new PrimaryKeyTransformer(), new HasOneTransformer(), new BelongsToTransformer()], - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(inputSchema, mySqlStrategy), }); expect(out).toBeDefined(); const schema = parse(out.schema); diff --git a/packages/amplify-graphql-relational-transformer/src/__tests__/amplify-graphql-has-many-transformer.test.ts b/packages/amplify-graphql-relational-transformer/src/__tests__/amplify-graphql-has-many-transformer.test.ts index 258a852408..84c153b172 100644 --- a/packages/amplify-graphql-relational-transformer/src/__tests__/amplify-graphql-has-many-transformer.test.ts +++ b/packages/amplify-graphql-relational-transformer/src/__tests__/amplify-graphql-has-many-transformer.test.ts @@ -1,7 +1,14 @@ import { IndexTransformer, PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer'; import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; -import { ConflictHandlerType, DDB_DB_TYPE, GraphQLTransform, validateModelSchema } from '@aws-amplify/graphql-transformer-core'; -import { DataSourceType, DynamoDBProvisionStrategy, SQLLambdaModelProvisionStrategy } from '@aws-amplify/graphql-transformer-interfaces'; +import { + ConflictHandlerType, + DDB_DEFAULT_DATASOURCE_STRATEGY, + GraphQLTransform, + MYSQL_DB_TYPE, + constructDataSourceStrategies, + validateModelSchema, +} from '@aws-amplify/graphql-transformer-core'; +import { SQLLambdaModelDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; import { Kind, parse } from 'graphql'; import { testTransform } from '@aws-amplify/graphql-transformer-test-utils'; import { BelongsToTransformer, HasManyTransformer, HasOneTransformer } from '..'; @@ -172,20 +179,14 @@ test('fails if @hasMany was used with a related type that is not a model', () => name: String! }`; - const DDB_DATASOURCE_TYPE: DataSourceType = { - dbType: DDB_DB_TYPE, - provisionDB: true, - provisionStrategy: DynamoDBProvisionStrategy.DEFAULT, - }; - expect(() => testTransform({ schema: inputSchema, transformers: [new ModelTransformer(), new HasManyTransformer()], - modelToDatasourceMap: new Map([ - ['Test', DDB_DATASOURCE_TYPE], - ['Test1', DDB_DATASOURCE_TYPE], - ]), + dataSourceStrategies: { + Test: DDB_DEFAULT_DATASOURCE_STRATEGY, + Test1: DDB_DEFAULT_DATASOURCE_STRATEGY, + }, }), ).toThrowError('Object type Test1 must be annotated with @model.'); }); @@ -942,19 +943,19 @@ describe('@hasMany connection field nullability tests', () => { }); describe('@hasMany directive with RDS datasource', () => { - test('happy case should generate correct resolvers', () => { - const modelToDatasourceMap = new Map(); - modelToDatasourceMap.set('Blog', { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - modelToDatasourceMap.set('Post', { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); + const mySqlStrategy: SQLLambdaModelDataSourceStrategy = { + name: 'mySqlStrategy', + dbType: MYSQL_DB_TYPE, + dbConnectionConfig: { + databaseNameSsmPath: '/databaseNameSsmPath', + hostnameSsmPath: '/hostnameSsmPath', + passwordSsmPath: '/passwordSsmPath', + portSsmPath: '/portSsmPath', + usernameSsmPath: '/usernameSsmPath', + }, + }; + test('happy case should generate correct resolvers', () => { const inputSchema = ` type Blog @model { id: String! @primaryKey @@ -971,7 +972,7 @@ describe('@hasMany directive with RDS datasource', () => { const out = testTransform({ schema: inputSchema, transformers: [new ModelTransformer(), new PrimaryKeyTransformer(), new HasManyTransformer()], - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(inputSchema, mySqlStrategy), }); expect(out).toBeDefined(); const schema = parse(out.schema); @@ -984,18 +985,6 @@ describe('@hasMany directive with RDS datasource', () => { }); test('composite key should generate correct resolvers', () => { - const modelToDatasourceMap = new Map(); - modelToDatasourceMap.set('System', { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - modelToDatasourceMap.set('Part', { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - const inputSchema = ` type System @model { systemId: String! @primaryKey(sortKeyFields: ["systemName"]) @@ -1014,7 +1003,7 @@ describe('@hasMany directive with RDS datasource', () => { const out = testTransform({ schema: inputSchema, transformers: [new ModelTransformer(), new PrimaryKeyTransformer(), new HasManyTransformer()], - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(inputSchema, mySqlStrategy), }); expect(out).toBeDefined(); const schema = parse(out.schema); diff --git a/packages/amplify-graphql-relational-transformer/src/__tests__/amplify-graphql-has-one-transformer.test.ts b/packages/amplify-graphql-relational-transformer/src/__tests__/amplify-graphql-has-one-transformer.test.ts index ade7c8cb07..f98758cd22 100644 --- a/packages/amplify-graphql-relational-transformer/src/__tests__/amplify-graphql-has-one-transformer.test.ts +++ b/packages/amplify-graphql-relational-transformer/src/__tests__/amplify-graphql-has-one-transformer.test.ts @@ -1,7 +1,14 @@ import { PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer'; import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; -import { ConflictHandlerType, DDB_DB_TYPE, GraphQLTransform, validateModelSchema } from '@aws-amplify/graphql-transformer-core'; -import { DataSourceType, DynamoDBProvisionStrategy, SQLLambdaModelProvisionStrategy } from '@aws-amplify/graphql-transformer-interfaces'; +import { + ConflictHandlerType, + DDB_DEFAULT_DATASOURCE_STRATEGY, + GraphQLTransform, + MYSQL_DB_TYPE, + constructDataSourceStrategies, + validateModelSchema, +} from '@aws-amplify/graphql-transformer-core'; +import { SQLLambdaModelDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; import { DocumentNode, Kind, parse } from 'graphql'; import { testTransform } from '@aws-amplify/graphql-transformer-test-utils'; import { HasManyTransformer, HasOneTransformer } from '..'; @@ -41,20 +48,14 @@ test('fails if @hasOne was used with a related type that is not a model', () => name: String! }`; - const DDB_DATASOURCE_TYPE: DataSourceType = { - dbType: DDB_DB_TYPE, - provisionDB: true, - provisionStrategy: DynamoDBProvisionStrategy.DEFAULT, - }; - expect(() => testTransform({ schema: inputSchema, transformers: [new ModelTransformer(), new HasOneTransformer()], - modelToDatasourceMap: new Map([ - ['Test', DDB_DATASOURCE_TYPE], - ['Test1', DDB_DATASOURCE_TYPE], - ]), + dataSourceStrategies: { + Test: DDB_DEFAULT_DATASOURCE_STRATEGY, + Test1: DDB_DEFAULT_DATASOURCE_STRATEGY, + }, }), ).toThrowError('Object type Test1 must be annotated with @model.'); }); @@ -438,7 +439,6 @@ test('@hasOne and @hasMany cannot point at each other if DataStore is enabled', id: ID! blog: Blog @hasOne }`; - ``; expect(() => testTransform({ @@ -757,19 +757,19 @@ describe('@hasOne connection field nullability tests', () => { }); describe('@hasOne directive with RDS datasource', () => { - test('happy case should generate correct resolvers', () => { - const modelToDatasourceMap = new Map(); - modelToDatasourceMap.set('User', { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - modelToDatasourceMap.set('Profile', { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); + const mySqlStrategy: SQLLambdaModelDataSourceStrategy = { + name: 'mySqlStrategy', + dbType: MYSQL_DB_TYPE, + dbConnectionConfig: { + databaseNameSsmPath: '/databaseNameSsmPath', + hostnameSsmPath: '/hostnameSsmPath', + passwordSsmPath: '/passwordSsmPath', + portSsmPath: '/portSsmPath', + usernameSsmPath: '/usernameSsmPath', + }, + }; + test('happy case should generate correct resolvers', () => { const inputSchema = ` type User @model { id: String! @primaryKey @@ -786,7 +786,7 @@ describe('@hasOne directive with RDS datasource', () => { const out = testTransform({ schema: inputSchema, transformers: [new ModelTransformer(), new PrimaryKeyTransformer(), new HasOneTransformer()], - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(inputSchema, mySqlStrategy), }); expect(out).toBeDefined(); const schema = parse(out.schema); @@ -799,18 +799,6 @@ describe('@hasOne directive with RDS datasource', () => { }); test('composite key should generate correct resolvers', () => { - const modelToDatasourceMap = new Map(); - modelToDatasourceMap.set('User', { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - modelToDatasourceMap.set('Profile', { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - const inputSchema = ` type User @model { firstName: String! @primaryKey(sortKeyFields: ["lastName"]) @@ -827,7 +815,7 @@ describe('@hasOne directive with RDS datasource', () => { const out = testTransform({ schema: inputSchema, transformers: [new ModelTransformer(), new PrimaryKeyTransformer(), new HasOneTransformer()], - modelToDatasourceMap, + dataSourceStrategies: constructDataSourceStrategies(inputSchema, mySqlStrategy), }); expect(out).toBeDefined(); const schema = parse(out.schema); diff --git a/packages/amplify-graphql-relational-transformer/src/__tests__/amplify-graphql-many-to-many-transformer.test.ts b/packages/amplify-graphql-relational-transformer/src/__tests__/amplify-graphql-many-to-many-transformer.test.ts index b12d55ca5d..60b50dcce9 100644 --- a/packages/amplify-graphql-relational-transformer/src/__tests__/amplify-graphql-many-to-many-transformer.test.ts +++ b/packages/amplify-graphql-relational-transformer/src/__tests__/amplify-graphql-many-to-many-transformer.test.ts @@ -2,12 +2,17 @@ import { AuthTransformer } from '@aws-amplify/graphql-auth-transformer'; import { IndexTransformer, PrimaryKeyTransformer } from '@aws-amplify/graphql-index-transformer'; import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; -import { DDB_DB_TYPE, GraphQLTransform, constructDataSourceMap, validateModelSchema } from '@aws-amplify/graphql-transformer-core'; +import { + DDB_DEFAULT_DATASOURCE_STRATEGY, + GraphQLTransform, + MYSQL_DB_TYPE, + constructDataSourceStrategies, + validateModelSchema, +} from '@aws-amplify/graphql-transformer-core'; import { AppSyncAuthConfiguration, - DataSourceType, - DynamoDBProvisionStrategy, - SQLLambdaModelProvisionStrategy, + ModelDataSourceStrategy, + SQLLambdaModelDataSourceStrategy, } from '@aws-amplify/graphql-transformer-interfaces'; import { DocumentNode, ObjectTypeDefinitionNode, parse } from 'graphql'; import { DeploymentResources, testTransform } from '@aws-amplify/graphql-transformer-test-utils'; @@ -172,12 +177,21 @@ test('fails if used on a SQL model', () => { id: ID! @primaryKey foos: [Foo] @manyToMany(relationName: "FooBar") }`; - const modelToDatasourceMap = constructDataSourceMap(inputSchema, { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }); - const transformer = createTransformer(undefined, modelToDatasourceMap); + + const mySqlStrategy: SQLLambdaModelDataSourceStrategy = { + name: 'mySqlStrategy', + dbType: MYSQL_DB_TYPE, + dbConnectionConfig: { + databaseNameSsmPath: '/databaseNameSsmPath', + hostnameSsmPath: '/hostnameSsmPath', + passwordSsmPath: '/passwordSsmPath', + portSsmPath: '/portSsmPath', + usernameSsmPath: '/usernameSsmPath', + }, + }; + + const dataSourceStrategies = constructDataSourceStrategies(inputSchema, mySqlStrategy); + const transformer = createTransformer(undefined, dataSourceStrategies); expect(() => transformer.transform(inputSchema)).toThrowError('@manyToMany directive cannot be used on a SQL model.'); }); @@ -669,7 +683,7 @@ describe('Pre Processing Many To Many Tests', () => { function createTransformer( overrideAuthConfig?: AppSyncAuthConfiguration, - overrideModelToDatasourceMap?: Map, + overrideDataSourceStrategies?: Record, ): { transform: (schema: string) => DeploymentResources & { logs: any[] }; preProcessSchema: (schema: DocumentNode) => DocumentNode; @@ -701,21 +715,15 @@ function createTransformer( populateOwnerFieldForStaticGroupAuth: false, }; - const DDB_DATASOURCE_TYPE: DataSourceType = { - dbType: DDB_DB_TYPE, - provisionDB: true, - provisionStrategy: DynamoDBProvisionStrategy.DEFAULT, - }; - return { transform: (schema: string) => { - const modelToDatasourceMap = overrideModelToDatasourceMap ?? constructDataSourceMap(schema, DDB_DATASOURCE_TYPE); + const dataSourceStrategies = overrideDataSourceStrategies ?? constructDataSourceStrategies(schema, DDB_DEFAULT_DATASOURCE_STRATEGY); return testTransform({ schema, authConfig, transformers, transformParameters, - modelToDatasourceMap, + dataSourceStrategies, }); }, preProcessSchema: (schema: DocumentNode) => diff --git a/packages/amplify-graphql-relational-transformer/src/graphql-belongs-to-transformer.ts b/packages/amplify-graphql-relational-transformer/src/graphql-belongs-to-transformer.ts index 664cd84de9..0f14975ae6 100644 --- a/packages/amplify-graphql-relational-transformer/src/graphql-belongs-to-transformer.ts +++ b/packages/amplify-graphql-relational-transformer/src/graphql-belongs-to-transformer.ts @@ -3,7 +3,7 @@ import { DDB_DB_TYPE, DirectiveWrapper, generateGetArgumentsInput, - getDataSourceType, + getStrategyDbTypeFromTypeNode, InvalidDirectiveError, TransformerPluginBase, isSqlModel, @@ -162,7 +162,7 @@ export class BelongsToTransformer extends TransformerPluginBase { const context = ctx as TransformerContextProvider; for (const config of this.directiveList) { - const dbType = getDataSourceType(config.field.type, context); + const dbType = getStrategyDbTypeFromTypeNode(config.field.type, context); if (dbType === DDB_DB_TYPE) { config.relatedTypeIndex = getRelatedTypeIndex(config, context); } else if (isSqlDbType(dbType)) { @@ -176,7 +176,7 @@ export class BelongsToTransformer extends TransformerPluginBase { const context = ctx as TransformerContextProvider; for (const config of this.directiveList) { - const dbType = getDataSourceType(config.field.type, context); + const dbType = getStrategyDbTypeFromTypeNode(config.field.type, context); const generator = getGenerator(dbType); generator.makeBelongsToGetItemConnectionWithKeyResolver(config, context); } @@ -188,11 +188,11 @@ const validate = (config: BelongsToDirectiveConfiguration, ctx: TransformerConte let dbType: ModelDataSourceStrategyDbType; try { - // getDataSourceType throws if a datasource is not found for the model. We want to catch that condition here to provide a friendlier + // getStrategyDbTypeFromTypeNode throws if a datasource is not found for the model. We want to catch that condition here to provide a friendlier // error message, since the most likely error scenario is that the customer neglected to annotate one of the types with `@model`. Since // this transformer gets invoked on both sides of the `belongsTo` relationship, a failure at this point is about the field itself, not // the related type. - dbType = getDataSourceType(field.type, ctx); + dbType = getStrategyDbTypeFromTypeNode(field.type, ctx); } catch { throw new InvalidDirectiveError( `Object type ${(field.type as NamedTypeNode)?.name.value ?? field.name} must be annotated with @model.`, diff --git a/packages/amplify-graphql-relational-transformer/src/graphql-has-many-transformer.ts b/packages/amplify-graphql-relational-transformer/src/graphql-has-many-transformer.ts index 786b59221e..f5fe54d336 100644 --- a/packages/amplify-graphql-relational-transformer/src/graphql-has-many-transformer.ts +++ b/packages/amplify-graphql-relational-transformer/src/graphql-has-many-transformer.ts @@ -3,7 +3,7 @@ import { DDB_DB_TYPE, DirectiveWrapper, generateGetArgumentsInput, - getDataSourceType, + getStrategyDbTypeFromTypeNode, InvalidDirectiveError, isSqlDbType, TransformerPluginBase, @@ -161,7 +161,7 @@ export class HasManyTransformer extends TransformerPluginBase { const context = ctx as TransformerContextProvider; for (const config of this.directiveList) { - const dbType = getDataSourceType(config.field.type, context); + const dbType = getStrategyDbTypeFromTypeNode(config.field.type, context); if (dbType === DDB_DB_TYPE) { config.relatedTypeIndex = getRelatedTypeIndex(config, context, config.indexName); } else if (isSqlDbType(dbType)) { @@ -176,7 +176,7 @@ export class HasManyTransformer extends TransformerPluginBase { const context = ctx as TransformerContextProvider; for (const config of this.directiveList) { - const dbType = getDataSourceType(config.field.type, context); + const dbType = getStrategyDbTypeFromTypeNode(config.field.type, context); if (dbType === DDB_DB_TYPE) { updateTableForConnection(config, context); } @@ -197,7 +197,7 @@ const makeQueryResolver = ( const validate = (config: HasManyDirectiveConfiguration, ctx: TransformerContextProvider): void => { const { field } = config; - const dbType = getDataSourceType(field.type, ctx); + const dbType = getStrategyDbTypeFromTypeNode(field.type, ctx); config.relatedType = getRelatedType(config, ctx); if (dbType === DDB_DB_TYPE) { diff --git a/packages/amplify-graphql-relational-transformer/src/graphql-has-one-transformer.ts b/packages/amplify-graphql-relational-transformer/src/graphql-has-one-transformer.ts index 133e8c8fcc..ae00d09420 100644 --- a/packages/amplify-graphql-relational-transformer/src/graphql-has-one-transformer.ts +++ b/packages/amplify-graphql-relational-transformer/src/graphql-has-one-transformer.ts @@ -3,7 +3,7 @@ import { DDB_DB_TYPE, DirectiveWrapper, generateGetArgumentsInput, - getDataSourceType, + getStrategyDbTypeFromTypeNode, InvalidDirectiveError, isSqlModel, isSqlDbType, @@ -185,7 +185,7 @@ export class HasOneTransformer extends TransformerPluginBase { const context = ctx as TransformerContextProvider; for (const config of this.directiveList) { - const dbType = getDataSourceType(config.field.type, context); + const dbType = getStrategyDbTypeFromTypeNode(config.field.type, context); if (dbType === DDB_DB_TYPE) { config.relatedTypeIndex = getRelatedTypeIndex(config, context); } else if (isSqlDbType(dbType)) { @@ -199,7 +199,7 @@ export class HasOneTransformer extends TransformerPluginBase { const context = ctx as TransformerContextProvider; for (const config of this.directiveList) { - const dbType = getDataSourceType(config.field.type, context); + const dbType = getStrategyDbTypeFromTypeNode(config.field.type, context); const generator = getGenerator(dbType); generator.makeHasOneGetItemConnectionWithKeyResolver(config, context); } @@ -209,7 +209,7 @@ export class HasOneTransformer extends TransformerPluginBase { const validate = (config: HasOneDirectiveConfiguration, ctx: TransformerContextProvider): void => { const { field } = config; - const dbType = getDataSourceType(field.type, ctx); + const dbType = getStrategyDbTypeFromTypeNode(field.type, ctx); config.relatedType = getRelatedType(config, ctx); if (dbType === DDB_DB_TYPE) { diff --git a/packages/amplify-graphql-relational-transformer/src/graphql-many-to-many-transformer.ts b/packages/amplify-graphql-relational-transformer/src/graphql-many-to-many-transformer.ts index 16b87202f1..31e928c253 100644 --- a/packages/amplify-graphql-relational-transformer/src/graphql-many-to-many-transformer.ts +++ b/packages/amplify-graphql-relational-transformer/src/graphql-many-to-many-transformer.ts @@ -1,5 +1,5 @@ import { - DDB_DEFAULT_DATASOURCE_TYPE, + DDB_DEFAULT_DATASOURCE_STRATEGY, DirectiveWrapper, InvalidDirectiveError, TransformerPluginBase, @@ -129,7 +129,7 @@ export class ManyToManyTransformer extends TransformerPluginBase { addDirectiveToRelationMap(this.relationMap, args); this.directiveList.push(args); - this.relationMap.forEach((_, relationName) => addJoinTableToModelToDatasourceMap(context, relationName)); + this.relationMap.forEach((_, relationName) => addJoinTableToDatasourceStrategies(context, relationName)); }; /** During the preProcess step, modify the document node and return it @@ -508,12 +508,12 @@ export class ManyToManyTransformer extends TransformerPluginBase { } /** - * Adds the join table created by the transformer to the context's `modelToDatasourceMap`. NOTE: This is the only place in the transformer - * chain where we use a default value for the DataSourceType. All other models must be explicitly set by the caller in the transformer - * context, but the many to many join table will always be created using the DynamoDB default provisioning strategy. + * Adds the join table created by the transformer to the context's `dataSourceStrategies`. NOTE: This is the only place in the transformer + * chain where we use a default value for the ModelDataSourceStrategy. All other models must be explicitly set by the caller in the + * transformer context, but the many to many join table will always be created using the DynamoDB default provisioning strategy. */ -const addJoinTableToModelToDatasourceMap = (ctx: DataSourceStrategiesProvider, relationName: string): void => { - ctx.modelToDatasourceMap.set(relationName, DDB_DEFAULT_DATASOURCE_TYPE); +const addJoinTableToDatasourceStrategies = (ctx: DataSourceStrategiesProvider, relationName: string): void => { + ctx.dataSourceStrategies[relationName] = DDB_DEFAULT_DATASOURCE_STRATEGY; }; function addDirectiveToRelationMap(map: Map, directive: ManyToManyDirectiveConfiguration): void { diff --git a/packages/amplify-graphql-relational-transformer/src/utils.ts b/packages/amplify-graphql-relational-transformer/src/utils.ts index b3ceae8888..f4a1b0ebf1 100644 --- a/packages/amplify-graphql-relational-transformer/src/utils.ts +++ b/packages/amplify-graphql-relational-transformer/src/utils.ts @@ -182,7 +182,7 @@ export function ensureReferencesArray( } if (!config.references) { - throw new InvalidDirectiveError(`Reference fields must be passed to @${config.directiveName} directive for RDS models.`); + throw new InvalidDirectiveError(`Reference fields must be passed to @${config.directiveName} directive for SQL models.`); } else if (!Array.isArray(config.references)) { config.references = [config.references]; } else if (config.references.length === 0) { diff --git a/packages/amplify-graphql-searchable-transformer/src/__tests__/amplify-graphql-searchable-transformer.test.ts b/packages/amplify-graphql-searchable-transformer/src/__tests__/amplify-graphql-searchable-transformer.test.ts index f1749b8892..5cda81977b 100644 --- a/packages/amplify-graphql-searchable-transformer/src/__tests__/amplify-graphql-searchable-transformer.test.ts +++ b/packages/amplify-graphql-searchable-transformer/src/__tests__/amplify-graphql-searchable-transformer.test.ts @@ -1,5 +1,4 @@ import { ConflictHandlerType, MYSQL_DB_TYPE } from '@aws-amplify/graphql-transformer-core'; -import { SQLLambdaModelProvisionStrategy } from '@aws-amplify/graphql-transformer-interfaces'; import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; import { Match, Template } from 'aws-cdk-lib/assertions'; import { parse } from 'graphql'; @@ -39,15 +38,19 @@ test('Throws error for Searchable RDS Models', () => { testTransform({ schema: validSchema, transformers: [new ModelTransformer(), new PrimaryKeyTransformer(), new SearchableModelTransformer()], - modelToDatasourceMap: new Map( - Object.entries({ - Post: { - dbType: MYSQL_DB_TYPE, - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, + dataSourceStrategies: { + Post: { + name: 'mysqlstrategy', + dbType: MYSQL_DB_TYPE, + dbConnectionConfig: { + databaseNameSsmPath: '/databaseNameSsmPath', + hostnameSsmPath: '/hostnameSsmPath', + portSsmPath: '/portSsmPath', + usernameSsmPath: '/usernameSsmPath', + passwordSsmPath: '/passwordSsmPath', }, - }), - ), + }, + }, }), ).toThrowErrorMatchingInlineSnapshot(`"@searchable is not supported on \\"Post\\" model as it uses RDS datasource."`); }); diff --git a/packages/amplify-graphql-sql-transformer/src/__tests__/amplify-graphql-sql-transformer.test.ts b/packages/amplify-graphql-sql-transformer/src/__tests__/amplify-graphql-sql-transformer.test.ts index 358cfeea95..f366ff97af 100644 --- a/packages/amplify-graphql-sql-transformer/src/__tests__/amplify-graphql-sql-transformer.test.ts +++ b/packages/amplify-graphql-sql-transformer/src/__tests__/amplify-graphql-sql-transformer.test.ts @@ -1,11 +1,23 @@ -import { validateModelSchema } from '@aws-amplify/graphql-transformer-core'; +import { MYSQL_DB_TYPE, constructDataSourceStrategies, validateModelSchema } from '@aws-amplify/graphql-transformer-core'; import { parse } from 'graphql'; import { TestTransformParameters, testTransform } from '@aws-amplify/graphql-transformer-test-utils'; import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; -import { SQLLambdaModelProvisionStrategy } from '@aws-amplify/graphql-transformer-interfaces'; +import { SQLLambdaModelDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; import { SqlTransformer } from '../graphql-sql-transformer'; describe('sql directive tests', () => { + const mySqlStrategy: SQLLambdaModelDataSourceStrategy = { + name: 'mySqlStrategy', + dbType: MYSQL_DB_TYPE, + dbConnectionConfig: { + databaseNameSsmPath: '/databaseNameSsmPath', + hostnameSsmPath: '/hostnameSsmPath', + passwordSsmPath: '/passwordSsmPath', + portSsmPath: '/portSsmPath', + usernameSsmPath: '/usernameSsmPath', + }, + }; + it('should compile happy case with statement argument', () => { const doc = /* GraphQL */ ` type Query { @@ -16,24 +28,12 @@ describe('sql directive tests', () => { const out = testTransform({ schema: doc, transformers: [new ModelTransformer(), new SqlTransformer()], - modelToDatasourceMap: new Map( - Object.entries({ - Post: { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }, - }), - ), - customSqlDataSourceStrategies: [ + dataSourceStrategies: constructDataSourceStrategies(doc, mySqlStrategy), + sqlDirectiveDataSourceStrategies: [ { typeName: 'Query', fieldName: 'calculateTaxRate', - dataSourceType: { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }, + strategy: mySqlStrategy, }, ], }); @@ -59,30 +59,17 @@ describe('sql directive tests', () => { } `; - const customQueries = new Map(); - customQueries.set('calculate-tax', 'SELECT * FROM TAXRATE WHERE ZIP = :zip'); - const out = testTransform({ schema: doc, transformers: [new ModelTransformer(), new SqlTransformer()], - customQueries, - modelToDatasourceMap: new Map( - Object.entries({ - Post: { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }, - }), - ), - customSqlDataSourceStrategies: [ + dataSourceStrategies: constructDataSourceStrategies(doc, mySqlStrategy), + sqlDirectiveDataSourceStrategies: [ { typeName: 'Query', fieldName: 'calculateTaxRate', - dataSourceType: { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, + strategy: mySqlStrategy, + customSqlStatements: { + 'calculate-tax': 'SELECT * FROM TAXRATE WHERE ZIP = :zip', }, }, ], @@ -109,30 +96,17 @@ describe('sql directive tests', () => { } `; - const customQueries = new Map(); - customQueries.set('calculate-tax-rate', 'SELECT * FROM TAXRATE WHERE ZIP = :zip'); - const transformConfig: TestTransformParameters = { schema: doc, transformers: [new ModelTransformer(), new SqlTransformer()], - customQueries, - modelToDatasourceMap: new Map( - Object.entries({ - Post: { - dbType: 'MYSQL' as const, - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }, - }), - ), - customSqlDataSourceStrategies: [ + dataSourceStrategies: constructDataSourceStrategies(doc, mySqlStrategy), + sqlDirectiveDataSourceStrategies: [ { typeName: 'Query', fieldName: 'calculateTaxRate', - dataSourceType: { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, + strategy: mySqlStrategy, + customSqlStatements: { + 'incorrect-reference-name': 'SELECT * FROM TAXRATE WHERE ZIP = :zip', }, }, ], @@ -156,31 +130,52 @@ describe('sql directive tests', () => { const transformConfig: TestTransformParameters = { schema: doc, transformers: [new ModelTransformer(), new SqlTransformer()], - customQueries, - modelToDatasourceMap: new Map( - Object.entries({ - Post: { - dbType: 'MYSQL' as const, - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, + dataSourceStrategies: constructDataSourceStrategies(doc, mySqlStrategy), + sqlDirectiveDataSourceStrategies: [ + { + typeName: 'Query', + fieldName: 'calculateTaxRate', + strategy: mySqlStrategy, + customSqlStatements: { + 'calculate-tax': 'SELECT * FROM TAXRATE WHERE ZIP = :zip', }, - }), - ), - customSqlDataSourceStrategies: [ + }, + ], + }; + + expect(() => testTransform(transformConfig)).toThrowError( + '@sql directive can have either a \'statement\' or a \'reference\' argument but not both. Check type "Query" and field "calculateTaxRate".', + ); + }); + + it('should throw error if neither statement and argument provided', () => { + const doc = /* GraphQL */ ` + type Query { + calculateTaxRate(zip: String): Int @sql + } + `; + + const customQueries = new Map(); + customQueries.set('calculate-tax', 'SELECT * FROM TAXRATE WHERE ZIP = :zip'); + + const transformConfig: TestTransformParameters = { + schema: doc, + transformers: [new ModelTransformer(), new SqlTransformer()], + dataSourceStrategies: constructDataSourceStrategies(doc, mySqlStrategy), + sqlDirectiveDataSourceStrategies: [ { typeName: 'Query', fieldName: 'calculateTaxRate', - dataSourceType: { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, + strategy: mySqlStrategy, + customSqlStatements: { + 'calculate-tax': 'SELECT * FROM TAXRATE WHERE ZIP = :zip', }, }, ], }; expect(() => testTransform(transformConfig)).toThrowError( - '@sql directive can have either \'statement\' or \'reference\' argument but not both. Check type "Query" and field "calculateTaxRate".', + '@sql directive must have either a \'statement\' or a \'reference\' argument. Check type "Query" and field "calculateTaxRate".', ); }); @@ -194,24 +189,12 @@ describe('sql directive tests', () => { const transformConfig: TestTransformParameters = { schema: doc, transformers: [new ModelTransformer(), new SqlTransformer()], - modelToDatasourceMap: new Map( - Object.entries({ - Post: { - dbType: 'MYSQL' as const, - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }, - }), - ), - customSqlDataSourceStrategies: [ + dataSourceStrategies: constructDataSourceStrategies(doc, mySqlStrategy), + sqlDirectiveDataSourceStrategies: [ { typeName: 'Query', fieldName: 'calculateTaxRate', - dataSourceType: { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }, + strategy: mySqlStrategy, }, ], }; @@ -231,24 +214,12 @@ describe('sql directive tests', () => { const transformConfig: TestTransformParameters = { schema: doc, transformers: [new ModelTransformer(), new SqlTransformer()], - modelToDatasourceMap: new Map( - Object.entries({ - Post: { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }, - }), - ), - customSqlDataSourceStrategies: [ + dataSourceStrategies: constructDataSourceStrategies(doc, mySqlStrategy), + sqlDirectiveDataSourceStrategies: [ { - typeName: 'Query', + typeName: 'Todo' as any, fieldName: 'calculateTaxRate', - dataSourceType: { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }, + strategy: mySqlStrategy, }, ], }; @@ -267,16 +238,12 @@ describe('sql directive tests', () => { const transformConfig: TestTransformParameters = { schema: doc, transformers: [new ModelTransformer(), new SqlTransformer()], - modelToDatasourceMap: new Map(), - customSqlDataSourceStrategies: [ + dataSourceStrategies: {}, + sqlDirectiveDataSourceStrategies: [ { typeName: 'Query', fieldName: 'calculateTaxRate', - dataSourceType: { - dbType: 'MYSQL', - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }, + strategy: mySqlStrategy, }, ], }; diff --git a/packages/amplify-graphql-sql-transformer/src/graphql-sql-transformer.ts b/packages/amplify-graphql-sql-transformer/src/graphql-sql-transformer.ts index 40ff3d83c5..81bb3e1cc4 100644 --- a/packages/amplify-graphql-sql-transformer/src/graphql-sql-transformer.ts +++ b/packages/amplify-graphql-sql-transformer/src/graphql-sql-transformer.ts @@ -2,13 +2,10 @@ import { DirectiveWrapper, generateGetArgumentsInput, InvalidDirectiveError, - isSqlDbType, - isSqlStrategy, MappingTemplate, TransformerPluginBase, } from '@aws-amplify/graphql-transformer-core'; import { TransformerContextProvider, TransformerSchemaVisitStepContextProvider } from '@aws-amplify/graphql-transformer-interfaces'; -import { RdsModelResourceGenerator } from '@aws-amplify/graphql-model-transformer'; import * as cdk from 'aws-cdk-lib'; import { obj, @@ -67,7 +64,7 @@ export class SqlTransformer extends TransformerPluginBase { !directive?.arguments?.find((arg) => arg.name.value === 'reference') ) { throw new InvalidDirectiveError( - `@sql directive must have either a 'statement' or 'reference' argument. Check type "${parent.name.value}" and field "${definition.name.value}".`, + `@sql directive must have either a 'statement' or a 'reference' argument. Check type "${parent.name.value}" and field "${definition.name.value}".`, ); } @@ -105,26 +102,17 @@ export class SqlTransformer extends TransformerPluginBase { this.sqlDirectiveFields.forEach((resolverFns) => { resolverFns.forEach((config) => { - const { SQLLambdaDataSourceLogicalID: dataSourceId } = ResourceConstants.RESOURCES; - let dataSource = context.api.host.getDataSource(dataSourceId); - - // Generate resources for schemas without @models, or schemas that use different data sources for custom SQL than for models - if (!dataSource) { - const generator = new RdsModelResourceGenerator(); - generator.enableGenerator(); - const typeName = config.resolverTypeName; - const fieldName = config.resolverFieldName; - const strategy = context.customSqlDataSourceStrategies?.find((css) => css.typeName === typeName && css.fieldName === fieldName); - // TODO: The isSqlDbType is redundant, but since the internals still use DataSourceType, we have to add a separate check for SQL - // db type. Remove this check once we refactor the internals to use ModelDataSourceStrategy - if (!strategy || !isSqlDbType(strategy.dataSourceType.dbType)) { - throw new Error(`Could not find custom SQL strategy for ${typeName}.${fieldName}`); - } - generator.generateResources(context, strategy.dataSourceType.dbType); - dataSource = context.api.host.getDataSource(dataSourceId); + const typeName = config.resolverTypeName; + const fieldName = config.resolverFieldName; + const strategy = context.sqlDirectiveDataSourceStrategies?.find((css) => css.typeName === typeName && css.fieldName === fieldName); + if (!strategy) { + throw new Error(`Could not find custom SQL strategy for ${typeName}.${fieldName}`); } - const statement = getStatement(config, context.customQueries); + const { SQLLambdaDataSourceLogicalID: dataSourceId } = ResourceConstants.RESOURCES; + const dataSource = context.api.host.getDataSource(dataSourceId); + + const statement = getStatement(config, strategy.customSqlStatements); const resolverResourceId = ResolverResourceIDs.ResolverResourceID(config.resolverTypeName, config.resolverFieldName); const resolver = context.resolvers.generateQueryResolver( config.resolverTypeName, @@ -167,27 +155,45 @@ const generateAuthExpressionForSandboxMode = (enabled: boolean): string => { ); }; -const getStatement = (config: SqlDirectiveConfiguration, customQueries: Map): string => { - if (config.reference && !customQueries.has(config.reference)) { +const getStatementFromStatementAttribute = (config: SqlDirectiveConfiguration): string => { + const statement = config.statement; + if (statement === undefined || statement.trim().length === 0) { throw new InvalidDirectiveError( - `@sql directive 'reference' argument must be a valid custom query name. Check type "${config.resolverTypeName}" and field "${config.resolverFieldName}". The custom query "${config.reference}" does not exist in "sql-statements" directory.`, + `@sql directive 'statement' argument must not be empty. Check type "${config.resolverTypeName}" and field "${config.resolverFieldName}".`, ); } + return statement; +}; - if (config.reference && config.statement) { +const getStatementFromReferenceAttribute = (config: SqlDirectiveConfiguration, customQueries?: Record): string => { + if (!config.reference || !customQueries || !customQueries[config.reference]) { throw new InvalidDirectiveError( - `@sql directive can have either 'statement' or 'reference' argument but not both. Check type "${config.resolverTypeName}" and field "${config.resolverFieldName}".`, + `@sql directive 'reference' argument must be a valid custom query name. Check type "${config.resolverTypeName}" and field "${config.resolverFieldName}". The custom query "${config.reference}" does not exist in "sql-statements" directory.`, ); } + return customQueries[config.reference]; +}; - if (config.statement !== undefined && config.statement.trim().length === 0) { +const getStatement = (config: SqlDirectiveConfiguration, customQueries?: Record): string => { + if (config.reference && config.statement) { throw new InvalidDirectiveError( - `@sql directive 'statement' argument must not be empty. Check type "${config.resolverTypeName}" and field "${config.resolverFieldName}".`, + `@sql directive can have either a 'statement' or a 'reference' argument but not both. Check type "${config.resolverTypeName}" and field "${config.resolverFieldName}".`, ); } - const statement = config.statement ?? customQueries.get(config.reference!); - return statement!; + if (typeof config.statement === 'string') { + return getStatementFromStatementAttribute(config); + } + + if (typeof config.reference === 'string') { + return getStatementFromReferenceAttribute(config, customQueries); + } + + // This should never happen -- it will be picked up during schema validation -- but we'll be defensive and ensure the type safety of the + // function + throw new InvalidDirectiveError( + `@sql directive must have either a 'statement' or a 'reference' argument. Check type "${config.resolverTypeName}" and field "${config.resolverFieldName}".`, + ); }; export const generateSqlLambdaRequestTemplate = (statement: string, operation: string, operationName: string): string => { diff --git a/packages/amplify-graphql-transformer-core/API.md b/packages/amplify-graphql-transformer-core/API.md index 54e8655d16..8dfd976499 100644 --- a/packages/amplify-graphql-transformer-core/API.md +++ b/packages/amplify-graphql-transformer-core/API.md @@ -4,6 +4,7 @@ ```ts +import { AmplifyDynamoDbModelDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; import { APIIAMResourceProvider } from '@aws-amplify/graphql-transformer-interfaces'; import { ApiKeyConfig } from 'aws-cdk-lib/aws-appsync'; import { AppSyncAuthConfiguration } from '@aws-amplify/graphql-transformer-interfaces'; @@ -17,12 +18,10 @@ import { CfnGraphQLSchema } from 'aws-cdk-lib/aws-appsync'; import { CfnParameter } from 'aws-cdk-lib'; import { CfnResource } from 'aws-cdk-lib'; import { Construct } from 'constructs'; -import { CustomSqlDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; import { DataSourceInstance } from '@aws-amplify/graphql-transformer-interfaces'; import { DataSourceProvider } from '@aws-amplify/graphql-transformer-interfaces'; -import { DataSourceProvisionStrategy } from '@aws-amplify/graphql-transformer-interfaces'; import { DataSourceStrategiesProvider } from '@aws-amplify/graphql-transformer-interfaces'; -import { DataSourceType } from '@aws-amplify/graphql-transformer-interfaces'; +import { DefaultDynamoDbModelDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; import { DefinitionNode } from 'graphql'; import { DirectiveDefinitionNode } from 'graphql'; import { DirectiveNode } from 'graphql'; @@ -57,11 +56,12 @@ import { NestedStackProvider } from '@aws-amplify/graphql-transformer-interfaces import { ObjectTypeDefinitionNode } from 'graphql'; import { ObjectTypeExtensionNode } from 'graphql'; import { OperationTypeDefinitionNode } from 'graphql'; -import { ProvisionedConcurrencyConfig } from '@aws-amplify/graphql-transformer-interfaces'; import { QueryFieldType } from '@aws-amplify/graphql-transformer-interfaces'; import { RDSLayerMapping } from '@aws-amplify/graphql-transformer-interfaces'; +import { RDSLayerMappingProvider } from '@aws-amplify/graphql-transformer-interfaces'; import { S3MappingTemplateProvider } from '@aws-amplify/graphql-transformer-interfaces'; import { SchemaDefinitionNode } from 'graphql'; +import { SqlDirectiveDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; import { SQLLambdaModelDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; import { Stack } from 'aws-cdk-lib'; import { StackManagerProvider } from '@aws-amplify/graphql-transformer-interfaces'; @@ -69,7 +69,6 @@ import { StringValueNode } from 'graphql'; import { SubscriptionFieldType } from '@aws-amplify/graphql-transformer-interfaces'; import { SynthParameters } from '@aws-amplify/graphql-transformer-interfaces'; import { TransformerAuthProvider } from '@aws-amplify/graphql-transformer-interfaces'; -import { TransformerBeforeStepContextProvider } from '@aws-amplify/graphql-transformer-interfaces'; import { TransformerContextMetadataProvider } from '@aws-amplify/graphql-transformer-interfaces'; import { TransformerContextOutputProvider } from '@aws-amplify/graphql-transformer-interfaces'; import { TransformerContextProvider } from '@aws-amplify/graphql-transformer-interfaces'; @@ -94,7 +93,6 @@ import { TypeNode } from 'graphql'; import { TypeSystemDefinitionNode } from 'graphql'; import { UnionTypeDefinitionNode } from 'graphql'; import { UnionTypeExtensionNode } from 'graphql'; -import { VpcConfig } from '@aws-amplify/graphql-transformer-interfaces'; // @public (undocumented) export const APICategory = "api"; @@ -122,7 +120,10 @@ export const enum ConflictHandlerType { } // @public (undocumented) -export function constructDataSourceMap(schema: string, datasourceType: DataSourceType): Map; +export const constructDataSourceStrategies: (schema: string, dataSourceStrategy: ModelDataSourceStrategy) => Record; + +// @public (undocumented) +export const constructSqlDirectiveDataSourceStrategies: (schema: string, dataSourceStrategy: ModelDataSourceStrategy, customSqlStatements?: Record) => SqlDirectiveDataSourceStrategy[]; // @public (undocumented) function createSyncLambdaIAMPolicy(context: TransformerContextProvider, scope: Construct, name: string, region?: string): iam.Policy; @@ -132,24 +133,15 @@ function createSyncLambdaIAMPolicy(context: TransformerContextProvider, scope: C // @public (undocumented) function createSyncTable(context: TransformerContext): void; -// @public (undocumented) -export const dataSourceStrategyToDataSourceType: (dataSourceStrategy: ModelDataSourceStrategy) => DataSourceType; - // @public (undocumented) export const DDB_AMPLIFY_MANAGED_DATASOURCE_STRATEGY: ModelDataSourceStrategy; -// @public (undocumented) -export const DDB_AMPLIFY_MANAGED_DATASOURCE_TYPE: DataSourceType; - // @public (undocumented) export const DDB_DB_TYPE: ModelDataSourceStrategyDbType; // @public (undocumented) export const DDB_DEFAULT_DATASOURCE_STRATEGY: ModelDataSourceStrategy; -// @public (undocumented) -export const DDB_DEFAULT_DATASOURCE_TYPE: DataSourceType; - // @public (undocumented) export class DirectiveWrapper { constructor(node: DirectiveNode); @@ -207,26 +199,23 @@ export type GetArgumentsOptions = { deepMergeArguments?: boolean; }; -// @public (undocumented) -export function getDatasourceProvisionStrategy(ctx: TransformerBeforeStepContextProvider, typeName?: string): DataSourceProvisionStrategy; - -// @public (undocumented) -export const getDataSourceType: (type: TypeNode, ctx: TransformerContextProvider) => ModelDataSourceStrategyDbType; - -// @public (undocumented) -export const getEngineFromDBType: (dbType: ModelDataSourceStrategyDbType) => ImportedRDSType; - // Warning: (ae-forgotten-export) The symbol "Operation" needs to be exported by the entry point index.d.ts // // @public (undocumented) export const getFieldNameFor: (op: Operation, typeName: string) => string; // @public (undocumented) -export const getImportedRDSType: (modelToDatasourceMap: Map) => ModelDataSourceStrategyDbType; +export const getImportedRDSTypeFromStrategyDbType: (dbType: ModelDataSourceStrategyDbType) => ImportedRDSType; // @public (undocumented) export const getKeySchema: (table: any, indexName?: string) => any; +// @public (undocumented) +export const getModelDataSourceStrategy: (ctx: DataSourceStrategiesProvider, typename: string) => ModelDataSourceStrategy; + +// @public (undocumented) +export const getModelTypeNames: (schema: string) => string[]; + // @public (undocumented) export const getParameterStoreSecretPath: (secret: string, secretsKey: string, apiName: string, environmentName: string, appId: string) => string; @@ -239,6 +228,9 @@ export const getResourceName: (scope: Construct) => string | undefined; // @public (undocumented) export const getSortKeyFieldNames: (type: ObjectTypeDefinitionNode) => string[]; +// @public (undocumented) +export const getStrategyDbTypeFromTypeNode: (type: TypeNode, ctx: TransformerContextProvider) => ModelDataSourceStrategyDbType; + // @public (undocumented) function getSyncConfig(ctx: TransformerTransformSchemaStepContextProvider, typeName: string): SyncConfig | undefined; @@ -260,7 +252,7 @@ export class GraphQLTransform { // Warning: (ae-forgotten-export) The symbol "TransformOption" needs to be exported by the entry point index.d.ts // // (undocumented) - transform({ scope, nestedStackProvider, parameterProvider, assetProvider, synthParameters, schema, datasourceConfig, }: TransformOption): void; + transform({ assetProvider, dataSourceStrategies, nestedStackProvider, parameterProvider, rdsLayerMapping, schema, scope, sqlDirectiveDataSourceStrategies, synthParameters, }: TransformOption): void; } // @public (undocumented) @@ -270,13 +262,7 @@ export interface GraphQLTransformOptions { // (undocumented) readonly host?: TransformHostProvider; // (undocumented) - readonly rdsLayerMapping?: RDSLayerMapping; - // (undocumented) readonly resolverConfig?: ResolverConfig; - // (undocumented) - readonly sqlLambdaProvisionedConcurrencyConfig?: ProvisionedConcurrencyConfig; - // (undocumented) - readonly sqlLambdaVpcConfig?: VpcConfig; // Warning: (ae-forgotten-export) The symbol "StackMapping" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -377,10 +363,19 @@ export class InvalidTransformerError extends Error { } // @public (undocumented) -export const isDynamoDbType: (dbType: ModelDataSourceStrategyDbType) => dbType is "DYNAMODB"; +export const isAmplifyDynamoDbModelDataSourceStrategy: (strategy: ModelDataSourceStrategy) => strategy is AmplifyDynamoDbModelDataSourceStrategy; // @public (undocumented) -export const isImportedRDSType: (dbInfo: DataSourceType) => boolean; +export const isDefaultDynamoDbModelDataSourceStrategy: (strategy: ModelDataSourceStrategy) => strategy is DefaultDynamoDbModelDataSourceStrategy; + +// @public (undocumented) +export const isDynamoDbModel: (ctx: DataSourceStrategiesProvider, typename: string) => boolean; + +// @public (undocumented) +export const isDynamoDbStrategy: (strategy: ModelDataSourceStrategy) => strategy is DefaultDynamoDbModelDataSourceStrategy | AmplifyDynamoDbModelDataSourceStrategy; + +// @public (undocumented) +export const isDynamoDbType: (dbType: ModelDataSourceStrategyDbType) => dbType is "DYNAMODB"; // @public (undocumented) function isLambdaSyncConfig(syncConfig: SyncConfig): syncConfig is SyncConfigLambda; diff --git a/packages/amplify-graphql-transformer-core/src/index.ts b/packages/amplify-graphql-transformer-core/src/index.ts index 058a3dd430..b7d11d939a 100644 --- a/packages/amplify-graphql-transformer-core/src/index.ts +++ b/packages/amplify-graphql-transformer-core/src/index.ts @@ -1,7 +1,14 @@ import { print } from 'graphql'; import { EXTRA_DIRECTIVES_DOCUMENT } from './transformation/validation'; -export { GraphQLTransform, GraphQLTransformOptions, SyncUtils, constructDataSourceMap } from './transformation'; +export { + constructDataSourceStrategies, + constructSqlDirectiveDataSourceStrategies, + getModelTypeNames, + GraphQLTransform, + GraphQLTransformOptions, + SyncUtils, +} from './transformation'; export { UserDefinedSlot, UserDefinedResolver } from './transformation/types'; export { validateModelSchema } from './transformation/validation'; export { @@ -18,23 +25,23 @@ export { APICategory, collectDirectives, collectDirectivesByTypeNames, - dataSourceStrategyToDataSourceType, DirectiveWrapper, fieldsWithSqlDirective, generateGetArgumentsInput, GetArgumentsOptions, - getDatasourceProvisionStrategy, - getDataSourceType, - getEngineFromDBType, - getImportedRDSType, + getImportedRDSTypeFromStrategyDbType, getKeySchema, + getModelDataSourceStrategy, getParameterStoreSecretPath, getPrimaryKeyFields, getResourceName, getSortKeyFieldNames, + getStrategyDbTypeFromTypeNode, getTable, + isAmplifyDynamoDbModelDataSourceStrategy, + isDefaultDynamoDbModelDataSourceStrategy, + isDynamoDbModel, isDynamoDbType, - isImportedRDSType, isMutationNode, isObjectTypeDefinitionNode, isQueryNode, @@ -42,6 +49,10 @@ export { isSqlModel, isSqlStrategy, setResourceName, + + // Exported but possibly unused + // TODO: Revisit these after the combine feature work. If they're not used, remove them + isDynamoDbStrategy, } from './utils'; export type { SetResourceNameProps } from './utils'; export * from './utils/operation-names'; @@ -55,10 +66,8 @@ export { export { TransformerResolver, StackManager } from './transformer-context'; export { DDB_AMPLIFY_MANAGED_DATASOURCE_STRATEGY, - DDB_AMPLIFY_MANAGED_DATASOURCE_TYPE, DDB_DB_TYPE, DDB_DEFAULT_DATASOURCE_STRATEGY, - DDB_DEFAULT_DATASOURCE_TYPE, ImportAppSyncAPIInputs, ImportedDataSourceConfig, ImportedDataSourceType, diff --git a/packages/amplify-graphql-transformer-core/src/transformation/index.ts b/packages/amplify-graphql-transformer-core/src/transformation/index.ts index 5903990261..ed01ecae53 100644 --- a/packages/amplify-graphql-transformer-core/src/transformation/index.ts +++ b/packages/amplify-graphql-transformer-core/src/transformation/index.ts @@ -2,4 +2,4 @@ import * as SyncUtils from './sync-utils'; export { GraphQLTransform, GraphQLTransformOptions } from './transform'; export { SyncUtils }; -export { constructDataSourceMap } from './utils'; +export { constructDataSourceStrategies, constructSqlDirectiveDataSourceStrategies, getModelTypeNames } from './utils'; diff --git a/packages/amplify-graphql-transformer-core/src/transformation/transform.ts b/packages/amplify-graphql-transformer-core/src/transformation/transform.ts index d658fc6a9b..f5d02fc863 100644 --- a/packages/amplify-graphql-transformer-core/src/transformation/transform.ts +++ b/packages/amplify-graphql-transformer-core/src/transformation/transform.ts @@ -5,17 +5,15 @@ import { TransformHostProvider, TransformerLog, NestedStackProvider, - RDSLayerMapping, SynthParameters, } from '@aws-amplify/graphql-transformer-interfaces'; import type { AssetProvider, - DataSourceType, StackManagerProvider, TransformParameterProvider, TransformParameters, - VpcConfig, - ProvisionedConcurrencyConfig, + DataSourceStrategiesProvider, + RDSLayerMappingProvider, } from '@aws-amplify/graphql-transformer-interfaces'; import { AuthorizationMode, AuthorizationType } from 'aws-cdk-lib/aws-appsync'; import { Aws, CfnOutput, Fn, Stack } from 'aws-cdk-lib'; @@ -47,7 +45,7 @@ import { MappingTemplate } from '../cdk-compat'; import { TransformerPreProcessContext } from '../transformer-context/pre-process-context'; import { defaultTransformParameters } from '../transformer-context/transform-parameters'; import * as SyncUtils from './sync-utils'; -import { UserDefinedSlot, DatasourceTransformationConfig } from './types'; +import { UserDefinedSlot } from './types'; import { makeSeenTransformationKey, matchArgumentDirective, @@ -87,20 +85,16 @@ export interface GraphQLTransformOptions { readonly host?: TransformHostProvider; readonly userDefinedSlots?: Record; readonly resolverConfig?: ResolverConfig; - readonly sqlLambdaVpcConfig?: VpcConfig; - readonly rdsLayerMapping?: RDSLayerMapping; - readonly sqlLambdaProvisionedConcurrencyConfig?: ProvisionedConcurrencyConfig; } -export type TransformOption = { +export interface TransformOption extends DataSourceStrategiesProvider, RDSLayerMappingProvider { scope: Construct; nestedStackProvider: NestedStackProvider; parameterProvider?: TransformParameterProvider; assetProvider: AssetProvider; synthParameters: SynthParameters; schema: string; - datasourceConfig?: DatasourceTransformationConfig; -}; +} export type StackMapping = { [resourceId: string]: string }; @@ -115,9 +109,7 @@ export class GraphQLTransform { private readonly userDefinedSlots: Record; - private readonly sqlLambdaVpcConfig?: VpcConfig; private readonly transformParameters: TransformParameters; - private readonly sqlLambdaProvisionedConcurrencyConfig?: ProvisionedConcurrencyConfig; // A map from `${directive}.${typename}.${fieldName?}`: true // that specifies we have run already run a directive at a given location. @@ -149,12 +141,10 @@ export class GraphQLTransform { this.stackMappingOverrides = options.stackMapping || {}; this.userDefinedSlots = options.userDefinedSlots || ({} as Record); this.resolverConfig = options.resolverConfig || {}; - this.sqlLambdaVpcConfig = options.sqlLambdaVpcConfig; this.transformParameters = { ...defaultTransformParameters, ...(options.transformParameters ?? {}), }; - this.sqlLambdaProvisionedConcurrencyConfig = options.sqlLambdaProvisionedConcurrencyConfig; this.logs = []; } @@ -194,31 +184,29 @@ export class GraphQLTransform { * cloudformation template is returned. */ public transform({ - scope, + assetProvider, + dataSourceStrategies, nestedStackProvider, parameterProvider, - assetProvider, - synthParameters, + rdsLayerMapping, schema, - datasourceConfig, + scope, + sqlDirectiveDataSourceStrategies, + synthParameters, }: TransformOption): void { this.seenTransformations = {}; const parsedDocument = parse(schema); const context = new TransformerContext({ assetProvider, authConfig: this.authConfig, - customQueries: datasourceConfig?.customQueries ?? new Map(), - customSqlDataSourceStrategies: datasourceConfig?.customSqlDataSourceStrategies ?? [], - datasourceSecretParameterLocations: datasourceConfig?.datasourceSecretParameterLocations, + dataSourceStrategies: dataSourceStrategies, inputDocument: parsedDocument, - modelToDatasourceMap: datasourceConfig?.modelToDatasourceMap ?? new Map(), nestedStackProvider, parameterProvider, - rdsLayerMapping: datasourceConfig?.rdsLayerMapping, + rdsLayerMapping, resolverConfig: this.resolverConfig, scope, - sqlLambdaProvisionedConcurrencyConfig: this.sqlLambdaProvisionedConcurrencyConfig, - sqlLambdaVpcConfig: this.sqlLambdaVpcConfig, + sqlDirectiveDataSourceStrategies: sqlDirectiveDataSourceStrategies ?? [], stackMapping: this.stackMappingOverrides, synthParameters, transformParameters: this.transformParameters, diff --git a/packages/amplify-graphql-transformer-core/src/transformation/types.ts b/packages/amplify-graphql-transformer-core/src/transformation/types.ts index 3238cefd82..5b6876abd1 100644 --- a/packages/amplify-graphql-transformer-core/src/transformation/types.ts +++ b/packages/amplify-graphql-transformer-core/src/transformation/types.ts @@ -1,6 +1,3 @@ -import { CustomSqlDataSourceStrategy, DataSourceType, RDSLayerMapping } from '@aws-amplify/graphql-transformer-interfaces'; -import { RDSConnectionSecrets } from '../types'; - export type UserDefinedSlot = { resolverTypeName: string; resolverFieldName: string; @@ -13,11 +10,3 @@ export type UserDefinedResolver = { fileName: string; template: string; }; - -export type DatasourceTransformationConfig = { - modelToDatasourceMap?: Map; - customSqlDataSourceStrategies?: CustomSqlDataSourceStrategy[]; - datasourceSecretParameterLocations?: Map; - rdsLayerMapping?: RDSLayerMapping; - customQueries?: Map; -}; diff --git a/packages/amplify-graphql-transformer-core/src/transformation/utils.ts b/packages/amplify-graphql-transformer-core/src/transformation/utils.ts index d6c2c7f18c..8e247de7c5 100644 --- a/packages/amplify-graphql-transformer-core/src/transformation/utils.ts +++ b/packages/amplify-graphql-transformer-core/src/transformation/utils.ts @@ -1,27 +1,35 @@ import { + DefinitionNode, + DirectiveDefinitionNode, DirectiveNode, - TypeDefinitionNode, + EnumValueDefinitionNode, FieldDefinitionNode, InputValueDefinitionNode, - EnumValueDefinitionNode, - DirectiveDefinitionNode, - TypeSystemDefinitionNode, Kind, + ObjectTypeDefinitionNode, parse, print, - DefinitionNode, - ObjectTypeDefinitionNode, + StringValueNode, + TypeDefinitionNode, + TypeSystemDefinitionNode, } from 'graphql'; -import { DataSourceType, TransformerPluginProvider, TransformerPluginType } from '@aws-amplify/graphql-transformer-interfaces'; +import { + ModelDataSourceStrategy, + SqlDirectiveDataSourceStrategy, + SQLLambdaModelDataSourceStrategy, + TransformerPluginProvider, + TransformerPluginType, +} from '@aws-amplify/graphql-transformer-interfaces'; import _ from 'lodash'; +import { fieldsWithSqlDirective, isMutationNode, isQueryNode, isSqlStrategy } from '../utils'; -export function makeSeenTransformationKey( +export const makeSeenTransformationKey = ( directive: DirectiveNode, type: TypeDefinitionNode, field?: FieldDefinitionNode | InputValueDefinitionNode | EnumValueDefinitionNode, arg?: InputValueDefinitionNode, index?: number, -): string { +): string => { let key = ''; if (directive && type && field && arg) { key = `${type.name.value}.${field.name.value}.${arg.name.value}@${directive.name.value}`; @@ -35,7 +43,7 @@ export function makeSeenTransformationKey( key += `[${index}]`; } return key; -} +}; /** * If this instance of the directive validates against its definition return true. @@ -43,7 +51,7 @@ export function makeSeenTransformationKey( * @param directive The directive definition to validate against. * @param nodeKind The kind of the current node where the directive was found. */ -export function matchDirective(definition: DirectiveDefinitionNode, directive: DirectiveNode, node: TypeSystemDefinitionNode) { +export const matchDirective = (definition: DirectiveDefinitionNode, directive: DirectiveNode, node: TypeSystemDefinitionNode): boolean => { if (!directive) { return false; } @@ -55,37 +63,37 @@ export function matchDirective(definition: DirectiveDefinitionNode, directive: D for (const location of definition.locations) { // tslint:disable-next-line: switch-default switch (location.value) { - case `SCHEMA`: + case 'SCHEMA': isValidLocation = node.kind === Kind.SCHEMA_DEFINITION || isValidLocation; break; - case `SCALAR`: + case 'SCALAR': isValidLocation = node.kind === Kind.SCALAR_TYPE_DEFINITION || isValidLocation; break; - case `OBJECT`: + case 'OBJECT': isValidLocation = node.kind === Kind.OBJECT_TYPE_DEFINITION || isValidLocation; break; - case `FIELD_DEFINITION`: + case 'FIELD_DEFINITION': isValidLocation = (node.kind as string) === Kind.FIELD_DEFINITION || isValidLocation; break; - case `ARGUMENT_DEFINITION`: + case 'ARGUMENT_DEFINITION': isValidLocation = (node.kind as string) === Kind.INPUT_VALUE_DEFINITION || isValidLocation; break; - case `INTERFACE`: + case 'INTERFACE': isValidLocation = node.kind === Kind.INTERFACE_TYPE_DEFINITION || isValidLocation; break; - case `UNION`: + case 'UNION': isValidLocation = node.kind === Kind.UNION_TYPE_DEFINITION || isValidLocation; break; - case `ENUM`: + case 'ENUM': isValidLocation = node.kind === Kind.ENUM_TYPE_DEFINITION || isValidLocation; break; - case `ENUM_VALUE`: + case 'ENUM_VALUE': isValidLocation = (node.kind as string) === Kind.ENUM_VALUE_DEFINITION || isValidLocation; break; - case `INPUT_OBJECT`: + case 'INPUT_OBJECT': isValidLocation = node.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION || isValidLocation; break; - case `INPUT_FIELD_DEFINITION`: + case 'INPUT_FIELD_DEFINITION': isValidLocation = (node.kind as string) === Kind.INPUT_VALUE_DEFINITION || isValidLocation; break; default: @@ -93,9 +101,9 @@ export function matchDirective(definition: DirectiveDefinitionNode, directive: D } } return isValidLocation; -} +}; -export function matchFieldDirective(definition: DirectiveDefinitionNode, directive: DirectiveNode, node: FieldDefinitionNode) { +export const matchFieldDirective = (definition: DirectiveDefinitionNode, directive: DirectiveNode, node: FieldDefinitionNode): boolean => { if (definition.name.value !== directive.name.value) { // The definition is for the wrong directive. Do not match. return false; @@ -103,7 +111,7 @@ export function matchFieldDirective(definition: DirectiveDefinitionNode, directi let isValidLocation = false; for (const location of definition.locations) { switch (location.value) { - case `FIELD_DEFINITION`: + case 'FIELD_DEFINITION': isValidLocation = node.kind === Kind.FIELD_DEFINITION || isValidLocation; break; default: @@ -111,9 +119,13 @@ export function matchFieldDirective(definition: DirectiveDefinitionNode, directi } } return isValidLocation; -} +}; -export function matchInputFieldDirective(definition: DirectiveDefinitionNode, directive: DirectiveNode, node: InputValueDefinitionNode) { +export const matchInputFieldDirective = ( + definition: DirectiveDefinitionNode, + directive: DirectiveNode, + node: InputValueDefinitionNode, +): boolean => { if (definition.name.value !== directive.name.value) { // The definition is for the wrong directive. Do not match. return false; @@ -121,7 +133,7 @@ export function matchInputFieldDirective(definition: DirectiveDefinitionNode, di let isValidLocation = false; for (const location of definition.locations) { switch (location.value) { - case `INPUT_FIELD_DEFINITION`: + case 'INPUT_FIELD_DEFINITION': isValidLocation = node.kind === Kind.INPUT_VALUE_DEFINITION || isValidLocation; break; default: @@ -129,9 +141,13 @@ export function matchInputFieldDirective(definition: DirectiveDefinitionNode, di } } return isValidLocation; -} +}; -export function matchArgumentDirective(definition: DirectiveDefinitionNode, directive: DirectiveNode, node: InputValueDefinitionNode) { +export const matchArgumentDirective = ( + definition: DirectiveDefinitionNode, + directive: DirectiveNode, + node: InputValueDefinitionNode, +): boolean => { if (definition.name.value !== directive.name.value) { // The definition is for the wrong directive. Do not match. return false; @@ -139,7 +155,7 @@ export function matchArgumentDirective(definition: DirectiveDefinitionNode, dire let isValidLocation = false; for (const location of definition.locations) { switch (location.value) { - case `ARGUMENT_DEFINITION`: + case 'ARGUMENT_DEFINITION': isValidLocation = node.kind === Kind.INPUT_VALUE_DEFINITION || isValidLocation; break; default: @@ -147,9 +163,13 @@ export function matchArgumentDirective(definition: DirectiveDefinitionNode, dire } } return isValidLocation; -} +}; -export function matchEnumValueDirective(definition: DirectiveDefinitionNode, directive: DirectiveNode, node: EnumValueDefinitionNode) { +export const matchEnumValueDirective = ( + definition: DirectiveDefinitionNode, + directive: DirectiveNode, + node: EnumValueDefinitionNode, +): boolean => { if (definition.name.value !== directive.name.value) { // The definition is for the wrong directive. Do not match. return false; @@ -157,7 +177,7 @@ export function matchEnumValueDirective(definition: DirectiveDefinitionNode, dir let isValidLocation = false; for (const location of definition.locations) { switch (location.value) { - case `ENUM_VALUE`: + case 'ENUM_VALUE': isValidLocation = node.kind === Kind.ENUM_VALUE_DEFINITION || isValidLocation; break; default: @@ -165,13 +185,13 @@ export function matchEnumValueDirective(definition: DirectiveDefinitionNode, dir } } return isValidLocation; -} +}; /** * Sort the plugin such that the DataSourceProviders are executed before dataSourceEnhancement plugins are executed * @param plugins plugin instances passed to the transformer */ -export function sortTransformerPlugins(plugins: TransformerPluginProvider[]): TransformerPluginProvider[] { +export const sortTransformerPlugins = (plugins: TransformerPluginProvider[]): TransformerPluginProvider[] => { const SORT_ORDER: TransformerPluginType[] = [ TransformerPluginType.DATA_SOURCE_PROVIDER, TransformerPluginType.DATA_SOURCE_ENHANCER, @@ -183,7 +203,7 @@ export function sortTransformerPlugins(plugins: TransformerPluginProvider[]): Tr const bIdx = SORT_ORDER.indexOf(b.pluginType); return aIdx - bIdx; }); -} +}; /** * Return the input schema with the `Amplify` input node stripped. @@ -206,19 +226,101 @@ export const removeAmplifyInputDefinition = (schema: string): string => { }); }; +const MODEL_DIRECTIVE_NAME = 'model'; +const MANY_TO_MANY_DIRECTIVE_NAME = 'manyToMany'; + /** - * Return the datasource map for the input schema with the provided datasource type - * @param schema input schema - * @param datasourceType input datasoure type - * @returns a map of datasource type per model + * Get the type names with model directives in the GraphQL schema in SDL + * @param schema graphql schema in SDL + * @returns type names which model diretives are attached */ -export function constructDataSourceMap(schema: string, datasourceType: DataSourceType): Map { +export const getModelTypeNames = (schema: string): string[] => { const parsedSchema = parse(schema); - const result = new Map(); - parsedSchema.definitions - .filter((obj) => obj.kind === Kind.OBJECT_TYPE_DEFINITION && obj.directives?.some((dir) => dir.name.value === 'model')) - .forEach((type) => { - result.set((type as ObjectTypeDefinitionNode).name.value, datasourceType); + const nodesWithModelDirective = parsedSchema.definitions.filter( + (obj) => obj.kind === Kind.OBJECT_TYPE_DEFINITION && obj.directives?.some((dir) => dir.name.value === MODEL_DIRECTIVE_NAME), + ); + const modelKeys = nodesWithModelDirective.map((type) => (type as ObjectTypeDefinitionNode).name.value); + nodesWithModelDirective.forEach((obj) => { + const { fields } = obj as ObjectTypeDefinitionNode; + fields?.forEach((field) => { + field.directives?.forEach((dir) => { + if (dir.name.value === MANY_TO_MANY_DIRECTIVE_NAME) { + const relationArg = dir.arguments?.find((arg) => arg.name.value === 'relationName'); + if (relationArg) { + modelKeys.push((relationArg.value as StringValueNode).value); + } + } + }); }); - return result; -} + }); + return modelKeys.filter((key, idx) => modelKeys.indexOf(key) === idx); +}; + +/** + * Return a Record associating the ModelDataSourceStrategy with each `@model`-annotated type in the schema. + * @param schema the schema to parse + * @param dataSourceStrategy the strategy to associate with each `@model` in the schema + */ +export const constructDataSourceStrategies = ( + schema: string, + dataSourceStrategy: ModelDataSourceStrategy, +): Record => { + const modelKeys = getModelTypeNames(schema); + return modelKeys.reduce((acc, cur) => ({ ...acc, [cur]: dataSourceStrategy }), {}); +}; + +/** + * Creates a customSqlDataSourceStrategies array from a schema and strategy. Internally, this function scans the fields of `Query` and + * `Mutation` looking for fields annotated with the `@sql` directive and designates the specified dataSourceStrategy to fulfill those custom + * queries. + * + * Note that we do not scan for `Subscription` fields: `@sql` directives are not allowed on those, and it wouldn't make sense to do so + * anyway, since subscriptions are processed from an incoming Mutation, not as the result of a direct datasource access. + * + * This method is largely the same as the utility in amplify-graphql-api-construct, but targeted toward the transformer type. + */ +export const constructSqlDirectiveDataSourceStrategies = ( + schema: string, + dataSourceStrategy: ModelDataSourceStrategy, + customSqlStatements?: Record, +): SqlDirectiveDataSourceStrategy[] => { + if (!isSqlStrategy(dataSourceStrategy)) { + return []; + } + + const parsedSchema = parse(schema); + + const queryNode = parsedSchema.definitions.find(isQueryNode); + const mutationNode = parsedSchema.definitions.find(isMutationNode); + if (!queryNode && !mutationNode) { + return []; + } + + const sqlDirectiveDataSourceStrategies: SqlDirectiveDataSourceStrategy[] = []; + + if (queryNode) { + const fields = fieldsWithSqlDirective(queryNode); + for (const field of fields) { + sqlDirectiveDataSourceStrategies.push({ + typeName: 'Query', + fieldName: field.name.value, + strategy: dataSourceStrategy, + customSqlStatements, + }); + } + } + + if (mutationNode) { + const fields = fieldsWithSqlDirective(mutationNode); + for (const field of fields) { + sqlDirectiveDataSourceStrategies.push({ + typeName: 'Mutation', + fieldName: field.name.value, + strategy: dataSourceStrategy, + customSqlStatements, + }); + } + } + + return sqlDirectiveDataSourceStrategies; +}; diff --git a/packages/amplify-graphql-transformer-core/src/transformer-context/index.ts b/packages/amplify-graphql-transformer-core/src/transformer-context/index.ts index 42a24d8c89..9b79617d55 100644 --- a/packages/amplify-graphql-transformer-core/src/transformer-context/index.ts +++ b/packages/amplify-graphql-transformer-core/src/transformer-context/index.ts @@ -2,12 +2,13 @@ import { AppSyncAuthConfiguration, AssetProvider, - CustomSqlDataSourceStrategy, - DataSourceType, + SqlDirectiveDataSourceStrategy, + DataSourceStrategiesProvider, GraphQLAPIProvider, + ModelDataSourceStrategy, NestedStackProvider, - ProvisionedConcurrencyConfig, RDSLayerMapping, + RDSLayerMappingProvider, StackManagerProvider, SynthParameters, TransformerContextMetadataProvider, @@ -16,12 +17,10 @@ import { TransformerDataSourceManagerProvider, TransformParameterProvider, TransformParameters, - VpcConfig, } from '@aws-amplify/graphql-transformer-interfaces'; import { DocumentNode } from 'graphql'; import { Construct } from 'constructs'; import { ResolverConfig } from '../config/transformer-config'; -import { RDSConnectionSecrets } from '../types'; import { TransformerDataSourceManager } from './datasource'; import { TransformerOutput } from './output'; import { TransformerContextProviderRegistry } from './provider-registry'; @@ -51,24 +50,17 @@ export class TransformerContextMetadata implements TransformerContextMetadataPro } } -export interface TransformerContextConstructorOptions { - scope: Construct; - nestedStackProvider: NestedStackProvider; - parameterProvider: TransformParameterProvider | undefined; +export interface TransformerContextConstructorOptions extends DataSourceStrategiesProvider, RDSLayerMappingProvider { assetProvider: AssetProvider; - synthParameters: SynthParameters; + authConfig: AppSyncAuthConfiguration; inputDocument: DocumentNode; - modelToDatasourceMap: Map; - customSqlDataSourceStrategies: CustomSqlDataSourceStrategy[]; - customQueries: Map; + nestedStackProvider: NestedStackProvider; + parameterProvider: TransformParameterProvider | undefined; + resolverConfig?: ResolverConfig; + scope: Construct; stackMapping: Record; - authConfig: AppSyncAuthConfiguration; + synthParameters: SynthParameters; transformParameters: TransformParameters; - resolverConfig?: ResolverConfig; - datasourceSecretParameterLocations?: Map; - sqlLambdaVpcConfig?: VpcConfig; - rdsLayerMapping?: RDSLayerMapping; - sqlLambdaProvisionedConcurrencyConfig?: ProvisionedConcurrencyConfig; } export class TransformerContext implements TransformerContextProvider { @@ -92,20 +84,12 @@ export class TransformerContext implements TransformerContextProvider { private resolverConfig: ResolverConfig | undefined; - public readonly modelToDatasourceMap: Map; + public readonly dataSourceStrategies: Record; - public readonly customSqlDataSourceStrategies: CustomSqlDataSourceStrategy[]; - - public readonly datasourceSecretParameterLocations: Map; - - public readonly sqlLambdaVpcConfig?: VpcConfig; + public readonly sqlDirectiveDataSourceStrategies: SqlDirectiveDataSourceStrategy[]; public readonly rdsLayerMapping?: RDSLayerMapping; - public readonly sqlLambdaProvisionedConcurrencyConfig?: ProvisionedConcurrencyConfig; - - public readonly customQueries: Map; - public metadata: TransformerContextMetadata; public readonly synthParameters: SynthParameters; @@ -116,39 +100,31 @@ export class TransformerContext implements TransformerContextProvider { const { assetProvider, authConfig, - customQueries, - customSqlDataSourceStrategies, - datasourceSecretParameterLocations, + sqlDirectiveDataSourceStrategies, + dataSourceStrategies, inputDocument, - modelToDatasourceMap, nestedStackProvider, parameterProvider, rdsLayerMapping, resolverConfig, scope, - sqlLambdaProvisionedConcurrencyConfig, - sqlLambdaVpcConfig, stackMapping, synthParameters, transformParameters, } = options; assetManager.setAssetProvider(assetProvider); this.authConfig = authConfig; - this.customQueries = customQueries; - this.customSqlDataSourceStrategies = customSqlDataSourceStrategies; + this.sqlDirectiveDataSourceStrategies = sqlDirectiveDataSourceStrategies ?? []; this.dataSources = new TransformerDataSourceManager(); - this.datasourceSecretParameterLocations = datasourceSecretParameterLocations ?? new Map(); + this.dataSourceStrategies = dataSourceStrategies; this.inputDocument = inputDocument; this.metadata = new TransformerContextMetadata(); - this.modelToDatasourceMap = modelToDatasourceMap; this.output = new TransformerOutput(inputDocument); this.providerRegistry = new TransformerContextProviderRegistry(); this.rdsLayerMapping = rdsLayerMapping; this.resolverConfig = resolverConfig; this.resolvers = new ResolverManager(); this.resourceHelper = new TransformerResourceHelper(synthParameters); - this.sqlLambdaProvisionedConcurrencyConfig = sqlLambdaProvisionedConcurrencyConfig; - this.sqlLambdaVpcConfig = sqlLambdaVpcConfig; this.stackManager = new StackManager(scope, nestedStackProvider, parameterProvider, stackMapping); this.synthParameters = synthParameters; this.transformParameters = transformParameters; diff --git a/packages/amplify-graphql-transformer-core/src/types/model-datasource-strategies.ts b/packages/amplify-graphql-transformer-core/src/types/model-datasource-strategies.ts index 37f0353183..31d9861201 100644 --- a/packages/amplify-graphql-transformer-core/src/types/model-datasource-strategies.ts +++ b/packages/amplify-graphql-transformer-core/src/types/model-datasource-strategies.ts @@ -1,6 +1,4 @@ import { - DataSourceType, - DynamoDBProvisionStrategy, ModelDataSourceStrategy, ModelDataSourceStrategyDbType, ModelDataSourceStrategySqlDbType, @@ -12,20 +10,6 @@ export const MYSQL_DB_TYPE: ModelDataSourceStrategySqlDbType = 'MYSQL'; export const POSTGRES_DB_TYPE: ModelDataSourceStrategySqlDbType = 'POSTGRES'; -// TODO: Remove this when we normalize internal data source handling to use ModelDataSourceStrategy -export const DDB_DEFAULT_DATASOURCE_TYPE: DataSourceType = { - dbType: DDB_DB_TYPE, - provisionDB: true, - provisionStrategy: DynamoDBProvisionStrategy.DEFAULT, -}; - -// TODO: Remove this when we normalize internal data source handling to use ModelDataSourceStrategy -export const DDB_AMPLIFY_MANAGED_DATASOURCE_TYPE: DataSourceType = { - dbType: DDB_DB_TYPE, - provisionDB: true, - provisionStrategy: DynamoDBProvisionStrategy.AMPLIFY_TABLE, -}; - export const DDB_DEFAULT_DATASOURCE_STRATEGY: ModelDataSourceStrategy = { dbType: DDB_DB_TYPE, provisionStrategy: 'DEFAULT', diff --git a/packages/amplify-graphql-transformer-core/src/utils/index.ts b/packages/amplify-graphql-transformer-core/src/utils/index.ts index c8c39c37b8..b0361b6e60 100644 --- a/packages/amplify-graphql-transformer-core/src/utils/index.ts +++ b/packages/amplify-graphql-transformer-core/src/utils/index.ts @@ -2,21 +2,11 @@ export { getPrimaryKeyFields } from './model-util'; export { DirectiveWrapper, GetArgumentsOptions, generateGetArgumentsInput } from './directive-wrapper'; export { collectDirectives, collectDirectivesByTypeNames } from './type-map-utils'; export { stripDirectives } from './strip-directives'; -export { getTable, getKeySchema, getSortKeyFieldNames, getDataSourceType } from './schema-utils'; +export { getTable, getKeySchema, getSortKeyFieldNames, getStrategyDbTypeFromTypeNode } from './schema-utils'; export { DEFAULT_SCHEMA_DEFINITION } from './defaultSchema'; -export { - constructDataSourceMap, - getEngineFromDBType, - getImportedRDSType, - getModelDataSourceType, - getParameterStoreSecretPath, - isDynamoDBModel, - isImportedRDSType, - isSqlModel, -} from './rds-util'; +export { getParameterStoreSecretPath } from './rds-util'; export const APICategory = 'api'; export { setResourceName, getResourceName } from './resource-name'; export type { SetResourceNameProps } from './resource-name'; -export { getDatasourceProvisionStrategy } from './provision-strategy-utils'; export * from './model-datasource-strategy-utils'; export * from './graphql-utils'; diff --git a/packages/amplify-graphql-transformer-core/src/utils/model-datasource-strategy-utils.ts b/packages/amplify-graphql-transformer-core/src/utils/model-datasource-strategy-utils.ts index 650599ddce..ad63e7572f 100644 --- a/packages/amplify-graphql-transformer-core/src/utils/model-datasource-strategy-utils.ts +++ b/packages/amplify-graphql-transformer-core/src/utils/model-datasource-strategy-utils.ts @@ -1,40 +1,17 @@ import { AmplifyDynamoDbModelDataSourceStrategy, - DataSourceType, + DataSourceStrategiesProvider, DefaultDynamoDbModelDataSourceStrategy, ModelDataSourceStrategy, ModelDataSourceStrategyDbType, ModelDataSourceStrategySqlDbType, SQLLambdaModelDataSourceStrategy, - SQLLambdaModelProvisionStrategy, } from '@aws-amplify/graphql-transformer-interfaces'; -import { - DDB_AMPLIFY_MANAGED_DATASOURCE_TYPE, - DDB_DB_TYPE, - DDB_DEFAULT_DATASOURCE_TYPE, - MYSQL_DB_TYPE, - POSTGRES_DB_TYPE, -} from '../types/model-datasource-strategies'; +import { DDB_DB_TYPE, ImportedRDSType, MYSQL_DB_TYPE, POSTGRES_DB_TYPE } from '../types'; +import { isBuiltInGraphqlType } from './graphql-utils'; -export const dataSourceStrategyToDataSourceType = (dataSourceStrategy: ModelDataSourceStrategy): DataSourceType => { - if (dataSourceStrategy.dbType === 'DYNAMODB') { - switch (dataSourceStrategy.provisionStrategy) { - case 'DEFAULT': - return DDB_DEFAULT_DATASOURCE_TYPE; - case 'AMPLIFY_TABLE': - return DDB_AMPLIFY_MANAGED_DATASOURCE_TYPE; - default: - throw new Error(`Encountered unexpected provision strategy: ${(dataSourceStrategy as any).provisionStrategy}`); - } - } else if (dataSourceStrategy.dbType === 'MYSQL' || dataSourceStrategy.dbType === 'POSTGRES') { - return { - dbType: dataSourceStrategy.dbType, - provisionDB: false, - provisionStrategy: SQLLambdaModelProvisionStrategy.DEFAULT, - }; - } - throw new Error(`Encountered unexpected database type ${dataSourceStrategy.dbType}`); -}; +// Exported but possibly unused +// TODO: Revisit these after the combine feature work. If they're not used, remove them /** * Type predicate that returns true if `obj` is one of the known DynamoDB-based strategies @@ -45,32 +22,77 @@ export const isDynamoDbStrategy = ( return isDefaultDynamoDbModelDataSourceStrategy(strategy) || isAmplifyDynamoDbModelDataSourceStrategy(strategy); }; +// Exported utils + /** - * Type predicate that returns true if `obj` is a DefaultDynamoDbModelDataSourceStrategy + * Map the database type that is set in the dataSourceStrategies to the engine represented by ImportedRDSType. This is used to generate + * parameters for invoking the SQL Lambda. */ -export const isDefaultDynamoDbModelDataSourceStrategy = ( +export const getImportedRDSTypeFromStrategyDbType = (dbType: ModelDataSourceStrategyDbType): ImportedRDSType => { + switch (dbType) { + case MYSQL_DB_TYPE: + return ImportedRDSType.MYSQL; + case POSTGRES_DB_TYPE: + return ImportedRDSType.POSTGRESQL; + default: + throw new Error(`Unsupported RDS datasource type: ${dbType}`); + } +}; + +/** + * Get the datasource database type of the model. + * @param ctx Transformer Context + * @param typename Model name + * @returns datasource type + */ +export const getModelDataSourceStrategy = (ctx: DataSourceStrategiesProvider, typename: string): ModelDataSourceStrategy => { + const strategy = ctx.dataSourceStrategies[typename]; + if (!strategy) { + throw new Error(`Cannot find datasource type for model ${typename}`); + } + return strategy; +}; + +/** + * Type predicate that returns true if `obj` is a AmplifyDynamoDbModelDataSourceStrategy + */ +export const isAmplifyDynamoDbModelDataSourceStrategy = ( strategy: ModelDataSourceStrategy, -): strategy is DefaultDynamoDbModelDataSourceStrategy => { +): strategy is AmplifyDynamoDbModelDataSourceStrategy => { return ( isDynamoDbType(strategy.dbType) && typeof (strategy as any)['provisionStrategy'] === 'string' && - (strategy as any)['provisionStrategy'] === 'DEFAULT' + (strategy as any)['provisionStrategy'] === 'AMPLIFY_TABLE' ); }; /** - * Type predicate that returns true if `obj` is a AmplifyDynamoDbModelDataSourceStrategy + * Type predicate that returns true if `obj` is a DefaultDynamoDbModelDataSourceStrategy */ -export const isAmplifyDynamoDbModelDataSourceStrategy = ( +export const isDefaultDynamoDbModelDataSourceStrategy = ( strategy: ModelDataSourceStrategy, -): strategy is AmplifyDynamoDbModelDataSourceStrategy => { +): strategy is DefaultDynamoDbModelDataSourceStrategy => { return ( isDynamoDbType(strategy.dbType) && typeof (strategy as any)['provisionStrategy'] === 'string' && - (strategy as any)['provisionStrategy'] === 'AMPLIFY_TABLE' + (strategy as any)['provisionStrategy'] === 'DEFAULT' ); }; +/** + * Checks if the given model is a DynamoDB model + * @param ctx Transformer Context + * @param typename Model name + * @returns boolean + */ +export const isDynamoDbModel = (ctx: DataSourceStrategiesProvider, typename: string): boolean => { + if (isBuiltInGraphqlType(typename)) { + return false; + } + const modelDataSourceType = getModelDataSourceStrategy(ctx, typename); + return isDynamoDbType(modelDataSourceType.dbType); +}; + /** * Type predicate that returns true if `dbType` is the DynamoDB database type */ @@ -78,6 +100,27 @@ export const isDynamoDbType = (dbType: ModelDataSourceStrategyDbType): dbType is return dbType === 'DYNAMODB'; }; +/** + * Type predicate that returns true if `dbType` is a supported SQL database type + */ +export const isSqlDbType = (dbType: ModelDataSourceStrategyDbType): dbType is ModelDataSourceStrategySqlDbType => { + return ([MYSQL_DB_TYPE, POSTGRES_DB_TYPE] as string[]).includes(dbType as string); +}; + +/** + * Checks if the given model is a SQL model + * @param ctx Transformer Context + * @param typename Model name + * @returns boolean + */ +export const isSqlModel = (ctx: DataSourceStrategiesProvider, typename: string): boolean => { + if (isBuiltInGraphqlType(typename)) { + return false; + } + const modelDataSourceType = getModelDataSourceStrategy(ctx, typename); + return isSqlDbType(modelDataSourceType.dbType); +}; + /** * Type predicate that returns true if `obj` is a SQLLambdaModelDataSourceStrategy */ @@ -87,13 +130,6 @@ export const isSqlStrategy = (strategy: ModelDataSourceStrategy): strategy is SQ ); }; -/** - * Type predicate that returns true if `dbType` is a supported SQL database type - */ -export const isSqlDbType = (dbType: ModelDataSourceStrategyDbType): dbType is ModelDataSourceStrategySqlDbType => { - return ([MYSQL_DB_TYPE, POSTGRES_DB_TYPE] as string[]).includes(dbType as string); -}; - /** * Normalize known variants of a database type to its canonical representation. E.g.: * diff --git a/packages/amplify-graphql-transformer-core/src/utils/provision-strategy-utils.ts b/packages/amplify-graphql-transformer-core/src/utils/provision-strategy-utils.ts deleted file mode 100644 index aa17790af3..0000000000 --- a/packages/amplify-graphql-transformer-core/src/utils/provision-strategy-utils.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { - DataSourceProvisionStrategy, - DynamoDBProvisionStrategy, - TransformerBeforeStepContextProvider, -} from '@aws-amplify/graphql-transformer-interfaces'; - -/** - * Get the project-level datasource provision strategy. When typeName is provided, the model level strategy will be fetched - * @param ctx transfomer before step context - * @param typeName model name defined in GraphQL schema defintion - * @returns Datasource provision strategy for the provided model. - */ -export function getDatasourceProvisionStrategy(ctx: TransformerBeforeStepContextProvider, typeName?: string): DataSourceProvisionStrategy { - let config: DataSourceProvisionStrategy = DynamoDBProvisionStrategy.DEFAULT; - - if (typeName) { - const typeConfig = ctx.modelToDatasourceMap.get(typeName); - if (typeConfig && typeConfig.dbType && typeConfig.provisionStrategy) { - config = typeConfig.provisionStrategy; - } - } - return config; -} diff --git a/packages/amplify-graphql-transformer-core/src/utils/rds-util.ts b/packages/amplify-graphql-transformer-core/src/utils/rds-util.ts index e5caa2cd38..507ffb9558 100644 --- a/packages/amplify-graphql-transformer-core/src/utils/rds-util.ts +++ b/packages/amplify-graphql-transformer-core/src/utils/rds-util.ts @@ -1,12 +1,6 @@ import path from 'path'; import _ from 'lodash'; -import { parse, Kind, ObjectTypeDefinitionNode } from 'graphql'; -import { DataSourceStrategiesProvider, DataSourceType, ModelDataSourceStrategyDbType } from '@aws-amplify/graphql-transformer-interfaces'; -import { MYSQL_DB_TYPE, POSTGRES_DB_TYPE } from '../types'; -import { ImportedRDSType } from '../types'; import { APICategory } from './api-category'; -import { isDynamoDbType, isSqlDbType } from './model-datasource-strategy-utils'; -import { isBuiltInGraphqlType } from './graphql-utils'; const getParameterNameForDBSecret = (secret: string, secretsKey: string): string => { return `${secretsKey}_${secret}`; @@ -34,103 +28,3 @@ export const getParameterStoreSecretPath = ( } return path.posix.join('/amplify', appId, environmentName, `AMPLIFY_${categoryName}${apiName}${paramName}`); }; - -/** - * Get the datasource database type of the model. - * @param ctx Transformer Context - * @param typename Model name - * @returns datasource type - */ -export const getModelDataSourceType = (ctx: DataSourceStrategiesProvider, typename: string): ModelDataSourceStrategyDbType => { - const config = ctx.modelToDatasourceMap.get(typename); - if (!config) { - throw new Error(`Cannot find datasource type for model ${typename}`); - } - return config.dbType; -}; - -/** - * Checks if the given model is a DynamoDB model - * @param ctx Transformer Context - * @param typename Model name - * @returns boolean - */ -export const isDynamoDBModel = (ctx: DataSourceStrategiesProvider, typename: string): boolean => { - if (isBuiltInGraphqlType(typename)) { - return false; - } - const modelDataSourceType = getModelDataSourceType(ctx, typename); - return isDynamoDbType(modelDataSourceType); -}; - -/** - * Checks if the given model is a RDS model - * @param ctx Transformer Context - * @param typename Model name - * @returns boolean - */ -export const isSqlModel = (ctx: DataSourceStrategiesProvider, typename: string): boolean => { - if (isBuiltInGraphqlType(typename)) { - return false; - } - const modelDataSourceType = getModelDataSourceType(ctx, typename); - return isSqlDbType(modelDataSourceType); -}; - -/** - * Checks if the given Datasource is imported RDS datasource - * @param dbInfo Datasource information - * @returns boolean - */ -export const isImportedRDSType = (dbInfo: DataSourceType): boolean => { - return isSqlDbType(dbInfo?.dbType) && !dbInfo?.provisionDB; -}; - -/** - * Constructs a map of model names to datasource types for the specified schema. Used by the transformer to auto-generate a model mapping if - * the customer has not provided an explicit one. - * @param schema the annotated GraphQL schema - * @param datasourceType the datasource type for each model to be associated with - * @returns a map of model names to datasource types - */ -export const constructDataSourceMap = (schema: string, datasourceType: DataSourceType): Map => { - const parsedSchema = parse(schema); - const result = new Map(); - parsedSchema.definitions - .filter((obj) => obj.kind === Kind.OBJECT_TYPE_DEFINITION && obj.directives?.some((dir) => dir.name.value === 'model')) - .forEach((type) => { - result.set((type as ObjectTypeDefinitionNode).name.value, datasourceType); - }); - return result; -}; - -/** - * Map the database type that is set in the modelToDatasourceMap to the engine represented by ImportedRDSType - */ -export const getEngineFromDBType = (dbType: ModelDataSourceStrategyDbType): ImportedRDSType => { - switch (dbType) { - case MYSQL_DB_TYPE: - return ImportedRDSType.MYSQL; - case POSTGRES_DB_TYPE: - return ImportedRDSType.POSTGRESQL; - default: - throw new Error(`Unsupported RDS datasource type: ${dbType}`); - } -}; - -/** - * Returns the datasource type of the imported RDS models. - * Throws an error if more than one datasource type is detected. - * - * TODO: Remove this during `combine` feature work; we will explicitly support more than one SQL data source - * @param modelToDatasourceMap Array of datasource types - * @returns datasource type - */ -export const getImportedRDSType = (modelToDatasourceMap: Map): ModelDataSourceStrategyDbType => { - const datasourceMapValues = Array.from(modelToDatasourceMap?.values()); - const dbTypes = new Set(datasourceMapValues?.filter((value) => isImportedRDSType(value))?.map((value) => value?.dbType)); - if (dbTypes.size > 1) { - throw new Error(`Multiple imported SQL datasource types ${Array.from(dbTypes)} are detected. Only one type is supported.`); - } - return dbTypes.values().next().value; -}; diff --git a/packages/amplify-graphql-transformer-core/src/utils/schema-utils.ts b/packages/amplify-graphql-transformer-core/src/utils/schema-utils.ts index dcb20db795..03cf935679 100644 --- a/packages/amplify-graphql-transformer-core/src/utils/schema-utils.ts +++ b/packages/amplify-graphql-transformer-core/src/utils/schema-utils.ts @@ -4,6 +4,7 @@ import { DynamoDbDataSource } from 'aws-cdk-lib/aws-appsync'; import { Table } from 'aws-cdk-lib/aws-dynamodb'; import { ListValueNode, ObjectTypeDefinitionNode, StringValueNode, TypeNode } from 'graphql'; import { ModelResourceIDs, getBaseType } from 'graphql-transformer-common'; +import { getModelDataSourceStrategy } from './model-datasource-strategy-utils'; /** * getKeySchema @@ -49,13 +50,10 @@ export const getSortKeyFieldNames = (type: ObjectTypeDefinitionNode): string[] = }; /** - * Get DB type from the transformer context + * Get ModelDataSourceStrategyDbType from the transformer context */ -export const getDataSourceType = (type: TypeNode, ctx: TransformerContextProvider): ModelDataSourceStrategyDbType => { +export const getStrategyDbTypeFromTypeNode = (type: TypeNode, ctx: TransformerContextProvider): ModelDataSourceStrategyDbType => { const baseTypeName = getBaseType(type); - const dataSourceType = ctx.modelToDatasourceMap.get(baseTypeName); - if (!dataSourceType) { - throw new Error(`No datasource found for type ${baseTypeName}`); - } - return dataSourceType.dbType; + const strategy = getModelDataSourceStrategy(ctx, baseTypeName); + return strategy.dbType; }; diff --git a/packages/amplify-graphql-transformer-interfaces/API.md b/packages/amplify-graphql-transformer-interfaces/API.md index 0d3481684b..256f17132a 100644 --- a/packages/amplify-graphql-transformer-interfaces/API.md +++ b/packages/amplify-graphql-transformer-interfaces/API.md @@ -145,16 +145,6 @@ export type AssetProvider = { provide: (scope: Construct, name: string, props: AssetProps) => S3Asset; }; -// @public (undocumented) -export interface CustomSqlDataSourceStrategy { - // (undocumented) - readonly dataSourceType: DataSourceType; - // (undocumented) - readonly fieldName: string; - // (undocumented) - readonly typeName: 'Query' | 'Mutation'; -} - // Warning: (ae-forgotten-export) The symbol "NoneDataSourceProvider" needs to be exported by the entry point index.d.ts // // @public (undocumented) @@ -172,25 +162,12 @@ export interface DataSourceOptions { export interface DataSourceProvider extends BackedDataSource { } -// @public (undocumented) -export type DataSourceProvisionStrategy = DynamoDBProvisionStrategy | SQLLambdaModelProvisionStrategy; - // @public (undocumented) export interface DataSourceStrategiesProvider { // (undocumented) - customSqlDataSourceStrategies?: CustomSqlDataSourceStrategy[]; - // (undocumented) - modelToDatasourceMap: Map; -} - -// @public (undocumented) -export interface DataSourceType { - // (undocumented) - dbType: ModelDataSourceStrategyDbType; - // (undocumented) - provisionDB: boolean; + dataSourceStrategies: Record; // (undocumented) - provisionStrategy: DataSourceProvisionStrategy; + sqlDirectiveDataSourceStrategies?: SqlDirectiveDataSourceStrategy[]; } // @public (undocumented) @@ -207,14 +184,6 @@ export interface DynamoDbDataSourceOptions extends DataSourceOptions { readonly serviceRole: IRole; } -// @public (undocumented) -export const enum DynamoDBProvisionStrategy { - // (undocumented) - AMPLIFY_TABLE = "AMPLIFY_TABLE", - // (undocumented) - DEFAULT = "DEFAULT" -} - // @public (undocumented) export type FieldMapEntry = { originalFieldName: string; @@ -326,6 +295,12 @@ export interface RDSLayerMapping { }; } +// @public (undocumented) +export interface RDSLayerMappingProvider { + // (undocumented) + rdsLayerMapping?: RDSLayerMapping; +} + // @public (undocumented) type ReadonlyArray_2 = Readonly>>; export { ReadonlyArray_2 as ReadonlyArray } @@ -371,9 +346,19 @@ export interface SearchableDataSourceOptions extends DataSourceOptions { } // @public (undocumented) -export interface SQLLambdaModelDataSourceStrategy extends ModelDataSourceStrategyBase { +export interface SqlDirectiveDataSourceStrategy { // (undocumented) readonly customSqlStatements?: Record; + // (undocumented) + readonly fieldName: string; + // (undocumented) + readonly strategy: SQLLambdaModelDataSourceStrategy; + // (undocumented) + readonly typeName: 'Query' | 'Mutation'; +} + +// @public (undocumented) +export interface SQLLambdaModelDataSourceStrategy extends ModelDataSourceStrategyBase { // (undocumented) readonly dbConnectionConfig: SqlModelDataSourceDbConnectionConfig; // (undocumented) @@ -386,12 +371,6 @@ export interface SQLLambdaModelDataSourceStrategy extends ModelDataSourceStrateg readonly vpcConfiguration?: VpcConfig; } -// @public (undocumented) -export const enum SQLLambdaModelProvisionStrategy { - // (undocumented) - DEFAULT = "DEFAULT" -} - // @public (undocumented) export interface SqlModelDataSourceDbConnectionConfig { // (undocumented) @@ -457,16 +436,16 @@ export type SynthParameters = { export type TransformerAuthProvider = TransformerPluginProvider; // @public (undocumented) -export type TransformerBeforeStepContextProvider = Pick; +export type TransformerBeforeStepContextProvider = Pick; // @public (undocumented) export interface TransformerContextMetadataProvider { // (undocumented) - get(key: string): T | undefined; + get: (key: string) => T | undefined; // (undocumented) - has(key: string): boolean; + has: (key: string) => boolean; // (undocumented) - set(key: string, value: T): void; + set: (key: string, value: T) => void; } // @public (undocumented) @@ -528,44 +507,30 @@ export interface TransformerContextOutputProvider { } // @public (undocumented) -export interface TransformerContextProvider extends DataSourceStrategiesProvider { +export interface TransformerContextProvider extends DataSourceStrategiesProvider, RDSLayerMappingProvider { // (undocumented) api: GraphQLAPIProvider; // (undocumented) authConfig: AppSyncAuthConfiguration; // (undocumented) - customQueries: Map; - // (undocumented) - customSqlDataSourceStrategies?: CustomSqlDataSourceStrategy[]; - // (undocumented) dataSources: TransformerDataSourceManagerProvider; // (undocumented) - datasourceSecretParameterLocations: Map; - // (undocumented) - getResolverConfig(): ResolverConfig | undefined; + getResolverConfig: () => ResolverConfig | undefined; // (undocumented) inputDocument: DocumentNode; // (undocumented) - isProjectUsingDataStore(): boolean; + isProjectUsingDataStore: () => boolean; // (undocumented) metadata: TransformerContextMetadataProvider; // (undocumented) - modelToDatasourceMap: Map; - // (undocumented) output: TransformerContextOutputProvider; // (undocumented) providerRegistry: TransformerProviderRegistry; // (undocumented) - readonly rdsLayerMapping?: RDSLayerMapping; - // (undocumented) resolvers: TransformerResolversManagerProvider; // (undocumented) resourceHelper: TransformerResourceHelperProvider; // (undocumented) - readonly sqlLambdaProvisionedConcurrencyConfig?: ProvisionedConcurrencyConfig; - // (undocumented) - readonly sqlLambdaVpcConfig?: VpcConfig; - // (undocumented) stackManager: StackManagerProvider; // (undocumented) synthParameters: SynthParameters; @@ -814,7 +779,7 @@ export interface TransformerSchemaHelperProvider { } // @public (undocumented) -export type TransformerSchemaVisitStepContextProvider = Pick; +export type TransformerSchemaVisitStepContextProvider = Pick; // @public (undocumented) export type TransformerSecrets = { @@ -825,7 +790,7 @@ export type TransformerSecrets = { export type TransformerTransformSchemaStepContextProvider = TransformerValidationStepContextProvider; // @public (undocumented) -export type TransformerValidationStepContextProvider = Pick; +export type TransformerValidationStepContextProvider = Pick; // @public (undocumented) export interface TransformHostProvider { diff --git a/packages/amplify-graphql-transformer-interfaces/src/model-datasource/types.ts b/packages/amplify-graphql-transformer-interfaces/src/model-datasource/types.ts index 7e1fc9a9a1..ef0a200194 100644 --- a/packages/amplify-graphql-transformer-interfaces/src/model-datasource/types.ts +++ b/packages/amplify-graphql-transformer-interfaces/src/model-datasource/types.ts @@ -1,6 +1,3 @@ -// This dependency cycle should go away once we refactor the internals to use ModelDataSourceStrategy instead of DataSourceType -import { DataSourceType } from '../transformer-context/transformer-datasource-provider'; - // ######################################################################################################################################### // If you change types in this file (the internal implementation), be sure to make corresponding necessary changes to // amplify-graphql-api-construct/src/model-datasource-strategy.ts (the customer-facing interface) and the adapter functions in this file. @@ -47,6 +44,8 @@ export interface AmplifyDynamoDbModelDataSourceStrategy extends ModelDataSourceS /** * A strategy that creates a Lambda to connect to a pre-existing SQL table to resolve model data. * + * Note: The implementation type is different from the interface type: the interface type contains the custom SQL statements that are + * reference by the `@sql` `reference` attribute, while the implementation moves those into the SqlDirectiveDataSourceStrategy type. */ export interface SQLLambdaModelDataSourceStrategy extends ModelDataSourceStrategyBase { /** @@ -70,12 +69,6 @@ export interface SQLLambdaModelDataSourceStrategy extends ModelDataSourceStrateg */ readonly vpcConfiguration?: VpcConfig; - /** - * Custom SQL statements. The key is the value of the `references` attribute of the `@sql` directive in the `schema`; the value is the SQL - * to be executed. - */ - readonly customSqlStatements?: Record; - /** * The configuration for the provisioned concurrency of the Lambda. */ @@ -145,30 +138,58 @@ export interface SqlModelDataSourceDbConnectionConfig { readonly databaseNameSsmPath: string; } -export interface CustomSqlDataSourceStrategy { +/** + * The internal implementation type for defining a ModelDataSourceStrategy used to resolve a field annotated with a `@sql` directive. + * + * Note: The implementation type is different from the interface type: it directly contains the SQL statement to be executed rather than + * passing a map. + */ +export interface SqlDirectiveDataSourceStrategy { + /** The built-in type (either "Query" or "Mutation") with which the custom SQL is associated */ readonly typeName: 'Query' | 'Mutation'; + + /** The field name with which the custom SQL is associated */ readonly fieldName: string; - // TODO: Replace this when we support heterogeneous SQL data sources in a single API - readonly dataSourceType: DataSourceType; + + /** The strategy used to create the datasource that will resolve the custom SQL statement. */ + readonly strategy: SQLLambdaModelDataSourceStrategy; + + /** + * Custom SQL statements to be executed to resolve this field. + * + * Note: It's overkill to make this a map: a SqlDirectiveDataSourceStrategy will only ever use at most one statement (and maybe not even + * that if the directive uses inline statements). However, to avoid having to parse the directive configuration in multiple places, we'll + * pass the entire map as specified in the CDK construct definition, and let the directive transformer use it to look up references. + */ + readonly customSqlStatements?: Record; } +/** + * Defines types that vend a dataSourceStrategies and optional customSqlDataSourceStrategies field. Primarily used for transformer context. + */ export interface DataSourceStrategiesProvider { /** Maps GraphQL model names to the ModelDataSourceStrategy used to resolve it. The key of the record is the GraphQL type name. */ - // TODO: Add this back during combine feature - // dataSourceStrategies: Record; - - // TODO: Remove this when we refactor the internals to use ModelDataSourceStrategy instead of DataSourceType - modelToDatasourceMap: Map; + dataSourceStrategies: Record; /** Maps custom Query and Mutation fields to the ModelDataSourceStrategy used to resolve them. */ - customSqlDataSourceStrategies?: CustomSqlDataSourceStrategy[]; + sqlDirectiveDataSourceStrategies?: SqlDirectiveDataSourceStrategy[]; } /** - * Maps a given AWS region to the SQL Lambda layer version ARN for that region. TODO: Rename to SQLLambdaLayerMapping + * Maps a given AWS region to the SQL Lambda layer version ARN for that region. TODO: Once we remove SQL imports from Gen1 CLI, remove this + * from the transformer interfaces package in favor of the model generator, which is the only place that needs it now that we always resolve + * the layer mapping at deploy time. */ export interface RDSLayerMapping { readonly [key: string]: { layerRegion: string; }; } + +/** + * Defines types that vend an rdsLayerMapping field. This is used solely for the Gen1 CLI import API flow, since wiring the custom resource + * provider used by the CDK isn't worth the cost. TODO: Remove this once we remove SQL imports from Gen1 CLI. + */ +export interface RDSLayerMappingProvider { + rdsLayerMapping?: RDSLayerMapping; +} diff --git a/packages/amplify-graphql-transformer-interfaces/src/transformer-context/index.ts b/packages/amplify-graphql-transformer-interfaces/src/transformer-context/index.ts index e38fde1ec1..4735656b3d 100644 --- a/packages/amplify-graphql-transformer-interfaces/src/transformer-context/index.ts +++ b/packages/amplify-graphql-transformer-interfaces/src/transformer-context/index.ts @@ -3,10 +3,6 @@ export { TransformerDataSourceManagerProvider, AppSyncDataSourceType, DataSourceInstance, - DataSourceType, - DataSourceProvisionStrategy, - DynamoDBProvisionStrategy, - SQLLambdaModelProvisionStrategy, } from './transformer-datasource-provider'; export { TransformerContextOutputProvider } from './transformer-context-output-provider'; export { TransformerProviderRegistry } from './transformer-provider-registry'; diff --git a/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transformer-context-provider.ts b/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transformer-context-provider.ts index cce92dd4f7..d1942b9e33 100644 --- a/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transformer-context-provider.ts +++ b/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transformer-context-provider.ts @@ -1,13 +1,7 @@ import { DocumentNode } from 'graphql'; import { AppSyncAuthConfiguration, GraphQLAPIProvider } from '../graphql-api-provider'; -import { - CustomSqlDataSourceStrategy, - DataSourceStrategiesProvider, - ProvisionedConcurrencyConfig, - RDSLayerMapping, - VpcConfig, -} from '../model-datasource'; -import { TransformerDataSourceManagerProvider, DataSourceType } from './transformer-datasource-provider'; +import { DataSourceStrategiesProvider, RDSLayerMappingProvider } from '../model-datasource'; +import { TransformerDataSourceManagerProvider } from './transformer-datasource-provider'; import { TransformerProviderRegistry } from './transformer-provider-registry'; import { TransformerContextOutputProvider } from './transformer-context-output-provider'; import { StackManagerProvider } from './stack-manager-provider'; @@ -17,23 +11,20 @@ import { TransformerResolversManagerProvider } from './transformer-resolver-prov import { SynthParameters } from './synth-parameters'; export interface TransformerContextMetadataProvider { - set(key: string, value: T): void; - get(key: string): T | undefined; - has(key: string): boolean; + set: (key: string, value: T) => void; + get: (key: string) => T | undefined; + has: (key: string) => boolean; } export type TransformerSecrets = { [key: string]: any }; -export interface TransformerContextProvider extends DataSourceStrategiesProvider { +export interface TransformerContextProvider extends DataSourceStrategiesProvider, RDSLayerMappingProvider { metadata: TransformerContextMetadataProvider; resolvers: TransformerResolversManagerProvider; dataSources: TransformerDataSourceManagerProvider; providerRegistry: TransformerProviderRegistry; inputDocument: DocumentNode; - modelToDatasourceMap: Map; - customSqlDataSourceStrategies?: CustomSqlDataSourceStrategy[]; - datasourceSecretParameterLocations: Map; output: TransformerContextOutputProvider; stackManager: StackManagerProvider; api: GraphQLAPIProvider; @@ -41,20 +32,16 @@ export interface TransformerContextProvider extends DataSourceStrategiesProvider authConfig: AppSyncAuthConfiguration; transformParameters: TransformParameters; synthParameters: SynthParameters; - customQueries: Map; - isProjectUsingDataStore(): boolean; - getResolverConfig(): ResolverConfig | undefined; - readonly sqlLambdaVpcConfig?: VpcConfig; - readonly rdsLayerMapping?: RDSLayerMapping; - readonly sqlLambdaProvisionedConcurrencyConfig?: ProvisionedConcurrencyConfig; + isProjectUsingDataStore: () => boolean; + getResolverConfig: () => ResolverConfig | undefined; } export type TransformerBeforeStepContextProvider = Pick< TransformerContextProvider, | 'inputDocument' - | 'modelToDatasourceMap' - | 'customSqlDataSourceStrategies' + | 'dataSourceStrategies' + | 'sqlDirectiveDataSourceStrategies' | 'transformParameters' | 'isProjectUsingDataStore' | 'getResolverConfig' @@ -66,8 +53,8 @@ export type TransformerBeforeStepContextProvider = Pick< export type TransformerSchemaVisitStepContextProvider = Pick< TransformerContextProvider, | 'inputDocument' - | 'modelToDatasourceMap' - | 'customSqlDataSourceStrategies' + | 'dataSourceStrategies' + | 'sqlDirectiveDataSourceStrategies' | 'output' | 'providerRegistry' | 'transformParameters' @@ -81,8 +68,8 @@ export type TransformerSchemaVisitStepContextProvider = Pick< export type TransformerValidationStepContextProvider = Pick< TransformerContextProvider, | 'inputDocument' - | 'modelToDatasourceMap' - | 'customSqlDataSourceStrategies' + | 'dataSourceStrategies' + | 'sqlDirectiveDataSourceStrategies' | 'output' | 'providerRegistry' | 'dataSources' diff --git a/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transformer-datasource-provider.ts b/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transformer-datasource-provider.ts index 2964b5c484..a0794b8b52 100644 --- a/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transformer-datasource-provider.ts +++ b/packages/amplify-graphql-transformer-interfaces/src/transformer-context/transformer-datasource-provider.ts @@ -3,7 +3,6 @@ import { ITable } from 'aws-cdk-lib/aws-dynamodb'; import { CfnDomain } from 'aws-cdk-lib/aws-elasticsearch'; import { IFunction } from 'aws-cdk-lib/aws-lambda'; import { InterfaceTypeDefinitionNode, ObjectTypeDefinitionNode } from 'graphql'; -import { ModelDataSourceStrategyDbType } from '../model-datasource'; export enum AppSyncDataSourceType { AMAZON_DYNAMODB = 'AMAZON_DYNAMODB', @@ -27,37 +26,3 @@ export interface TransformerDataSourceManagerProvider { } export interface DataSourceProvider extends BackedDataSource {} - -// TODO: Remove this type in favor of fully-specified SQLLambdaModelDataSourceStrategy from amplify-graphql-api-construct -export type DataSourceProvisionStrategy = DynamoDBProvisionStrategy | SQLLambdaModelProvisionStrategy; - -/** - * Provisioning configuration for a DynamoDB datasource. TODO: Remove this type in favor of strategy definitions in - * amplify-graphql-api-construct - */ -// TODO: Remove this type in favor of fully-specified SQLLambdaModelDataSourceStrategy from amplify-graphql-api-construct -export const enum DynamoDBProvisionStrategy { - /** - * Use default CloudFormation resource of `AWS::DynamoDB::Table` - */ - DEFAULT = 'DEFAULT', - /** - * Use custom resource type `Custom::AmplifyDynamoDBTable` - */ - AMPLIFY_TABLE = 'AMPLIFY_TABLE', -} - -// TODO: Remove this type in favor of fully-specified SQLLambdaModelDataSourceStrategy from amplify-graphql-api-construct -export const enum SQLLambdaModelProvisionStrategy { - /** - * A strategy that creates a Lambda to connect to a pre-existing SQL table to resolve model data. - */ - DEFAULT = 'DEFAULT', -} - -// TODO: Replace usages of this type with ModelDataSourceStrategy -export interface DataSourceType { - dbType: ModelDataSourceStrategyDbType; - provisionDB: boolean; - provisionStrategy: DataSourceProvisionStrategy; -} diff --git a/packages/amplify-graphql-transformer-test-utils/API.md b/packages/amplify-graphql-transformer-test-utils/API.md index beda3676e8..655156cc1d 100644 --- a/packages/amplify-graphql-transformer-test-utils/API.md +++ b/packages/amplify-graphql-transformer-test-utils/API.md @@ -21,18 +21,17 @@ import { CfnRole } from 'aws-cdk-lib/aws-iam'; import { CfnStack } from 'aws-cdk-lib'; import { CfnTable } from 'aws-cdk-lib/aws-dynamodb'; import { Construct } from 'constructs'; -import type { CustomSqlDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; -import type { DataSourceType } from '@aws-amplify/graphql-transformer-interfaces'; import { ISynthesisSession } from 'aws-cdk-lib'; +import type { ModelDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; import type { NestedStackProvider } from '@aws-amplify/graphql-transformer-interfaces'; -import { RDSConnectionSecrets } from '@aws-amplify/graphql-transformer-core'; +import type { RDSLayerMappingProvider } from '@aws-amplify/graphql-transformer-interfaces'; import { ResolverConfig } from '@aws-amplify/graphql-transformer-core'; +import type { SqlDirectiveDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; import { Stack } from 'aws-cdk-lib'; import type { SynthParameters } from '@aws-amplify/graphql-transformer-interfaces'; import { TransformerPluginProvider } from '@aws-amplify/graphql-transformer-interfaces'; import type { TransformParameters } from '@aws-amplify/graphql-transformer-interfaces'; import { UserDefinedSlot } from '@aws-amplify/graphql-transformer-core'; -import type { VpcConfig } from '@aws-amplify/graphql-transformer-interfaces'; // @public (undocumented) export interface AmplifyApiGraphQlResourceStackTemplate { @@ -131,21 +130,18 @@ export const testTransform: (params: TestTransformParameters) => DeploymentResou }; // @public (undocumented) -export type TestTransformParameters = { - transformers: TransformerPluginProvider[]; - schema: string; - transformParameters?: Partial; - resolverConfig?: ResolverConfig; +export type TestTransformParameters = RDSLayerMappingProvider & { authConfig?: AppSyncAuthConfiguration; - userDefinedSlots?: Record; - stackMapping?: Record; - modelToDatasourceMap?: Map; - customSqlDataSourceStrategies?: CustomSqlDataSourceStrategy[]; - datasourceSecretParameterLocations?: Map; - customQueries?: Map; + dataSourceStrategies?: Record; overrideConfig?: OverrideConfig; - sqlLambdaVpcConfig?: VpcConfig; + resolverConfig?: ResolverConfig; + schema: string; + sqlDirectiveDataSourceStrategies?: SqlDirectiveDataSourceStrategy[]; + stackMapping?: Record; synthParameters?: Partial; + transformers: TransformerPluginProvider[]; + transformParameters?: Partial; + userDefinedSlots?: Record; }; // @public (undocumented) @@ -169,7 +165,7 @@ export class TransformManager { // Warnings were encountered during analysis: // -// src/test-transform.ts:32:3 - (ae-forgotten-export) The symbol "OverrideConfig" needs to be exported by the entry point index.d.ts +// src/test-transform.ts:23:3 - (ae-forgotten-export) The symbol "OverrideConfig" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/packages/amplify-graphql-transformer-test-utils/src/test-transform.ts b/packages/amplify-graphql-transformer-test-utils/src/test-transform.ts index 958bf6e0cb..e1043cef96 100644 --- a/packages/amplify-graphql-transformer-test-utils/src/test-transform.ts +++ b/packages/amplify-graphql-transformer-test-utils/src/test-transform.ts @@ -1,37 +1,34 @@ import { AppSyncAuthConfiguration, TransformerPluginProvider, TransformerLogLevel } from '@aws-amplify/graphql-transformer-interfaces'; import type { - DataSourceType, - CustomSqlDataSourceStrategy, + ModelDataSourceStrategy, + RDSLayerMappingProvider, + SqlDirectiveDataSourceStrategy, SynthParameters, TransformParameters, - VpcConfig, } from '@aws-amplify/graphql-transformer-interfaces'; import { - DDB_DEFAULT_DATASOURCE_TYPE, + DDB_DEFAULT_DATASOURCE_STRATEGY, GraphQLTransform, - RDSConnectionSecrets, ResolverConfig, UserDefinedSlot, - constructDataSourceMap, + constructDataSourceStrategies, } from '@aws-amplify/graphql-transformer-core'; import { OverrideConfig, TransformManager } from './cdk-compat/transform-manager'; import { DeploymentResources } from './deployment-resources'; -export type TestTransformParameters = { - transformers: TransformerPluginProvider[]; - schema: string; - transformParameters?: Partial; - resolverConfig?: ResolverConfig; +export type TestTransformParameters = RDSLayerMappingProvider & { authConfig?: AppSyncAuthConfiguration; - userDefinedSlots?: Record; - stackMapping?: Record; - modelToDatasourceMap?: Map; - customSqlDataSourceStrategies?: CustomSqlDataSourceStrategy[]; - datasourceSecretParameterLocations?: Map; - customQueries?: Map; + // Making this optional so test code can simply use a default DDB strategy for each model in the schema. + dataSourceStrategies?: Record; overrideConfig?: OverrideConfig; - sqlLambdaVpcConfig?: VpcConfig; + resolverConfig?: ResolverConfig; + schema: string; + sqlDirectiveDataSourceStrategies?: SqlDirectiveDataSourceStrategy[]; + stackMapping?: Record; synthParameters?: Partial; + transformers: TransformerPluginProvider[]; + transformParameters?: Partial; + userDefinedSlots?: Record; }; /** @@ -40,20 +37,18 @@ export type TestTransformParameters = { */ export const testTransform = (params: TestTransformParameters): DeploymentResources & { logs: any[] } => { const { - schema, - modelToDatasourceMap, - customSqlDataSourceStrategies, - datasourceSecretParameterLocations, - customQueries, - overrideConfig, - transformers, authConfig, + dataSourceStrategies, + overrideConfig, + rdsLayerMapping, resolverConfig, - userDefinedSlots, + schema, + sqlDirectiveDataSourceStrategies, stackMapping, - transformParameters, - sqlLambdaVpcConfig, synthParameters: overrideSynthParameters, + transformers, + transformParameters, + userDefinedSlots, } = params; const transform = new GraphQLTransform({ @@ -63,7 +58,6 @@ export const testTransform = (params: TestTransformParameters): DeploymentResour transformParameters, userDefinedSlots, resolverConfig, - sqlLambdaVpcConfig, }); const transformManager = new TransformManager(overrideConfig); @@ -84,12 +78,9 @@ export const testTransform = (params: TestTransformParameters): DeploymentResour ...overrideSynthParameters, }, schema, - datasourceConfig: { - modelToDatasourceMap: modelToDatasourceMap ?? constructDataSourceMap(schema, DDB_DEFAULT_DATASOURCE_TYPE), - datasourceSecretParameterLocations, - customQueries, - customSqlDataSourceStrategies, - }, + rdsLayerMapping, + dataSourceStrategies: dataSourceStrategies ?? constructDataSourceStrategies(schema, DDB_DEFAULT_DATASOURCE_STRATEGY), + sqlDirectiveDataSourceStrategies, }); const logs: any[] = []; diff --git a/packages/amplify-graphql-transformer/API.md b/packages/amplify-graphql-transformer/API.md index aa9c24d43c..437411a8ee 100644 --- a/packages/amplify-graphql-transformer/API.md +++ b/packages/amplify-graphql-transformer/API.md @@ -7,14 +7,11 @@ import { AppSyncAuthConfiguration } from '@aws-amplify/graphql-transformer-interfaces'; import { AssetProvider } from '@aws-amplify/graphql-transformer-interfaces'; import { Construct } from 'constructs'; -import type { CustomSqlDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; -import { DataSourceType } from '@aws-amplify/graphql-transformer-interfaces'; +import type { DataSourceStrategiesProvider } from '@aws-amplify/graphql-transformer-interfaces'; import { GraphQLTransform } from '@aws-amplify/graphql-transformer-core'; import { IFunction } from 'aws-cdk-lib/aws-lambda'; import { NestedStackProvider } from '@aws-amplify/graphql-transformer-interfaces'; -import { ProvisionedConcurrencyConfig } from '@aws-amplify/graphql-transformer-interfaces'; -import { RDSConnectionSecrets } from '@aws-amplify/graphql-transformer-core'; -import { RDSLayerMapping } from '@aws-amplify/graphql-transformer-interfaces'; +import type { RDSLayerMappingProvider } from '@aws-amplify/graphql-transformer-interfaces'; import { ResolverConfig } from '@aws-amplify/graphql-transformer-core'; import { SynthParameters } from '@aws-amplify/graphql-transformer-interfaces'; import { TransformerLog } from '@aws-amplify/graphql-transformer-interfaces'; @@ -22,7 +19,6 @@ import { TransformerPluginProvider } from '@aws-amplify/graphql-transformer-inte import { TransformParameterProvider } from '@aws-amplify/graphql-transformer-interfaces'; import type { TransformParameters } from '@aws-amplify/graphql-transformer-interfaces'; import { UserDefinedSlot } from '@aws-amplify/graphql-transformer-core'; -import { VpcConfig } from '@aws-amplify/graphql-transformer-interfaces'; // @public (undocumented) export const constructTransform: (config: TransformConfig) => GraphQLTransform; @@ -34,21 +30,14 @@ export const constructTransformerChain: (options?: TransformerFactoryArgs) => Tr export const executeTransform: (config: ExecuteTransformConfig) => void; // @public (undocumented) -export type ExecuteTransformConfig = TransformConfig & { +export type ExecuteTransformConfig = TransformConfig & DataSourceStrategiesProvider & RDSLayerMappingProvider & { schema: string; - modelToDatasourceMap?: Map; - customQueries?: Map; - customSqlDataSourceStrategies?: CustomSqlDataSourceStrategy[]; - datasourceSecretParameterLocations?: Map; printTransformerLog?: (log: TransformerLog) => void; - sqlLambdaVpcConfig?: VpcConfig; - rdsLayerMapping?: RDSLayerMapping; scope: Construct; nestedStackProvider: NestedStackProvider; parameterProvider?: TransformParameterProvider; assetProvider: AssetProvider; synthParameters: SynthParameters; - sqlLambdaProvisionedConcurrencyConfig?: ProvisionedConcurrencyConfig; }; // @public (undocumented) @@ -59,9 +48,6 @@ export type TransformConfig = { userDefinedSlots?: Record; stackMapping?: Record; transformParameters: TransformParameters; - sqlLambdaVpcConfig?: VpcConfig; - rdsLayerMapping?: RDSLayerMapping; - sqlLambdaProvisionedConcurrencyConfig?: ProvisionedConcurrencyConfig; }; // @public (undocumented) diff --git a/packages/amplify-graphql-transformer/src/__tests__/graphql-transformer.test.ts b/packages/amplify-graphql-transformer/src/__tests__/graphql-transformer.test.ts index f835546cc9..f558cf07ec 100644 --- a/packages/amplify-graphql-transformer/src/__tests__/graphql-transformer.test.ts +++ b/packages/amplify-graphql-transformer/src/__tests__/graphql-transformer.test.ts @@ -6,7 +6,7 @@ import { TransformerLogLevel, TransformerPluginProvider, } from '@aws-amplify/graphql-transformer-interfaces'; -import { DDB_DEFAULT_DATASOURCE_TYPE, GraphQLTransform, constructDataSourceMap } from '@aws-amplify/graphql-transformer-core'; +import { DDB_DEFAULT_DATASOURCE_STRATEGY, GraphQLTransform, constructDataSourceStrategies } from '@aws-amplify/graphql-transformer-core'; import { TransformerLog } from '@aws-amplify/graphql-transformer-interfaces/src'; import { NestedStack, Stack } from 'aws-cdk-lib'; import { Construct } from 'constructs'; @@ -71,6 +71,12 @@ describe('executeTransform', () => { const assets = new Map(); const tempAssetDir = fs.mkdtempSync(path.join(os.tmpdir(), 'transformer-assets')); + const schema = /* GraphQL */ ` + type Todo @model { + content: String! + } + `; + executeTransform({ scope: new Stack(), nestedStackProvider: { @@ -95,11 +101,8 @@ describe('executeTransform', () => { apiName: 'testApi', }, ...defaultTransformConfig, - schema: /* GraphQL */ ` - type Todo @model { - content: String! - } - `, + schema, + dataSourceStrategies: constructDataSourceStrategies(schema, DDB_DEFAULT_DATASOURCE_STRATEGY), }); }); @@ -151,7 +154,7 @@ describe('executeTransform', () => { printTransformerLog: (): void => { didLog = true; }, - modelToDatasourceMap: constructDataSourceMap(schema, DDB_DEFAULT_DATASOURCE_TYPE), + dataSourceStrategies: constructDataSourceStrategies(schema, DDB_DEFAULT_DATASOURCE_STRATEGY), }); expect(didLog).toEqual(true); }); @@ -159,6 +162,11 @@ describe('executeTransform', () => { it('does not log warnings on simple schema', () => { const assets = new Map(); const tempAssetDir = fs.mkdtempSync(path.join(os.tmpdir(), 'transformer-assets')); + const schema = /* GraphQL */ ` + type Todo @model { + content: String! + } + `; executeTransform({ scope: new Stack(), nestedStackProvider: { @@ -183,11 +191,8 @@ describe('executeTransform', () => { apiName: 'testApi', }, ...defaultTransformConfig, - schema: /* GraphQL */ ` - type Todo @model { - content: String! - } - `, + schema, + dataSourceStrategies: constructDataSourceStrategies(schema, DDB_DEFAULT_DATASOURCE_STRATEGY), printTransformerLog: ({ message }: TransformerLog): void => { throw new Error(`Transformer logging not expected, received ${message}`); }, diff --git a/packages/amplify-graphql-transformer/src/graphql-transformer.ts b/packages/amplify-graphql-transformer/src/graphql-transformer.ts index 43e6fc6c21..aae1bbf5b7 100644 --- a/packages/amplify-graphql-transformer/src/graphql-transformer.ts +++ b/packages/amplify-graphql-transformer/src/graphql-transformer.ts @@ -19,17 +19,17 @@ import { TransformerPluginProvider, TransformerLog, TransformerLogLevel, - VpcConfig, - RDSLayerMapping, NestedStackProvider, AssetProvider, SynthParameters, TransformParameterProvider, - DataSourceType, - ProvisionedConcurrencyConfig, } from '@aws-amplify/graphql-transformer-interfaces'; -import type { CustomSqlDataSourceStrategy, TransformParameters } from '@aws-amplify/graphql-transformer-interfaces'; -import { GraphQLTransform, RDSConnectionSecrets, ResolverConfig, UserDefinedSlot } from '@aws-amplify/graphql-transformer-core'; +import type { + DataSourceStrategiesProvider, + RDSLayerMappingProvider, + TransformParameters, +} from '@aws-amplify/graphql-transformer-interfaces'; +import { GraphQLTransform, ResolverConfig, UserDefinedSlot } from '@aws-amplify/graphql-transformer-core'; import { Construct } from 'constructs'; import { IFunction } from 'aws-cdk-lib/aws-lambda'; @@ -53,9 +53,6 @@ export type TransformConfig = { userDefinedSlots?: Record; stackMapping?: Record; transformParameters: TransformParameters; - sqlLambdaVpcConfig?: VpcConfig; - rdsLayerMapping?: RDSLayerMapping; - sqlLambdaProvisionedConcurrencyConfig?: ProvisionedConcurrencyConfig; }; export const constructTransformerChain = (options?: TransformerFactoryArgs): TransformerPluginProvider[] => { @@ -91,17 +88,7 @@ export const constructTransformerChain = (options?: TransformerFactoryArgs): Tra * @returns the GraphQLTransform object, which can be used for transformation or preprocessing a given schema. */ export const constructTransform = (config: TransformConfig): GraphQLTransform => { - const { - transformersFactoryArgs, - authConfig, - resolverConfig, - userDefinedSlots, - stackMapping, - transformParameters, - sqlLambdaVpcConfig, - rdsLayerMapping, - sqlLambdaProvisionedConcurrencyConfig, - } = config; + const { transformersFactoryArgs, authConfig, resolverConfig, userDefinedSlots, stackMapping, transformParameters } = config; const transformers = constructTransformerChain(transformersFactoryArgs); @@ -112,28 +99,20 @@ export const constructTransform = (config: TransformConfig): GraphQLTransform => transformParameters, userDefinedSlots, resolverConfig, - sqlLambdaVpcConfig, - rdsLayerMapping, - sqlLambdaProvisionedConcurrencyConfig, }); }; -export type ExecuteTransformConfig = TransformConfig & { - schema: string; - modelToDatasourceMap?: Map; - customQueries?: Map; - customSqlDataSourceStrategies?: CustomSqlDataSourceStrategy[]; - datasourceSecretParameterLocations?: Map; - printTransformerLog?: (log: TransformerLog) => void; - sqlLambdaVpcConfig?: VpcConfig; - rdsLayerMapping?: RDSLayerMapping; - scope: Construct; - nestedStackProvider: NestedStackProvider; - parameterProvider?: TransformParameterProvider; - assetProvider: AssetProvider; - synthParameters: SynthParameters; - sqlLambdaProvisionedConcurrencyConfig?: ProvisionedConcurrencyConfig; -}; +export type ExecuteTransformConfig = TransformConfig & + DataSourceStrategiesProvider & + RDSLayerMappingProvider & { + schema: string; + printTransformerLog?: (log: TransformerLog) => void; + scope: Construct; + nestedStackProvider: NestedStackProvider; + parameterProvider?: TransformParameterProvider; + assetProvider: AssetProvider; + synthParameters: SynthParameters; + }; /** * By default, rely on console to print out the transformer logs. @@ -165,37 +144,31 @@ export const defaultPrintTransformerLog = (log: TransformerLog): void => { */ export const executeTransform = (config: ExecuteTransformConfig): void => { const { - schema, - modelToDatasourceMap, - customSqlDataSourceStrategies, - datasourceSecretParameterLocations, + assetProvider, + dataSourceStrategies, + nestedStackProvider, + parameterProvider, printTransformerLog, rdsLayerMapping, + schema, scope, - nestedStackProvider, - assetProvider, + sqlDirectiveDataSourceStrategies, synthParameters, - customQueries, - parameterProvider, } = config; const printLog = printTransformerLog ?? defaultPrintTransformerLog; const transform = constructTransform(config); try { transform.transform({ - scope, + assetProvider, + dataSourceStrategies, nestedStackProvider, parameterProvider, - assetProvider, - synthParameters, + rdsLayerMapping, schema, - datasourceConfig: { - modelToDatasourceMap, - customSqlDataSourceStrategies, - datasourceSecretParameterLocations, - rdsLayerMapping, - customQueries, - }, + scope, + sqlDirectiveDataSourceStrategies, + synthParameters, }); } finally { transform.getLogs().forEach(printLog); diff --git a/packages/amplify-util-mock/src/__e2e__/model-with-maps-to.e2e.test.ts b/packages/amplify-util-mock/src/__e2e__/model-with-maps-to.e2e.test.ts index 9e3202e5f2..51d8280bbd 100644 --- a/packages/amplify-util-mock/src/__e2e__/model-with-maps-to.e2e.test.ts +++ b/packages/amplify-util-mock/src/__e2e__/model-with-maps-to.e2e.test.ts @@ -1,4 +1,3 @@ -import { DDB_DEFAULT_DATASOURCE_TYPE } from '@aws-amplify/graphql-transformer-core'; import { GraphQLClient } from './utils/graphql-client'; import { defaultTransformParams, deploy, launchDDBLocal, logDebug, terminateDDB, transformAndSynth } from './utils/index'; @@ -26,11 +25,6 @@ beforeAll(async () => { ...defaultTransformParams.transformParameters, useSubUsernameForDefaultIdentityClaim: false, }, - modelToDatasourceMap: new Map( - Object.entries({ - Todo: DDB_DEFAULT_DATASOURCE_TYPE, - }), - ), }); let ddbClient; diff --git a/packages/amplify-util-mock/src/__e2e__/utils/index.ts b/packages/amplify-util-mock/src/__e2e__/utils/index.ts index 32c70f6551..bfbff49e83 100644 --- a/packages/amplify-util-mock/src/__e2e__/utils/index.ts +++ b/packages/amplify-util-mock/src/__e2e__/utils/index.ts @@ -7,8 +7,8 @@ import { DynamoDB } from 'aws-sdk'; import { functionRuntimeContributorFactory } from 'amplify-nodejs-function-runtime-provider'; import { ExecuteTransformConfig, executeTransform } from '@aws-amplify/graphql-transformer'; import { DeploymentResources, TransformManager } from '@aws-amplify/graphql-transformer-test-utils'; -import { AppSyncAuthConfiguration } from '@aws-amplify/graphql-transformer-interfaces'; -import { constructDataSourceMap, DDB_DEFAULT_DATASOURCE_TYPE } from '@aws-amplify/graphql-transformer-core'; +import { AppSyncAuthConfiguration, ModelDataSourceStrategy } from '@aws-amplify/graphql-transformer-interfaces'; +import { DDB_DEFAULT_DATASOURCE_STRATEGY, constructDataSourceStrategies } from '@aws-amplify/graphql-transformer-core'; import { processTransformerStacks } from '../../CFNParser/appsync-resource-processor'; import { configureDDBDataSource, createAndUpdateTable } from '../../utils/dynamo-db'; import { getFunctionDetails } from './lambda-helper'; @@ -35,7 +35,9 @@ const hasUserPoolAuth = (authConfig?: AppSyncAuthConfiguration): boolean => getAuthenticationTypesForAuthConfig(authConfig).some((authType) => authType === 'AMAZON_COGNITO_USER_POOLS'); export const transformAndSynth = ( - options: Omit, + options: Omit & { + dataSourceStrategies?: Record; + }, ): DeploymentResources => { const transformManager = new TransformManager(); executeTransform({ @@ -44,7 +46,7 @@ export const transformAndSynth = ( nestedStackProvider: transformManager.getNestedStackProvider(), assetProvider: transformManager.getAssetProvider(), synthParameters: transformManager.getSynthParameters(hasIamAuth(options.authConfig), hasUserPoolAuth(options.authConfig)), - modelToDatasourceMap: constructDataSourceMap(options.schema, DDB_DEFAULT_DATASOURCE_TYPE), + dataSourceStrategies: options.dataSourceStrategies ?? constructDataSourceStrategies(options.schema, DDB_DEFAULT_DATASOURCE_STRATEGY), }); return transformManager.generateDeploymentResources(); }; diff --git a/packages/amplify-util-mock/src/__e2e_v2__/function-transformer.e2e.test.ts b/packages/amplify-util-mock/src/__e2e_v2__/function-transformer.e2e.test.ts index 52fa02ddad..c518dbdd99 100644 --- a/packages/amplify-util-mock/src/__e2e_v2__/function-transformer.e2e.test.ts +++ b/packages/amplify-util-mock/src/__e2e_v2__/function-transformer.e2e.test.ts @@ -3,8 +3,8 @@ import { deploy, logDebug, GraphQLClient, defaultTransformParams, transformAndSy jest.setTimeout(2000000); -const ECHO_FUNCTION_NAME = `echoFunction`; -const HELLO_FUNCTION_NAME = `hello`; +const ECHO_FUNCTION_NAME = 'echoFunction'; +const HELLO_FUNCTION_NAME = 'hello'; let GRAPHQL_CLIENT: GraphQLClient; let server: AmplifyAppSyncSimulator; diff --git a/packages/graphql-transformer-core/API.md b/packages/graphql-transformer-core/API.md index 8776043a6a..1beb28b84e 100644 --- a/packages/graphql-transformer-core/API.md +++ b/packages/graphql-transformer-core/API.md @@ -111,9 +111,6 @@ export const enum ConflictHandlerType { OPTIMISTIC = "OPTIMISTIC_CONCURRENCY" } -// @public (undocumented) -export const constructDataSourceMap: (schema: string, datasourceType: DataSourceType) => Map; - // @public (undocumented) export type DataSourceProvisionStrategy = DynamoDBProvisionStrategy; diff --git a/packages/graphql-transformer-core/src/util/transformConfig.ts b/packages/graphql-transformer-core/src/util/transformConfig.ts index cb02648e2d..d52084b780 100644 --- a/packages/graphql-transformer-core/src/util/transformConfig.ts +++ b/packages/graphql-transformer-core/src/util/transformConfig.ts @@ -170,7 +170,12 @@ interface ProjectConfiguration { [k: string]: Template; }; config: TransformConfig; + + /** TODO: Remove this type when we migrate our SQL E2E tests to use the CDK construct rather than the Gen1 CLI to provision an API. This + * is not compatible with transformer internals. */ modelToDatasourceMap: Map; + /** TODO: Remove this type when we migrate our SQL E2E tests to use the CDK construct rather than the Gen1 CLI to provision an API. This + * is not compatible with transformer internals. */ customQueries: Map; } export const loadProject = async (projectDirectory: string, opts?: ProjectOptions): Promise => { @@ -402,13 +407,16 @@ async function readSchemaDocuments(schemaDirectoryPath: string): Promise => { +const constructDataSourceMap = (schema: string, datasourceType: DataSourceType): Map => { const parsedSchema = parse(schema); const result = new Map(); parsedSchema.definitions diff --git a/packages/graphql-transformers-e2e-tests/src/__tests__/MapsToTransformer.e2e.test.ts b/packages/graphql-transformers-e2e-tests/src/__tests__/MapsToTransformer.e2e.test.ts index 9430eb559d..4c9496cbe3 100644 --- a/packages/graphql-transformers-e2e-tests/src/__tests__/MapsToTransformer.e2e.test.ts +++ b/packages/graphql-transformers-e2e-tests/src/__tests__/MapsToTransformer.e2e.test.ts @@ -2,17 +2,10 @@ import { MapsToTransformer } from '@aws-amplify/graphql-maps-to-transformer'; import { ModelTransformer } from '@aws-amplify/graphql-model-transformer'; import { BelongsToTransformer, HasManyTransformer } from '@aws-amplify/graphql-relational-transformer'; import { testTransform } from '@aws-amplify/graphql-transformer-test-utils'; -import { DDB_DB_TYPE } from '@aws-amplify/graphql-transformer-core'; -import { DataSourceType, DynamoDBProvisionStrategy } from '@aws-amplify/graphql-transformer-interfaces'; import { getSchemaDeployer, SchemaDeployer } from '../deploySchema'; describe('@mapsTo transformer', () => { jest.setTimeout(1000 * 60 * 15); // 15 minutes - const ddbInfo: DataSourceType = { - dbType: DDB_DB_TYPE, - provisionDB: true, - provisionStrategy: DynamoDBProvisionStrategy.DEFAULT, - }; const transform = (schema: string) => testTransform({ schema, @@ -21,14 +14,8 @@ describe('@mapsTo transformer', () => { sandboxModeEnabled: true, }, transformers: [new ModelTransformer(), new HasManyTransformer(), new BelongsToTransformer(), new MapsToTransformer()], - modelToDatasourceMap: new Map( - Object.entries({ - Post: ddbInfo, - Comment: ddbInfo, - Article: ddbInfo, - }), - ), }); + const initialSchema = /* GraphQL */ ` type Post @model { id: ID!