Skip to content

Commit

Permalink
feat(amplify-appsync-simulato): add TransactWriteItems support to Dyn…
Browse files Browse the repository at this point in the history
…amoDB operations

Add TransactWriteItems support to amplify-appsync-simulator

implemenst #5504
  • Loading branch information
Fernando Chucre committed Oct 13, 2020
1 parent 763f4af commit 520ebda
Show file tree
Hide file tree
Showing 2 changed files with 247 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {
ConditionExpression,
ExpressionAttributeNameMap,
ExpressionAttributeValueMap,
Key,
PutItemInputAttributeMap,
UpdateExpression,
} from 'aws-sdk/clients/dynamodb';

export interface AppSyncPutItemTransactionWriteItems {
table: string;
operation: 'PutItem';
key: Key;
attributeValues: PutItemInputAttributeMap;
condition?: {
expression: ConditionExpression;
expressionNames?: ExpressionAttributeNameMap;
expressionValues?: ExpressionAttributeValueMap;
returnValuesOnConditionCheckFailure?: true | false;
};
}

export interface AppSyncUpdateItemTransactionWriteItems {
table: string;
operation: 'UpdateItem';
key: Key;
update: {
expression: string;
expressionNames?: ExpressionAttributeNameMap;
expressionValues?: ExpressionAttributeValueMap;
};
condition?: {
expression: ConditionExpression;
expressionNames?: ExpressionAttributeNameMap;
expressionValues?: ExpressionAttributeValueMap;
returnValuesOnConditionCheckFailure?: true | false;
};
}

export interface AppSyncDeleteItemTransactionWriteItems {
table: 'table3';
operation: 'DeleteItem';
key: Key;
condition?: {
expression: ConditionExpression;
expressionNames?: ExpressionAttributeNameMap;
expressionValues?: ExpressionAttributeValueMap;
returnValuesOnConditionCheckFailure?: true | false;
};
}

export interface AppSyncConditionCheckTransactionWriteItems {
table: 'table4';
operation: 'ConditionCheck';
key: Key;
condition: {
expression: ConditionExpression;
expressionNames?: ExpressionAttributeNameMap;
expressionValues?: ExpressionAttributeValueMap;
returnValuesOnConditionCheckFailure?: true | false;
};
}

export type AppSyncTransactionWriteItem =
| AppSyncPutItemTransactionWriteItems
| AppSyncUpdateItemTransactionWriteItems
| AppSyncDeleteItemTransactionWriteItems
| AppSyncConditionCheckTransactionWriteItems;
export type AppSyncTransactionWriteItems = AppSyncTransactionWriteItem[];

export interface AppSyncTransactionWriteItemsOperation {
version: '2018-05-29';
operation: 'TransactWriteItems';
transactItems: AppSyncTransactionWriteItems;
}

export interface AppSyncTransactionWriteItemsCancellationReasons {
item?: PutItemInputAttributeMap;
type: string;
message: string;
}

export interface AppSyncTransactionWriteItemsOperationResponse {
keys: Key[] | null;
cancellationReasons: AppSyncTransactionWriteItemsCancellationReasons[] | null;
}
161 changes: 161 additions & 0 deletions packages/amplify-appsync-simulator/src/data-loader/dynamo-db/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { DynamoDB } from 'aws-sdk';
import { unmarshall, nullIfEmpty } from './utils';
import { AmplifyAppSyncSimulatorDataLoader } from '..';
import { ExpressionAttributeValueMap, TransactWriteItem } from 'aws-sdk/clients/dynamodb';
import {
AppSyncConditionCheckTransactionWriteItems,
AppSyncDeleteItemTransactionWriteItems,
AppSyncPutItemTransactionWriteItems,
AppSyncTransactionWriteItem,
AppSyncTransactionWriteItemsOperation,
AppSyncTransactionWriteItemsOperationResponse,
AppSyncUpdateItemTransactionWriteItems,
} from './AppSyncTransactionWriteItems';

