From 21223d6d993612e4c2a47d547acefda07e67a52b Mon Sep 17 00:00:00 2001 From: Ian Hou <45278651+iankhou@users.noreply.github.com> Date: Thu, 30 Oct 2025 12:01:58 -0400 Subject: [PATCH] feat: api for adopted table during gen1 -> gen2 migration --- .changeset/tasty-wings-brush.md | 5 + .../backend-data/src/convert_schema.test.ts | 103 +++++++++++------- packages/backend-data/src/convert_schema.ts | 42 +++++-- packages/backend-data/src/factory.ts | 4 +- packages/backend-data/src/types.ts | 1 + 5 files changed, 106 insertions(+), 49 deletions(-) create mode 100644 .changeset/tasty-wings-brush.md diff --git a/.changeset/tasty-wings-brush.md b/.changeset/tasty-wings-brush.md new file mode 100644 index 00000000000..d1cbce6d570 --- /dev/null +++ b/.changeset/tasty-wings-brush.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/backend-data': minor +--- + +Enabled a new "adoption" strategy for moving DynamoDB tables from Gen1 to Gen2, which will require an import operation. The migrated table will be fully managed by the Gen2 stack. diff --git a/packages/backend-data/src/convert_schema.test.ts b/packages/backend-data/src/convert_schema.test.ts index 93ea8236ee4..b4aab17bc27 100644 --- a/packages/backend-data/src/convert_schema.test.ts +++ b/packages/backend-data/src/convert_schema.test.ts @@ -80,11 +80,11 @@ void describe('convertSchemaToCDK', () => { echo(message: String!): String! } `; - const convertedDefinition = convertSchemaToCDK( - graphqlSchema, - secretResolver, + const convertedDefinition = convertSchemaToCDK({ + schema: graphqlSchema, + backendSecretResolver: secretResolver, stableBackendIdentifiers, - ); + }); assert.deepEqual(convertedDefinition.schema, graphqlSchema); assert.deepEqual(convertedDefinition.dataSourceStrategies, { Todo: { @@ -107,11 +107,11 @@ void describe('convertSchemaToCDK', () => { }), }) .authorization((allow) => allow.publicApiKey()); - const convertedDefinition = convertSchemaToCDK( - typedSchema, - secretResolver, + const convertedDefinition = convertSchemaToCDK({ + schema: typedSchema, + backendSecretResolver: secretResolver, stableBackendIdentifiers, - ); + }); assert.deepEqual( removeWhiteSpaceForComparison(convertedDefinition.schema), removeWhiteSpaceForComparison(expectedGraphqlSchema), @@ -135,11 +135,11 @@ void describe('convertSchemaToCDK', () => { }), }) .authorization((allow) => allow.publicApiKey()); - const convertedDefinition = convertSchemaToCDK( - typedSchema, - secretResolver, + const convertedDefinition = convertSchemaToCDK({ + schema: typedSchema, + backendSecretResolver: secretResolver, stableBackendIdentifiers, - ); + }); assert.deepEqual(convertedDefinition.dataSourceStrategies, { Todo: { dbType: 'DYNAMODB', @@ -152,12 +152,12 @@ void describe('convertSchemaToCDK', () => { }); }); - void it('uses the only appropriate dbType and provisioningStrategy', () => { - const convertedDefinition = convertSchemaToCDK( - 'type Todo @model @auth(rules: { allow: public }) { id: ID! }', - secretResolver, + void it('uses the default dbType and provisioningStrategy', () => { + const convertedDefinition = convertSchemaToCDK({ + schema: 'type Todo @model @auth(rules: { allow: public }) { id: ID! }', + backendSecretResolver: secretResolver, stableBackendIdentifiers, - ); + }); assert.equal( Object.values(convertedDefinition.dataSourceStrategies).length, 1, @@ -198,11 +198,11 @@ void describe('convertSchemaToCDK', () => { .authorization((allow) => allow.publicApiKey()), }); - const convertedDefinition = convertSchemaToCDK( - modified, - secretResolver, + const convertedDefinition = convertSchemaToCDK({ + schema: modified, + backendSecretResolver: secretResolver, stableBackendIdentifiers, - ); + }); assert.equal( Object.values(convertedDefinition.dataSourceStrategies).length, @@ -253,11 +253,11 @@ void describe('convertSchemaToCDK', () => { .authorization((allow) => allow.publicApiKey()), }); - const convertedDefinition = convertSchemaToCDK( - modified, - secretResolver, + const convertedDefinition = convertSchemaToCDK({ + schema: modified, + backendSecretResolver: secretResolver, stableBackendIdentifiers, - ); + }); assert.equal( Object.values(convertedDefinition.dataSourceStrategies).length, @@ -337,11 +337,11 @@ void describe('convertSchemaToCDK', () => { .authorization((allow) => allow.publicApiKey()), }); - const convertedDefinition = convertSchemaToCDK( - modified, - secretResolver, + const convertedDefinition = convertSchemaToCDK({ + schema: modified, + backendSecretResolver: secretResolver, stableBackendIdentifiers, - ); + }); assert.equal( Object.values(convertedDefinition.dataSourceStrategies).length, @@ -420,11 +420,11 @@ void describe('convertSchemaToCDK', () => { .authorization((allow) => allow.publicApiKey()), }); - const convertedDefinition = convertSchemaToCDK( - modified, - secretResolver, + const convertedDefinition = convertSchemaToCDK({ + schema: modified, + backendSecretResolver: secretResolver, stableBackendIdentifiers, - ); + }); assert.equal( Object.values(convertedDefinition.dataSourceStrategies).length, @@ -480,11 +480,11 @@ void describe('convertSchemaToCDK', () => { .authorization((allow) => allow.publicApiKey()), }); - const convertedDefinition = convertSchemaToCDK( - modified, - secretResolver, + const convertedDefinition = convertSchemaToCDK({ + schema: modified, + backendSecretResolver: secretResolver, stableBackendIdentifiers, - ); + }); assert.equal( Object.values(convertedDefinition.dataSourceStrategies).length, @@ -514,4 +514,33 @@ void describe('convertSchemaToCDK', () => { }, ); }); + + void it('produces IMPORTED strategy for importedTableName', () => { + const result = convertSchemaToCDK({ + schema: 'type Todo @model { id: ID! }', + backendSecretResolver: secretResolver, + stableBackendIdentifiers, + importedTableName: 'ExistingTable', + }); + assert.deepEqual(Object.values(result.dataSourceStrategies)[0], { + dbType: 'DYNAMODB', + provisionStrategy: 'IMPORTED_AMPLIFY_TABLE', + tableName: 'ExistingTable', + }); + }); + + void it('produces ADOPTED strategy for shouldAdoptExistingTable', () => { + const result = convertSchemaToCDK({ + schema: 'type Todo @model { id: ID! }', + backendSecretResolver: secretResolver, + stableBackendIdentifiers, + importedTableName: 'AdoptedTable', + shouldAdoptExistingTable: true, + }); + assert.deepEqual(Object.values(result.dataSourceStrategies)[0], { + dbType: 'DYNAMODB', + provisionStrategy: 'ADOPTED_AMPLIFY_TABLE', + tableName: 'AdoptedTable', + }); + }); }); diff --git a/packages/backend-data/src/convert_schema.ts b/packages/backend-data/src/convert_schema.ts index d160e9c6f4b..b689c12d728 100644 --- a/packages/backend-data/src/convert_schema.ts +++ b/packages/backend-data/src/convert_schema.ts @@ -72,6 +72,12 @@ const IMPORTED_DYNAMO_DATA_SOURCE_STRATEGY = { provisionStrategy: 'IMPORTED_AMPLIFY_TABLE', } as const; +// Adopted strategy where Amplify will synthesize a managed CFN table resource +const ADOPTED_DYNAMO_DATA_SOURCE_STRATEGY = { + dbType: 'DYNAMODB', + provisionStrategy: 'ADOPTED_AMPLIFY_TABLE', +} as const; + // Translate the external engine types to the config values // Reference: https://github.com/aws-amplify/amplify-category-api/blob/fd7f6fbc17c199331c4b04debaff69ea0424cd74/packages/amplify-graphql-api-construct/src/model-datasource-strategy-types.ts#L25 const SQL_DB_TYPES = { @@ -81,18 +87,28 @@ const SQL_DB_TYPES = { /** * Given an input schema type, produce the relevant CDK Graphql Def interface - * @param schema TS schema builder definition or string GraphQL schema - * @param backendSecretResolver secret resolver - * @param stableBackendIdentifiers backend identifiers - * @param importedTableName table name to use for imported models. If not defined the model is not imported. + * @param params configuration parameters + * @param params.schema TS schema builder definition or string GraphQL schema + * @param params.backendSecretResolver secret resolver + * @param params.stableBackendIdentifiers backend identifiers + * @param params.importedTableName table name to use for imported models. If not defined the model is not imported. + * @param params.shouldAdoptExistingTable whether to adopt the existing table * @returns the cdk graphql definition interface */ -export const convertSchemaToCDK = ( - schema: DataSchema, - backendSecretResolver: BackendSecretResolver, - stableBackendIdentifiers: StableBackendIdentifiers, - importedTableName?: string, -): IAmplifyDataDefinition => { +export const convertSchemaToCDK = (params: { + schema: DataSchema; + backendSecretResolver: BackendSecretResolver; + stableBackendIdentifiers: StableBackendIdentifiers; + importedTableName?: string; + shouldAdoptExistingTable?: boolean; +}): IAmplifyDataDefinition => { + const { + schema, + backendSecretResolver, + stableBackendIdentifiers, + importedTableName, + shouldAdoptExistingTable, + } = params; if (isDataSchema(schema)) { /** * This is not super obvious, but the IAmplifyDataDefinition interface requires a record of each model type to a @@ -146,6 +162,12 @@ export const convertSchemaToCDK = ( } if (importedTableName) { + if (shouldAdoptExistingTable) { + return AmplifyDataDefinition.fromString(schema, { + ...ADOPTED_DYNAMO_DATA_SOURCE_STRATEGY, + tableName: importedTableName, + } as unknown as ModelDataSourceStrategy); + } return AmplifyDataDefinition.fromString(schema, { ...IMPORTED_DYNAMO_DATA_SOURCE_STRATEGY, tableName: importedTableName, diff --git a/packages/backend-data/src/factory.ts b/packages/backend-data/src/factory.ts index 9e7b4e10e12..a0a42d0c63f 100644 --- a/packages/backend-data/src/factory.ts +++ b/packages/backend-data/src/factory.ts @@ -195,12 +195,12 @@ class DataGenerator implements ConstructContainerEntryGenerator { } amplifyGraphqlDefinitions.push( - convertSchemaToCDK( + convertSchemaToCDK({ schema, backendSecretResolver, stableBackendIdentifiers, importedTableName, - ), + }), ); }); } catch (error) { diff --git a/packages/backend-data/src/types.ts b/packages/backend-data/src/types.ts index d7b537d8a3c..3989f27f551 100644 --- a/packages/backend-data/src/types.ts +++ b/packages/backend-data/src/types.ts @@ -253,4 +253,5 @@ export type DataLogLevel = Extract< export type AmplifyGen1DynamoDbTableMapping = { branchName: string; modelNameToTableNameMapping?: Record; + shouldAdoptExistingTable?: boolean; };