@amazon-dax-sdk/client-dax
Version:
Amazon DAX Client for JavaScript
966 lines (863 loc) • 38.9 kB
JavaScript
/*
* Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
* use this file except in compliance with the License. A copy of the License
* is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
;
const DaxClientError = require('./DaxClientError');
const DaxErrorCode = require('./DaxErrorCode');
const StreamBuffer = require('./ByteStreamBuffer');
const CborEncoder = require('./CborEncoder');
const Encoder = require('./Encoders');
const AttributeValueEncoder = require('./AttributeValueEncoder');
const Constants = require('./Constants');
const Util = require('./Util');
const DaxServiceError = require('./DaxServiceError');
const Tube = require('./Tube');
const DaxMethodIds = require('./Constants').DaxMethodIds;
const Operation = require('./Constants').Operation;
const ReturnValueOnConditionCheckFailure = require('./Constants').ReturnValueOnConditionCheckFailure;
const RequestValidator = require('./RequestValidator');
const UUID = require('uuid');
const MAX_WRITE_BATCH_SIZE = 25;
const MAX_READ_BATCH_SIZE = 100;
const BATCH_WRITE_MAX_ITEM_SIZE = 409600;
module.exports = class BaseOperations {
constructor(keyCache, attrListCache, attrListIdCache, tubePool, requestTimeout) {
this.tubePool = tubePool;
this.keyCache = keyCache;
this.attrListCache = attrListCache;
this.attrListIdCache = attrListIdCache;
this.caches = {
attrListCache: this.attrListCache,
attrListIdCache: this.attrListIdCache,
keyCache: this.keyCache,
};
this._requestTimeout = requestTimeout || 0;
}
_getReturnHandler(tube, assembler, name) {
let endListener;
return new Promise((resolve, reject) => {
// Listen on end event to avoid the situation that server close
// the connection but client is still waiting for an response
// till timeout.
endListener = () => {
return reject(new DaxClientError('Connection is closed by server', DaxErrorCode.Connection, true));
};
tube.socket.on('end', endListener);
tube.socket.on('data', (data) => {
let result;
try {
// Pass data to the assembler
result = assembler.feed(data);
} catch(err) {
// Catch & reject any errors, including those returned from the server
if(err._tubeInvalid) {
tube.invalidateAuth();
}
if(!(err instanceof DaxServiceError)) {
// On non-DAX errors, reset the pool
this.tubePool.reset(tube);
} else {
// On DAX errors, the tube is still usable
this.tubePool.recycle(tube);
if(err.cancellationReasons) { // For TransactionCanceledException, we also need to deanonymize items.
let deanonymizePromiseList = [];
for(let cancellationReason of err.cancellationReasons) {
if(cancellationReason.Item) {
deanonymizePromiseList.push(
BaseOperations._resolveItemAttributeValues(this.attrListCache, cancellationReason.Item)
);
}
}
return Promise.all(deanonymizePromiseList).then(() => {
return reject(err);
});
}
}
return reject(err);
}
if(result) {
// If the response is complete, resolve immediately
return resolve(result);
}
// Otherwise, the assembler needs more data, so wait for it
});
tube.socket.on('error', (err) => {
// On socket errors, reset the pool
this.tubePool.reset(tube);
return reject(new DaxClientError(err.message, DaxErrorCode.Connection, true));
});
let timeout = this._requestTimeout; // capture the timeout in case it changes
tube.setTimeout(timeout, () => {
// Either the network is down or the node is stuck. Either way, the pool can be reset.
this.tubePool.reset(tube);
return reject(new Tube.TimeoutError(timeout));
});
}).then((result) => {
// Remove end listener
if(endListener) {
tube.socket.removeListener('end', endListener);
}
this.tubePool.recycle(tube);
return this._resolveAttributeValues(result);
}).catch((err) => {
// Remove end listener
if(endListener) {
tube.socket.removeListener('end', endListener);
}
throw err;
});
}
reauth(tube) {
return tube.reauth().catch((err) => {
// need to reset connection to prevent leak.
// https://issues.amazon.com/issues/DAX-3535
this.tubePool.reset(tube);
throw err;
});
}
_resolveAttributeValues(response) {
let deanonymizePromiseList = [];
// getItem
if(response.Item) {
deanonymizePromiseList.push(
BaseOperations._resolveItemAttributeValues(this.attrListCache, response.Item)
);
}
// deleteItem, putItem
if(response.Attributes) {
deanonymizePromiseList.push(
BaseOperations._resolveItemAttributeValues(this.attrListCache, response.Attributes)
);
}
// query, scan
if(response.Items) {
for(let item of response.Items) {
deanonymizePromiseList.push(
BaseOperations._resolveItemAttributeValues(this.attrListCache, item)
);
}
}
// batchGetItem and transactGetItems
if(response.Responses) {
if(Array.isArray(response.Responses)) {
// transactGetItems (list of items)
for(let item of response.Responses) {
deanonymizePromiseList.push(
BaseOperations._resolveItemAttributeValues(this.attrListCache, item.Item));
}
} else {
// batchGetItem (map of table names to items)
for(let tableName in response.Responses) {
if(response.Responses.hasOwnProperty(tableName)) {
let tableResults = response.Responses[tableName];
for(let item of tableResults) {
deanonymizePromiseList.push(
BaseOperations._resolveItemAttributeValues(this.attrListCache, item)
);
}
}
}
}
}
// batchWriteItem
if(response.UnprocessedItems) {
for(let tableName in response.UnprocessedItems) {
if(response.UnprocessedItems.hasOwnProperty(tableName)) {
let unprocessedRequests = response.UnprocessedItems[tableName];
for(let unprocessedRequest of unprocessedRequests) {
for(let i = 0; i < unprocessedRequest.length; i++) {
let writeType = unprocessedRequest[i][0];
let writeRequest = unprocessedRequest[i][0];
if(writeType === 'PutRequest' && writeRequest.Item) {
deanonymizePromiseList.push(
BaseOperations._resolveItemAttributeValues(this.attrListCache, writeRequest.Item)
);
}
}
}
}
}
}
return Promise.all(deanonymizePromiseList).then(() => response);
}
static _resolveItemAttributeValues(attrListCache, item) {
if(item && item._attrListId !== undefined) {
return attrListCache.get(item._attrListId).then((attrNames) => {
Util.deanonymizeAttributeValues(item, attrNames);
});
} else {
return Promise.resolve(item);
}
}
_validateBatchGetItem(request) {
if(!request.RequestItems) {
throw new DaxClientError('1 validation error detected: Value ' +
JSON.stringify(request.RequestItems) +
' at "requestItems" failed to satisfy constraint: Member must have length greater than or equal to 1',
DaxErrorCode.Validation, false);
}
let requestByTable = request.RequestItems;
let batchSize = 0;
let isEmpty = true;
Object.keys(requestByTable).forEach((tableName) => {
let kaas = requestByTable[tableName];
if(!kaas) {
throw new DaxClientError('Request can not be null for table ' + tableName, DaxErrorCode.Validation, false);
}
if(!Object.keys(kaas).length) {
throw new DaxClientError('Keys can not be null for table ' + tableName, DaxErrorCode.Validation, false);
}
batchSize += Object.keys(kaas).length;
if(batchSize > MAX_READ_BATCH_SIZE) {
throw new DaxClientError('Batch size should be less than ' + MAX_READ_BATCH_SIZE, DaxErrorCode.Validation, false);
}
isEmpty = false;
});
if(isEmpty) {
throw new DaxClientError(
'1 validation error detected: Value null at "requestItems" failed to satisfy constraint: Member must not be null',
DaxErrorCode.Validation, false);
}
}
prepare_batchGetItem_N697851100_1(request) {
this._validateBatchGetItem(request);
let stubData = {};
let requestByTable = request.RequestItems;
let keysPerTable = {};
let tableProjOrdinals = {};
let buffer = new StreamBuffer();
let encoder = new CborEncoder();
buffer.write(encoder.encodeMapHeader(Object.keys(requestByTable).length));
let fetchKeySchema = [];
Object.keys(requestByTable).forEach((tableName) => {
fetchKeySchema.push(this.keyCache.get(tableName).then((keySchema) => {
keysPerTable[tableName] = keySchema;
}));
});
return Promise.all(fetchKeySchema).then(() => {
let keySet = new Set();
Object.keys(requestByTable).forEach((tableName) => {
keySet.clear();
let kaas = requestByTable[tableName];
buffer.write(encoder.encodeString(tableName));
buffer.write(encoder.encodeArrayHeader(3));
if(!kaas.ConsistentRead) {
buffer.write(encoder.encodeBoolean(false));
} else {
buffer.write(encoder.encodeBoolean(true));
}
tableProjOrdinals[tableName] = [];
if(kaas.ProjectionExpression) {
buffer.write(encoder.encodeBinary(Encoder._encodeProjection(kaas.ProjectionExpression, kaas.ExpressionAttributeNames)));
Encoder._prepareProjection(kaas.ProjectionExpression, kaas.ExpressionAttributeNames, tableProjOrdinals[tableName]);
} else {
buffer.write(encoder.encodeNull());
}
let keys = (kaas.Keys ? kaas.Keys : []);
buffer.write(encoder.encodeArrayHeader(keys.length));
for(let key of keys) {
let keyBytes = Encoder.encodeKey(key, keysPerTable[tableName], encoder);
if(keySet.has(keyBytes)) {
throw new DaxClientError('Provided list of item keys contains duplicates', DaxErrorCode.Validation, false);
}
keySet.add(keyBytes);
buffer.write(keyBytes);
}
});
stubData.getItemKeys = buffer.read();
let hasKwargs = request.ReturnConsumedCapacity;
if(hasKwargs) {
buffer.write(encoder.encodeMapStreamHeader());
if(request.ReturnConsumedCapacity) {
buffer.write(encoder.encodeInt(Constants.DaxDataRequestParam.ReturnConsumedCapacity));
buffer.write(encoder.encodeInt(Constants.ReturnConsumedCapacityValues[request.ReturnConsumedCapacity]));
}
buffer.write(encoder.encodeStreamBreak());
} else {
buffer.write(encoder.encodeNull());
}
stubData.kwargs = buffer.read();
// attach data neccessary for decoding to request
request._keysPerTable = keysPerTable;
request._tableProjOrdinals = tableProjOrdinals;
request._attrListCache = this.attrListCache; // need cache for assemble, it's more efficient during that phase
request._stubData = stubData;
return stubData;
});
}
write_batchGetItem_N697851100_1(data, tube) {
tube.write(tube.cbor.encodeInt(1));
tube.write(tube.cbor.encodeInt(Constants.DaxMethodIds.batchGetItem));
tube.write(data.getItemKeys ? data.getItemKeys : tube.cbor.encodeNull());
tube.write(data.kwargs ? data.kwargs : tube.cbor.encodeNull());
tube.flush();
}
prepare_batchWriteItem_116217951_1(request) {
let stubData = {};
let keysPerTable = {};
let attrListIdPerTable = {};
let requestByTable = request.RequestItems;
let buffer = new StreamBuffer();
let encoder = new CborEncoder();
let totalRequests = 0;
if(!requestByTable) {
throw new DaxClientError('1 validation error detected: Value ' +
JSON.stringify(request.RequestItems) +
' at "requestItems" failed to satisfy constraint: Member must have length greater than or equal to 1',
DaxErrorCode.Validation, false);
}
let fetchKeySchema = [];
Object.keys(requestByTable).forEach((tableName) => {
fetchKeySchema.push(this.keyCache.get(tableName).then((keySchema) => {
keysPerTable[tableName] = keySchema;
}));
});
let fetchAttributeSchema = [];
buffer.write(encoder.encodeMapHeader(Object.keys(requestByTable).length));
return Promise.all(fetchKeySchema).then(() => {
Object.keys(requestByTable).forEach((tableName) => {
attrListIdPerTable[tableName] = [];
for(let i = 0; i < requestByTable[tableName].length; ++i) {
if(requestByTable[tableName][i].PutRequest) {
let attrNames = AttributeValueEncoder.getCanonicalAttributeList(requestByTable[tableName][i].PutRequest.Item, keysPerTable[tableName]);
fetchAttributeSchema.push(this.attrListIdCache.get(attrNames).then((attrListId) => {
attrListIdPerTable[tableName][i] = attrListId;
}));
}
}
});
return Promise.all(fetchAttributeSchema);
}).then(() => {
let keySet = new Set();
Object.keys(requestByTable).forEach((tableName) => {
keySet.clear();
if(!tableName) {
throw new DaxClientError('Value null at "tableName" failed to satisfy constraint: Member must not be null', DaxErrorCode.Validation, false);
}
let writeRequests = requestByTable[tableName];
if((totalRequests += writeRequests.length) > MAX_WRITE_BATCH_SIZE) {
throw new DaxClientError(`Batch size should be less than ${MAX_WRITE_BATCH_SIZE}`, DaxErrorCode.Validation, false);
}
buffer.write(encoder.encodeString(tableName));
let requestItemCount = 0;
let isEmpty = true;
for(let writeRequest of writeRequests) {
if(writeRequest.PutRequest || writeRequest.DeleteRequest) {
requestItemCount++;
}
}
buffer.write(encoder.encodeArrayHeader(requestItemCount * 2));
for(let i = 0; i < writeRequests.length; ++i) {
let writeRequest = writeRequests[i];
if(!writeRequest) {
continue;
}
isEmpty = false;
this._validateWriteRequest(writeRequest);
if(writeRequest.PutRequest) {
let attributes = writeRequest.PutRequest.Item;
this._validateBatchWriteItem(attributes);
let keyBytes = Encoder.encodeKey(attributes, keysPerTable[tableName], encoder);
if(keySet.has(keyBytes)) {
throw new DaxClientError('Provided list of item keys contains duplicates', DaxErrorCode.Validation, false);
}
keySet.add(keyBytes);
buffer.write(keyBytes);
buffer.write(Encoder.encodeValuesWithKeys(attributes, keysPerTable[tableName], attrListIdPerTable[tableName][i], encoder));
} else if(writeRequest.DeleteRequest) {
let key = writeRequest.DeleteRequest.Key;
let keyBytes = Encoder.encodeKey(key, keysPerTable[tableName], encoder);
if(keySet.has(keyBytes)) {
throw new DaxClientError('Provided list of item keys contains duplicates', DaxErrorCode.Validation, false);
}
keySet.add(keyBytes);
buffer.write(keyBytes);
buffer.write(encoder.encodeNull());
}
}
if(isEmpty) {
throw new DaxClientError(`1 validation error detected: Value '{ ${tableName} =`,
JSON.stringify(writeRequests),
`}' at 'requestItems' failed to satisfy constraint: Map value must satisfy constraint:`,
`[Member must have length less than or equal to 25, Member must have length greater than or equal to 1`,
DaxErrorCode.Validation, false);
}
});
if(totalRequests === 0) {
throw new DaxClientError(`1 validation error detected: Value`,
JSON.stringify(requestByTable),
`at "requestItems" failed to satisfy constaint: Member must have length greater than or equal to 1`,
DaxErrorCode.Validation, false);
}
stubData.keyValuesByTable = buffer.read();
buffer.write(encoder.encodeMapStreamHeader());
if(request.ReturnConsumedCapacity && request.ReturnConsumedCapacity !== 'NONE') {
buffer.write(encoder.encodeInt(Constants.DaxDataRequestParam.ReturnConsumedCapacity));
buffer.write(encoder.encodeInt(Constants.ReturnConsumedCapacityValues[request.ReturnConsumedCapacity]));
}
if(request.ReturnItemCollectionMetrics && request.ReturnItemCollectionMetrics !== 'NONE') {
buffer.write(encoder.encodeInt(Constants.DaxDataRequestParam.ReturnItemCollectionMetrics));
buffer.write(encoder.encodeInt(Constants.ReturnItemCollectionMetricsValue[request.ReturnItemCollectionMetrics]));
}
buffer.write(encoder.encodeStreamBreak());
stubData.kwargs = buffer.read();
request._keysPerTable = keysPerTable;
request._attrListCache = this.attrListCache; // need cache for assemble, it's more efficient during that phase
request._stubData = stubData;
return stubData;
});
}
write_batchWriteItem_116217951_1(data, tube) {
tube.write(tube.cbor.encodeInt(1));
tube.write(tube.cbor.encodeInt(Constants.DaxMethodIds.batchWriteItem));
tube.write(data.keyValuesByTable ? data.keyValuesByTable : tube.cbor.encodeNull());
tube.write(data.kwargs ? data.kwargs : tube.cbor.encodeNull());
tube.flush();
}
prepare_transactWriteItems_N1160037738_1(request) {
if(request.TransactItems == null) {
throw RequestValidator.newValidationException(`1 validation error detected: Value ${JSON.stringify(request.TransactItems)} at 'transactItems' failed to satisfy constraint: Member must not be null`);
} else if(request.TransactItems.length < 1) {
throw RequestValidator.newValidationException(`1 validation error detected: Value '${request.TransactItems}' at 'transactItems' failed to satisfy constraint: Member must have length greater than or equal to 1`);
}
let keySetPerTable = {};
let keysPerTable = {};
let keysPerRequest = [];
let tableNamesBuffer = new StreamBuffer();
let keysBuffer = new StreamBuffer();
let valuesBuffer = new StreamBuffer();
let conditionExprsBuffer = new StreamBuffer();
let updateExprsBuffer = new StreamBuffer();
// Use small buffer for following parameters. Each only occupy tens of bytes.
let operationsBuffer = new StreamBuffer(64);
let rvOnConditionCheckFailuresBuffer = new StreamBuffer(64);
let encoder = new CborEncoder();
operationsBuffer.write(encoder.encodeArrayHeader(request.TransactItems.length));
tableNamesBuffer.write(encoder.encodeArrayHeader(request.TransactItems.length));
keysBuffer.write(encoder.encodeArrayHeader(request.TransactItems.length));
valuesBuffer.write(encoder.encodeArrayHeader(request.TransactItems.length));
conditionExprsBuffer.write(encoder.encodeArrayHeader(request.TransactItems.length));
updateExprsBuffer.write(encoder.encodeArrayHeader(request.TransactItems.length));
rvOnConditionCheckFailuresBuffer.write(encoder.encodeArrayHeader(request.TransactItems.length));
let promiseChain = Promise.resolve();
request.TransactItems.forEach((transactWriteItem, i) => {
promiseChain = promiseChain.then(() => {
if(!transactWriteItem) {
throw RequestValidator.newValidationException(`1 validation error detected: Value '${JSON.stringify(request.TransactItems)}' at 'transactItems' failed to satisfy constraint: Member must not be null`);
}
let tableName;
let updateExpr;
let conditionExpr;
let rvOnConditionCheckFailure;
let operation;
let key;
let item;
let eAttrVal;
let eAttrName;
let opName;
let operations = 0;
if(transactWriteItem.ConditionCheck) {
operations++;
let check = transactWriteItem.ConditionCheck;
tableName = check.TableName;
operation = Operation.CHECK;
key = check.Key;
conditionExpr = check.ConditionExpression;
if(!conditionExpr) {
throw RequestValidator.newValidationException(
`Value ${JSON.stringify(check.ConditionExpression)} at 'transactItems.${i+1}.member.conditionCheck.conditionExpression' failed to satisfy constraint: Member must not be null`);
}
eAttrName = check.ExpressionAttributeNames;
eAttrVal = check.ExpressionAttributeValues;
rvOnConditionCheckFailure = check.ReturnValuesOnConditionCheckFailure;
opName = 'conditionCheck';
}
if(transactWriteItem.Put) {
operations++;
let put = transactWriteItem.Put;
tableName = put.TableName;
operation = Operation.PUT;
item = put.Item;
conditionExpr = put.ConditionExpression;
eAttrName = put.ExpressionAttributeNames;
eAttrVal = put.ExpressionAttributeValues;
rvOnConditionCheckFailure = put.ReturnValuesOnConditionCheckFailure;
opName = 'put';
}
if(transactWriteItem.Delete) {
operations++;
// delete is the reserved word for JS.
let deleteOp = transactWriteItem.Delete;
tableName = deleteOp.TableName;
operation = Operation.DELETE;
key = deleteOp.Key;
conditionExpr = deleteOp.ConditionExpression;
eAttrName = deleteOp.ExpressionAttributeNames;
eAttrVal = deleteOp.ExpressionAttributeValues;
rvOnConditionCheckFailure = deleteOp.ReturnValuesOnConditionCheckFailure;
opName = 'delete';
}
if(transactWriteItem.Update) {
operations++;
let update = transactWriteItem.Update;
tableName = update.TableName;
operation = Operation.PARTIAL_UPDATE;
key = update.Key;
conditionExpr = update.ConditionExpression;
updateExpr = update.UpdateExpression;
if(!updateExpr) {
throw RequestValidator.newValidationException(
`1 validation error detected: Value null at 'transactItems.${i+1}.member.update.updateExpression' failed to satisfy constraint: Member must not be null`);
}
eAttrName = update.ExpressionAttributeNames;
eAttrVal = update.ExpressionAttributeValues;
rvOnConditionCheckFailure = update.ReturnValuesOnConditionCheckFailure;
opName = 'update';
}
if(operations > 1) {
throw RequestValidator.newValidationException('TransactItems can only contain one of ConditionCheck, Put, Update or Delete');
}
if(operations === 0) {
throw RequestValidator.newValidationException('Invalid Request: TransactWriteRequest should contain Delete or Put or Update request');
}
RequestValidator.validateTableName(tableName, `transactItems.${i+1}.member.${opName}.tableName`);
RequestValidator.validateTransactItem(opName === 'put' ? item : key, `transactItems.${i+1}.member.${opName}.${opName === 'put' ? 'item' : 'key'}`);
RequestValidator.validateExpression(conditionExpr,
updateExpr, // UpdateExpression
null, // ProjectionExpression
null, // FilterExpression
null, // key condition expression
null,
null,
null, // AttributeUpdates
null, // AttributesToGet
null, // query filter
null, // scan filter
null, // Key Condition
eAttrName,
eAttrVal);
return this.keyCache.get(tableName).then((keySchema) => {
keysPerTable[tableName] = keySchema;
keysPerRequest.push(Util.extractKey(key ? key : item, keySchema));
if(key) {
RequestValidator.validateKey(key, keySchema);
}
let keyBytes = Encoder.encodeKey(key ? key : item, keySchema, encoder);
let keySet = keySetPerTable[tableName];
if(!keySet) {
keySet = {};
keySetPerTable[tableName] = keySet;
}
if(keySet[keyBytes]) {
throw new DaxClientError('Provided list of item keys contains duplicates', DaxErrorCode.Validation, false);
}
// This will convert buffer to string first. Set will compare Buffer reference instead of actual value.
keySet[keyBytes] = true;
keysBuffer.write(keyBytes);
if(item) {
let attrNames = AttributeValueEncoder.getCanonicalAttributeList(item, keySchema);
return this.attrListIdCache.get(attrNames).then((attrListId) => {
valuesBuffer.write(Encoder.encodeValuesWithKeys(item, keySchema, attrListId, encoder));
});
} else {
valuesBuffer.write(encoder.encodeNull());
}
}).then(() => {
operationsBuffer.write(encoder.encodeInt(operation));
tableNamesBuffer.write(encoder.encodeBinary(tableName));
if(rvOnConditionCheckFailure) {
rvOnConditionCheckFailuresBuffer.write(encoder.encodeInt(ReturnValueOnConditionCheckFailure[rvOnConditionCheckFailure]));
} else {
rvOnConditionCheckFailuresBuffer.write(encoder.encodeInt(ReturnValueOnConditionCheckFailure.NONE));
}
let encodedExprs = Encoder.encodeExpressions({
ConditionExpression: conditionExpr,
UpdateExpression: updateExpr,
ExpressionAttributeNames: eAttrName,
ExpressionAttributeValues: eAttrVal,
});
if(encodedExprs.Condition) {
conditionExprsBuffer.write(encoder.encodeBinary(encodedExprs.Condition));
} else {
conditionExprsBuffer.write(encoder.encodeNull());
}
if(encodedExprs.Update) {
updateExprsBuffer.write(encoder.encodeBinary(encodedExprs.Update));
} else {
updateExprsBuffer.write(encoder.encodeNull());
}
});
});
});
return promiseChain.then(() => {
request._keysPerTable = keysPerTable;
request._keysPerRequest = keysPerRequest;
if(!request.ClientRequestToken) {
request.ClientRequestToken = UUID.v4();
}
request._stubData = {
operations: operationsBuffer.read(),
tableNames: tableNamesBuffer.read(),
keys: keysBuffer.read(),
values: valuesBuffer.read(),
returnValues: null,
returnValuesOnConditionCheckFailure: rvOnConditionCheckFailuresBuffer.read(),
conditionExpressions: conditionExprsBuffer.read(),
updateExpressions: updateExprsBuffer.read(),
kwargs: Encoder.encodeExpressionAndKwargs({
ReturnConsumedCapacity: request.ReturnConsumedCapacity,
ReturnItemCollectionMetrics: request.ReturnItemCollectionMetrics,
ClientRequestToken: request.ClientRequestToken,
}, encoder).kwargs,
};
});
}
write_transactWriteItems_N1160037738_1(data, tube) {
tube.write(tube.cbor.encodeInt(1));
tube.write(tube.cbor.encodeInt(DaxMethodIds.transactWriteItems));
tube.write(data.operations);
tube.write(data.tableNames);
tube.write(data.keys);
tube.write(data.values ? data.values : tube.cbor.encodeNull());
tube.write(data.returnValues ? data.returnValues : tube.cbor.encodeNull());
tube.write(data.returnValuesOnConditionCheckFailure ? data.returnValuesOnConditionCheckFailure : tube.cbor.encodeNull());
tube.write(data.conditionExpressions ? data.conditionExpressions : tube.cbor.encodeNull());
tube.write(data.updateExpressions ? data.updateExpressions : tube.cbor.encodeNull());
tube.write(data.kwargs ? data.kwargs : tube.cbor.encodeNull());
tube.flush();
}
_validateWriteRequest(writeRequest) {
if(writeRequest.PutRequest && writeRequest.DeleteRequest) {
throw new DaxClientError('Both delete and put request cannot be set', DaxErrorCode.Validation, false);
}
if(!writeRequest.PutRequest && !writeRequest.DeleteRequest) {
throw new DaxClientError('Both delete and put request cannot be empty', DaxErrorCode.Validation, false);
}
}
_validateBatchWriteItem(attributes) {
if(!attributes || Object.keys(attributes) === 0) {
throw new DaxClientError(
`1 validation error detected. Value ${JSON.stringify(attributes)} at "item" failed to satisfy constraint: Item must not be null`,
DaxErrorCode.Validation, false);
}
Object.keys(attributes).forEach((name) => {
if(this._simpleAttrValLength(attributes[name]) > BATCH_WRITE_MAX_ITEM_SIZE) {
throw new DaxClientError('Item size has exceeded the maximum allowed size', DaxErrorCode.Validation, false);
}
});
}
/*
* Validate the TransactGetItems request.
*
* Attempt to match the codes and messages emitted by the DynamoDB JavaScript client:
*
* $ node
* > var AWS = require('aws-sdk')
* > AWS.config.update({region: 'us-west-2'})
* > ddb = new AWS.DynamoDB({apiVersion: '2012-10-08'})
* > ddb.transactGetItems({}, function(err, data) { if(err) { console.log('Error', err); } })
* ...
* Response {
* error:
* { MissingRequiredParameter: Missing required key 'TransactItems' in params
* ...
* message: 'Missing required key \'TransactItems\' in params',
* code: 'MissingRequiredParameter',
*/
_validateTransactGetItems(request) {
let validationErrors = [];
if(request.TransactItems == null) {
throw new DaxClientError(
'Missing required key \'TransactItems\' in params',
DaxErrorCode.Validation, false, undefined, 400);
}
if(request.TransactItems.length < 1) {
validationErrors.push(
'Value \'' + JSON.stringify(request.TransactItems)
+ '\' at \'transactItems\' failed to satisfy constraint: Member must have length greater than or equal to 1');
} else if(request.TransactItems.length > 25) {
validationErrors.push(
'Value \'' + JSON.stringify(request.TransactItems)
+ '\' at \'transactItems\' failed to satisfy constraint: Member must have length less than or equal to 25');
}
for(const [i, item] of request.TransactItems.entries()) {
if(item === null || !('Get' in item)) {
throw new DaxClientError(
'Cannot read property \'Get\' of ' + JSON.stringify(item), DaxErrorCode.Validation, false, undefined, 400);
}
let get = item.Get;
if(get === null || !('Key' in get)) {
throw new DaxClientError(
'Cannot read property \'Key\' of ' + JSON.stringify(item), DaxErrorCode.Validation, false, undefined, 400);
}
if(!('TableName' in get) || get.TableName == null) {
throw new DaxClientError(
`Missing required key 'TableName' in params.TransactItems[${i}].Get`, DaxErrorCode.Validation, false, undefined, 400);
}
let tableName = get.TableName;
if(tableName.length < 3) {
validationErrors.push(`Value '${tableName}' at 'transactItems.${i + 1}.member.get.tableName' failed to satisfy constraint: `
+ 'Member must have length greater than or equal to 3');
}
if(!/[a-zA-Z0-9_.-]+/.test(tableName)) {
validationErrors.push(`Value '${tableName}' at 'transactItems.${i + 1}.member.get.tableName' failed to satisfy constraint: `
+ 'Member must satisfy regular expression pattern: [a-zA-Z0-9_.-]+');
}
let key = get.Key;
if(key === null) {
throw new DaxClientError(
`Missing required key 'Key' in params.TransactItems[${i}].Get`, DaxErrorCode.Validation, false, undefined, 400);
}
}
if(validationErrors.length > 0) {
throw new DaxClientError(
validationErrors.length + ' validation errors detected: ' + validationErrors.join('; '),
DaxErrorCode.Validation, false, undefined, 400);
}
}
/*
* Validate the keys against the table schemas. We've already validated
* the existence of the table names and keys in
* _validateTransactGetItems(), so it's safe to dereference them now.
*/
_validate_transactgetitems_keys(items, keySchemasForTables) {
let validationErrors = [];
for(const [i, item] of items.entries()) {
let schema = keySchemasForTables[item.TableName];
for(const attrSchema of schema) {
let attrName = attrSchema.AttributeName;
let attrSchemaType = attrSchema.AttributeType;
let keyAttr = item.Key[attrName];
if(!keyAttr) {
// Missing key attribute
validationErrors.push(`key attribute '${attrName}' is missing from params.TransactItems[${i}].Get.Key`);
} else if(!keyAttr.hasOwnProperty(attrSchemaType)) {
// Key attribute with wrong type
validationErrors.push(`key attribute '${attrName}' with value '${JSON.stringify(item.Key[attrName])}' `
+ 'does not match schema type \'${attrSchemaType}\'');
}
}
let keyAttrNames = Object.keys(item.Key);
if(schema.length != keyAttrNames.length) {
validationErrors.push(`key length does not match schema for table '${item.TableName}' in params.TransactItems[${i}].Get.Key`);
}
}
if(validationErrors.length > 0) {
throw new DaxClientError(
validationErrors.length + ' validation errors detected: ' + validationErrors.join('; '),
DaxErrorCode.Validation, false, undefined, 400);
}
}
prepare_transactGetItems_1866287579_1(request) {
let stubData = {};
let kwargsBuffer = new StreamBuffer();
let tableNameBuffer = new StreamBuffer();
let keysBuffer = new StreamBuffer();
let encoder = new CborEncoder();
this._validateTransactGetItems(request);
// Prepare the keyword arguments
let hasKwargs = request.ReturnConsumedCapacity;
if(hasKwargs) {
kwargsBuffer.write(encoder.encodeMapStreamHeader());
if(request.ReturnConsumedCapacity) {
kwargsBuffer.write(encoder.encodeInt(Constants.DaxDataRequestParam.ReturnConsumedCapacity));
kwargsBuffer.write(encoder.encodeInt(Constants.ReturnConsumedCapacityValues[request.ReturnConsumedCapacity]));
}
kwargsBuffer.write(encoder.encodeStreamBreak());
} else {
kwargsBuffer.write(encoder.encodeNull());
}
stubData.kwargs = kwargsBuffer.read();
let items = request.TransactItems.map((transactItem) => transactItem.Get);
tableNameBuffer.write(encoder.encodeArrayHeader(items.length));
keysBuffer.write(encoder.encodeArrayHeader(items.length));
// Prepare the table names
for(const item of items) {
let tableName = item.TableName;
tableNameBuffer.write(encoder.encodeBinary(tableName));
}
stubData.tableNames = tableNameBuffer.read();
// Validate the keys against the key schema.
// First, collect the table names.
let tableNames = items.reduce((tableNames, item) => {
tableNames.add(item.TableName);
return tableNames;
}, new Set());
// Next, build a promise of a map of table names to key schema.
let keySchemasForTablesPromise = Array.from(tableNames.values()).reduce(
(keySchemasForTables, tableName) => {
// We need to resolve both the table names to schemas dict, and the
// schema value from the key cache. Put both promises in an array
// to call Promise.all() on, then assign the schema to the dict.
return Promise.all([keySchemasForTables, this.keyCache.get(tableName)]).then((arr) => {
arr[0][tableName] = arr[1];
return arr[0];
});
},
Promise.resolve({})
);
return keySchemasForTablesPromise
.then((keySchemasForTables) => {
// Now we can validate the keys against their table schema
this._validate_transactgetitems_keys(items, keySchemasForTables);
// and write them
items.forEach((item) => keysBuffer.write(Encoder.encodeKey(item.Key, keySchemasForTables[item.TableName], encoder)));
// and save them in the stub data
stubData.keys = keysBuffer.read();
request._stubData = stubData;
// and write the projection expressions
let projectionExpressionsBuffer = new StreamBuffer();
projectionExpressionsBuffer.write(encoder.encodeArrayHeader(items.length));
for(const item of items) {
if('ProjectionExpression' in item) {
projectionExpressionsBuffer.write(encoder.encodeBinary(Encoder._encodeProjection(item.ProjectionExpression, item.ExpressionAttributeNames)));
item._projectionOrdinals = [];
Encoder._prepareProjection(item.ProjectionExpression, item.ExpressionAttributeNames, item._projectionOrdinals);
} else {
projectionExpressionsBuffer.write(encoder.encodeNull());
}
}
stubData.projectionExpressions = projectionExpressionsBuffer.read();
})
.then(() => stubData);
}
write_transactGetItems_1866287579_1(data, tube) {
tube.write(tube.cbor.encodeInt(1));
tube.write(tube.cbor.encodeInt(Constants.DaxMethodIds.transactGetItems));
tube.write(data.tableNames ? data.tableNames : tube.cbor.encodeNull());
tube.write(data.keys ? data.keys : tube.cbor.encodeNull());
tube.write(data.projectionExpressions ? data.projectionExpressions : tube.cbor.encodeNull());
tube.write(data.kwargs ? data.kwargs : tube.cbor.encodeNull());
tube.flush();
}
_simpleAttrValLength(v) {
if(!v) {
return 0;
} else if(v.S) {
return v.S.length;
} else if(v.B) {
return v.B.length;
} else if(v.N) {
return v.N.length;
} else if(v.BS) {
let size = 0;
for(let b of v.BS) {
size += b.length;
}
return size;
}
// Only the primitive types are expected
return 0;
}
};