type DynamoDBConnectionConfig = {
endpoint: string;
Expand Down Expand Up @@ -41,6 +51,8 @@ export class DynamoDBDataLoader implements AmplifyAppSyncSimulatorDataLoader {
return await this.query(payload);
case 'Scan':
return await this.scan(payload);
case 'TransactWriteItems':
return await this.transactWriteItems(payload);

case 'BatchGetItem':
case 'BatchPutItem':
Expand Down Expand Up @@ -180,6 +192,155 @@ export class DynamoDBDataLoader implements AmplifyAppSyncSimulatorDataLoader {

return unmarshall(deleted);
}

private transformExpressionAttributeValues(
expressions: ExpressionAttributeValueMap | null | undefined,
): ExpressionAttributeValueMap | null | undefined {
if (typeof expressions !== 'object') {
return expressions;
}
const attributes: ExpressionAttributeValueMap[] = [];
attributes.push(expressions);

while (attributes.length > 0) {
const attr = attributes.shift();
Object.keys(attr).forEach(key => {
const value = attr[key];
if (value.M) {
attributes.push(value.M);
}
if (value.N) {
value.N = `${value.N}`;
}
if (value.NS) {
value.NS = value.NS.map(item => `${item}`);
}
});
}
return expressions;
}

private transformPutTransationWriteItem(transactItem: AppSyncPutItemTransactionWriteItems): TransactWriteItem {
return {
Put: {
TableName: transactItem.table,
Item: transactItem.attributeValues,
ConditionExpression: transactItem.condition?.expression,
ExpressionAttributeNames: transactItem.condition?.expressionNames,
ExpressionAttributeValues: this.transformExpressionAttributeValues(transactItem.condition?.expressionValues),
ReturnValuesOnConditionCheckFailure: transactItem.condition?.returnValuesOnConditionCheckFailure ? 'ALL_OLD' : 'NONE',
},
};
}

private transformUpdateTransationWriteItem(transactItem: AppSyncUpdateItemTransactionWriteItems): TransactWriteItem {
return {
Update: {
Key: transactItem.key,
TableName: transactItem.table,
UpdateExpression: transactItem.update.expression,
ConditionExpression: transactItem.condition?.expression,
ExpressionAttributeNames:
transactItem.update.expressionNames || transactItem.condition?.expressionNames
? {
...(transactItem.update.expressionNames || {}),
...(transactItem.condition.expressionNames || {}),
}
: undefined,
ExpressionAttributeValues:
transactItem.update.expressionValues || transactItem.condition?.expressionValues
? this.transformExpressionAttributeValues({
...(transactItem.update.expressionValues || {}),
...(transactItem.condition.expressionValues || {}),
})
: undefined,
ReturnValuesOnConditionCheckFailure: transactItem.condition?.returnValuesOnConditionCheckFailure ? 'ALL_OLD' : 'NONE',
},
};
}

private transformDeleteTransationWriteItem(transactItem: AppSyncDeleteItemTransactionWriteItems): TransactWriteItem {
return {
Delete: {
Key: transactItem.key,
TableName: transactItem.table,
ConditionExpression: transactItem.condition?.expression,
ExpressionAttributeNames: transactItem.condition?.expressionNames,
ExpressionAttributeValues: this.transformExpressionAttributeValues(transactItem.condition?.expressionValues),
ReturnValuesOnConditionCheckFailure: transactItem.condition?.returnValuesOnConditionCheckFailure ? 'ALL_OLD' : 'NONE',
},
};
}

private transformCheckConditionTransationWriteItem(transactItem: AppSyncConditionCheckTransactionWriteItems): TransactWriteItem {
return {
ConditionCheck: {
Key: transactItem.key,
TableName: transactItem.table,
ConditionExpression: transactItem.condition?.expression,
ExpressionAttributeNames: transactItem.condition?.expressionNames,
ExpressionAttributeValues: this.transformExpressionAttributeValues(transactItem.condition?.expressionValues),
ReturnValuesOnConditionCheckFailure: transactItem.condition?.returnValuesOnConditionCheckFailure ? 'ALL_OLD' : 'NONE',
},
};
}

private async transactWriteItems({
transactItems,
}: AppSyncTransactionWriteItemsOperation): Promise<AppSyncTransactionWriteItemsOperationResponse> {
const keys = transactItems.map(({ key }) => key);

const transactionMap = (transactItem: AppSyncTransactionWriteItem): TransactWriteItem => {
switch (transactItem.operation) {
case 'PutItem':
return this.transformPutTransationWriteItem(transactItem);
case 'UpdateItem':
return this.transformUpdateTransationWriteItem(transactItem);
case 'DeleteItem':
return this.transformDeleteTransationWriteItem(transactItem);
case 'ConditionCheck':
return this.transformCheckConditionTransationWriteItem(transactItem);

default:
return transactItem;
}
};

const transactionItemsMapped = transactItems.map(transactionMap);

const request = this.client.transactWriteItems({
TransactItems: transactionItemsMapped,
});

return new Promise((resolve, reject) => {
request.on('extractError', resp => {
try {
const errors = JSON.parse(resp.httpResponse.body.toString());
resolve({
keys: null,
cancellationReasons: errors.CancellationReasons,
});
} catch (e) {
reject(resp.httpResponse.body.toString());
}
});

request.on('extractData', response => {
resolve({
keys,
cancellationReasons: null,
});
});

request.on('error', error => {
console.log('[on error]', error);
reject(error);
});

request.send();
});
}

private async scan(payload) {
const { filter, index, limit, consistentRead = false, nextToken, select, totalSegments, segment } = payload;

Expand Down

0 comments on commit 520ebda

Please sign in to comment.