Skip to content

Commit

Permalink
fix: allow custom table names via props (#221)
Browse files Browse the repository at this point in the history
* fix: allow custom table names via props
  • Loading branch information
kcwinner authored Jun 15, 2021
1 parent 206cccc commit 02f6041
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 14 deletions.
6 changes: 5 additions & 1 deletion API.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ new AppSyncTransformer(scope: Construct, id: string, props: AppSyncTransformerPr
* **dynamoDbStreamConfig** (<code>Map<string, [StreamViewType](#aws-cdk-aws-dynamodb-streamviewtype)></code>) A map of @model type names to stream view type e.g { Blog: StreamViewType.NEW_IMAGE }. __*Optional*__
* **enableDynamoPointInTimeRecovery** (<code>boolean</code>) Whether to enable dynamo Point In Time Recovery. __*Default*__: false
* **fieldLogLevel** (<code>[FieldLogLevel](#aws-cdk-aws-appsync-fieldloglevel)</code>) Optional. __*Default*__: FieldLogLevel.NONE
* **nestedStackName** (<code>string</code>) Specify a custom nested stack name. __*Default*__: "appsync-nested-stack"
* **postCdkTransformers** (<code>Array<any></code>) Optional. __*Default*__: undefined
* **preCdkTransformers** (<code>Array<any></code>) Optional. __*Default*__: undefined
* **syncEnabled** (<code>boolean</code>) Whether to enable Amplify DataStore and Sync Tables. __*Default*__: false
* **tableNames** (<code>Map<string, string></code>) A map of names to specify the generated dynamo table names instead of auto generated names. __*Default*__: undefined
* **xrayEnabled** (<code>boolean</code>) Determines whether xray should be enabled on the AppSync API. __*Default*__: false


Expand All @@ -69,7 +71,7 @@ Name | Type | Description
**outputs**🔹 | <code>[SchemaTransformerOutputs](#cdk-appsync-transformer-schematransformeroutputs)</code> | The outputs from the SchemaTransformer.
**resolvers**🔹 | <code>Map<string, [CdkTransformerResolver](#cdk-appsync-transformer-cdktransformerresolver)></code> | The AppSync resolvers from the transformer minus any function resolvers.
**tableMap**🔹 | <code>Map<string, [Table](#aws-cdk-aws-dynamodb-table)></code> | Map of cdk table keys to L2 Table e.g. { 'TaskTable': Table }.
**tableNameMap**🔹 | <code>Map<string, any></code> | Map of cdk table tokens to table names.
**tableNameMap**🔹 | <code>Map<string, string></code> | Map of cdk table tokens to table names.

### Methods

Expand Down Expand Up @@ -156,9 +158,11 @@ Name | Type | Description
**dynamoDbStreamConfig**?🔹 | <code>Map<string, [StreamViewType](#aws-cdk-aws-dynamodb-streamviewtype)></code> | A map of @model type names to stream view type e.g { Blog: StreamViewType.NEW_IMAGE }.<br/>__*Optional*__
**enableDynamoPointInTimeRecovery**?🔹 | <code>boolean</code> | Whether to enable dynamo Point In Time Recovery.<br/>__*Default*__: false
**fieldLogLevel**?🔹 | <code>[FieldLogLevel](#aws-cdk-aws-appsync-fieldloglevel)</code> | Optional.<br/>__*Default*__: FieldLogLevel.NONE
**nestedStackName**?🔹 | <code>string</code> | Specify a custom nested stack name.<br/>__*Default*__: "appsync-nested-stack"
**postCdkTransformers**?🔹 | <code>Array<any></code> | Optional.<br/>__*Default*__: undefined
**preCdkTransformers**?🔹 | <code>Array<any></code> | Optional.<br/>__*Default*__: undefined
**syncEnabled**?🔹 | <code>boolean</code> | Whether to enable Amplify DataStore and Sync Tables.<br/>__*Default*__: false
**tableNames**?🔹 | <code>Map<string, string></code> | A map of names to specify the generated dynamo table names instead of auto generated names.<br/>__*Default*__: undefined
**xrayEnabled**?🔹 | <code>boolean</code> | Determines whether xray should be enabled on the AppSync API.<br/>__*Default*__: false


Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,19 @@ Often you will need to access your table names in a lambda function or elsewhere

You may need to access your dynamo table L2 constructs. These can be accessed via `appSyncTransformer.tableMap`.

### Custom Table Names

If you do not like autogenerated names for your Dynamo tables you can pass in props to specify them. Use the `tableKey` value derived from the `@model` directive. Example, if you have `type Foo @model` you would use `FooTable` as the key value.
```ts
const appSyncTransformer = new AppSyncTransformer(stack, 'test-transformer', {
schemaPath: testSchemaPath,
tableNames: {
CustomerTable: customerTableName,
OrderTable: orderTableName
},
});
```

### DynamoDB Streams

There are two ways to enable DynamoDB streams for a table. The first version is probably most preferred. You pass in the `@model` type name and the StreamViewType as properties when creating the AppSyncTransformer. This will also allow you to access the `tableStreamArn` property of the L2 table construct from the `tableMap`.
Expand Down
41 changes: 28 additions & 13 deletions src/appsync-transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,24 @@ export interface AppSyncTransformerProps {
*/
readonly xrayEnabled?: boolean;

/**
* A map of names to specify the generated dynamo table names instead of auto generated names
* @default undefined
*/
readonly tableNames?: Record<string, string>;

/**
* A map of @model type names to stream view type
* e.g { Blog: StreamViewType.NEW_IMAGE }
*/
readonly dynamoDbStreamConfig?: { [name: string]: StreamViewType };

/**
* Specify a custom nested stack name
* @default "appsync-nested-stack"
*/
readonly nestedStackName?: string;

/**
* Optional. Additonal custom transformers to run prior to the CDK resource generations.
* Particularly useful for custom directives.
Expand Down Expand Up @@ -132,7 +144,7 @@ export class AppSyncTransformer extends Construct {
/**
* Map of cdk table tokens to table names
*/
public readonly tableNameMap: { [name: string]: any };
public readonly tableNameMap: { [name: string]: string };

/**
* Map of cdk table keys to L2 Table
Expand Down Expand Up @@ -204,7 +216,7 @@ export class AppSyncTransformer extends Construct {
for (const [_, functionResolvers] of Object.entries(
this.functionResolvers,
)) {
functionResolvers.forEach((resolver: any) => {
functionResolvers.forEach((resolver) => {
switch (resolver.typeName) {
case 'Query':
case 'Mutation':
Expand All @@ -220,7 +232,7 @@ export class AppSyncTransformer extends Construct {
// Remove any http resolvers from the total list of resolvers
// Otherwise it will add them twice
for (const [_, httpResolvers] of Object.entries(this.httpResolvers)) {
httpResolvers.forEach((resolver: any) => {
httpResolvers.forEach((resolver) => {
switch (resolver.typeName) {
case 'Query':
case 'Mutation':
Expand All @@ -233,7 +245,7 @@ export class AppSyncTransformer extends Construct {

this.resolvers = resolvers;

this.nestedAppsyncStack = new NestedStack(this, 'appsync-nested-stack');
this.nestedAppsyncStack = new NestedStack(this, props.nestedStackName ?? 'appsync-nested-stack');

// AppSync
this.appsyncAPI = new GraphqlApi(this.nestedAppsyncStack, `${id}-api`, {
Expand All @@ -259,7 +271,7 @@ export class AppSyncTransformer extends Construct {
delete tableData.DataStore; // We don't want to create this again below so remove it from the tableData map
}

this.tableNameMap = this.createTablesAndResolvers(tableData, resolvers);
this.tableNameMap = this.createTablesAndResolvers(tableData, resolvers, props.tableNames);
if (this.outputs.noneResolvers) {
this.createNoneDataSourceAndResolvers(
this.outputs.noneResolvers,
Expand Down Expand Up @@ -303,7 +315,7 @@ export class AppSyncTransformer extends Construct {
) {
const noneDataSource = this.appsyncAPI.addNoneDataSource('NONE');

Object.keys(noneResolvers).forEach((resolverKey: any) => {
Object.keys(noneResolvers).forEach((resolverKey) => {
const resolver = resolvers[resolverKey];
new Resolver(
this.nestedAppsyncStack,
Expand Down Expand Up @@ -334,11 +346,13 @@ export class AppSyncTransformer extends Construct {
private createTablesAndResolvers(
tableData: { [name: string]: CdkTransformerTable },
resolvers: any,
tableNames: Record<string, string> = {},
): { [name: string]: string } {
const tableNameMap: any = {};

Object.keys(tableData).forEach((tableKey: any) => {
const table = this.createTable(tableData[tableKey]);
Object.keys(tableData).forEach((tableKey) => {
const tableName = tableNames[tableKey] ?? undefined;
const table = this.createTable(tableData[tableKey], tableName);
this.tableMap[tableKey] = table;

const dataSource = this.appsyncAPI.addDynamoDbDataSource(tableKey, table);
Expand Down Expand Up @@ -373,7 +387,7 @@ export class AppSyncTransformer extends Construct {
tableNameMap[tableKey] = dynamoDbConfig.tableName;

// Loop the basic resolvers
tableData[tableKey].resolvers.forEach((resolverKey: any) => {
tableData[tableKey].resolvers.forEach((resolverKey) => {
let resolver = resolvers[resolverKey];
new Resolver(
this.nestedAppsyncStack,
Expand All @@ -394,7 +408,7 @@ export class AppSyncTransformer extends Construct {
});

// Loop the gsi resolvers
tableData[tableKey].gsiResolvers.forEach((resolverKey: any) => {
tableData[tableKey].gsiResolvers.forEach((resolverKey) => {
let resolver = resolvers.gsi[resolverKey];
new Resolver(
this.nestedAppsyncStack,
Expand All @@ -418,11 +432,12 @@ export class AppSyncTransformer extends Construct {
return tableNameMap;
}

private createTable(tableData: CdkTransformerTable) {
private createTable(tableData: CdkTransformerTable, tableName?: string) {
// I do not want to force people to pass `TypeTable` - this way they are only passing the @model Type name
const modelTypeName = tableData.tableName.replace('Table', '');
const streamSpecification = this.props.dynamoDbStreamConfig && this.props.dynamoDbStreamConfig[modelTypeName];
const tableProps: TableProps = {
tableName,
billingMode: BillingMode.PAY_PER_REQUEST,
partitionKey: {
name: tableData.partitionKey.name,
Expand All @@ -444,7 +459,7 @@ export class AppSyncTransformer extends Construct {
tableProps,
);

tableData.localSecondaryIndexes.forEach((lsi: any) => {
tableData.localSecondaryIndexes.forEach((lsi) => {
table.addLocalSecondaryIndex({
indexName: lsi.indexName,
sortKey: {
Expand All @@ -457,7 +472,7 @@ export class AppSyncTransformer extends Construct {
});
});

tableData.globalSecondaryIndexes.forEach((gsi: any) => {
tableData.globalSecondaryIndexes.forEach((gsi) => {
table.addGlobalSecondaryIndex({
indexName: gsi.indexName,
partitionKey: {
Expand Down
43 changes: 43 additions & 0 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1090,4 +1090,47 @@ test('Grant Access To Public/Private Fields', () => {
},
],
});
});

test('Custom Table Names Are Applied', () => {
const mockApp = new App();
const stack = new Stack(mockApp, 'custom-table-names-stack');

const customerTableName = 'CustomerTableCustomName';
const orderTableName = 'OrderTableCustomName';

const appSyncTransformer = new AppSyncTransformer(stack, 'test-transformer', {
schemaPath: testSchemaPath,
apiName: 'custom-table-names-api',
tableNames: {
CustomerTable: customerTableName,
OrderTable: orderTableName,
},
});

const tableData = appSyncTransformer.outputs.cdkTables;
if (!tableData) throw new Error('Expected table data');

// Make sure custom name was applied to CustomerTable
const customerTable = appSyncTransformer.nestedAppsyncStack.node.findChild('CustomerTable');
expect(customerTable).toBeTruthy();

expect(appSyncTransformer.nestedAppsyncStack).toHaveResource('AWS::DynamoDB::Table', {
TableName: customerTableName,
KeySchema: [
{ AttributeName: 'id', KeyType: 'HASH' },
],
});

// Make sure custom name was applied to OrderTable
const orderTable = appSyncTransformer.nestedAppsyncStack.node.findChild('OrderTable');
expect(orderTable).toBeTruthy();

expect(appSyncTransformer.nestedAppsyncStack).toHaveResource('AWS::DynamoDB::Table', {
TableName: orderTableName,
KeySchema: [
{ AttributeName: 'id', KeyType: 'HASH' },
{ AttributeName: 'productID', KeyType: 'RANGE' },
],
});
});

0 comments on commit 02f6041

Please sign in to comment.