diff --git a/actions/describeTimeToLive.js b/actions/describeTimeToLive.js index 3423986..37a7edb 100644 --- a/actions/describeTimeToLive.js +++ b/actions/describeTimeToLive.js @@ -5,7 +5,8 @@ module.exports = function describeTimeToLive (store, data, cb) { if (table.TimeToLiveDescription !== null && typeof table.TimeToLiveDescription === 'object') { cb(null, { TimeToLiveDescription: table.TimeToLiveDescription }) - } else { + } + else { cb(null, { TimeToLiveDescription: { TimeToLiveStatus: 'DISABLED' } }) } }) diff --git a/actions/updateTimeToLive.js b/actions/updateTimeToLive.js index d142eb5..5097a6d 100644 --- a/actions/updateTimeToLive.js +++ b/actions/updateTimeToLive.js @@ -1,38 +1,39 @@ -var db = require('../db'); +var db = require('../db') -module.exports = function updateTimeToLive(store, data, cb) { - var key = data.TableName, - TimeToLiveSpecification = data.TimeToLiveSpecification, - tableDb = store.tableDb, - returnValue; +module.exports = function updateTimeToLive (store, data, cb) { + var key = data.TableName, + TimeToLiveSpecification = data.TimeToLiveSpecification, + tableDb = store.tableDb, + returnValue - store.getTable(key, false, function(err, table) { - if (err) return cb(err) + store.getTable(key, false, function (err, table) { + if (err) return cb(err) - if (TimeToLiveSpecification.Enabled) { - if (table.TimeToLiveDescription && table.TimeToLiveDescription.TimeToLiveStatus === 'ENABLED') { - return cb(db.validationError('TimeToLive is already enabled')) - } - table.TimeToLiveDescription = { - AttributeName: TimeToLiveSpecification.AttributeName, - TimeToLiveStatus: 'ENABLED', - } - returnValue = TimeToLiveSpecification - } else { - if (table.TimeToLiveDescription == null || table.TimeToLiveDescription.TimeToLiveStatus === 'DISABLED') { - return cb(db.validationError('TimeToLive is already disabled')) - } + if (TimeToLiveSpecification.Enabled) { + if (table.TimeToLiveDescription && table.TimeToLiveDescription.TimeToLiveStatus === 'ENABLED') { + return cb(db.validationError('TimeToLive is already enabled')) + } + table.TimeToLiveDescription = { + AttributeName: TimeToLiveSpecification.AttributeName, + TimeToLiveStatus: 'ENABLED', + } + returnValue = TimeToLiveSpecification + } + else { + if (table.TimeToLiveDescription == null || table.TimeToLiveDescription.TimeToLiveStatus === 'DISABLED') { + return cb(db.validationError('TimeToLive is already disabled')) + } - table.TimeToLiveDescription = { - TimeToLiveStatus: 'DISABLED', - } - returnValue = {Enabled: false} - } + table.TimeToLiveDescription = { + TimeToLiveStatus: 'DISABLED', + } + returnValue = { Enabled: false } + } - tableDb.put(key, table, function(err) { - if (err) return cb(err) + tableDb.put(key, table, function (err) { + if (err) return cb(err) - cb(null, {TimeToLiveSpecification: returnValue}) - }) + cb(null, { TimeToLiveSpecification: returnValue }) }) + }) } diff --git a/db/index.js b/db/index.js index 698b901..ab2be88 100644 --- a/db/index.js +++ b/db/index.js @@ -137,50 +137,50 @@ function create (options) { timerIdTtlScanner = setInterval(function () { var currentUnixSeconds = Math.round(Date.now() / 1000) - function logError(err, result) { + function logError (err) { if (err) console.error(err) } lazyStream(tableDb.createKeyStream({}), logError) - .join(function (tableNames) { - tableNames.forEach(function (name) { - getTable(name, false, function (err, table) { - if (err) return - if (!table.TimeToLiveDescription || table.TimeToLiveDescription.TimeToLiveStatus !== 'ENABLED') return - - var keyAttrNames = table.KeySchema.map(function (i) { - return i.AttributeName - }) - var source = { - getItemDb: getItemDb, - getIndexDb: getIndexDb, - } - var itemDb = getItemDb(table.TableName) - var kvStream = lazyStream(itemDb.createReadStream({}), logError) - kvStream = kvStream.filter(function (item) { - var ttl = item.value[table.TimeToLiveDescription.AttributeName] - return ttl && typeof ttl.N === 'string' && currentUnixSeconds > Number(ttl.N) - }) - kvStream.join(function (kvs) { - kvs.forEach(function (kv) { - var itemKey = keyAttrNames.reduce(function (key, attrName) { - key[attrName] = kv.value[attrName] - return key - }, {}) - var data = {TableName: name, Key: itemKey} - var cb = function (err) { - // Noop ? - } - deleteItem(source, data, table, itemDb, kv.key, cb) - }) + .join(function (tableNames) { + tableNames.forEach(function (name) { + getTable(name, false, function (err, table) { + if (err) return + if (!table.TimeToLiveDescription || table.TimeToLiveDescription.TimeToLiveStatus !== 'ENABLED') return + + var keyAttrNames = table.KeySchema.map(function (i) { + return i.AttributeName + }) + var source = { + getItemDb: getItemDb, + getIndexDb: getIndexDb, + } + var itemDb = getItemDb(table.TableName) + var kvStream = lazyStream(itemDb.createReadStream({}), logError) + kvStream = kvStream.filter(function (item) { + var ttl = item.value[table.TimeToLiveDescription.AttributeName] + return ttl && typeof ttl.N === 'string' && currentUnixSeconds > Number(ttl.N) + }) + kvStream.join(function (kvs) { + kvs.forEach(function (kv) { + var itemKey = keyAttrNames.reduce(function (key, attrName) { + key[attrName] = kv.value[attrName] + return key + }, {}) + var data = { TableName: name, Key: itemKey } + var cb = () => { + // Noop ? + } + deleteItem(source, data, table, itemDb, kv.key, cb) }) }) }) }) + }) }, ttlScannerInterval) } - function stopBackgroundJobs() { + function stopBackgroundJobs () { clearInterval(timerIdTtlScanner) } @@ -244,14 +244,15 @@ function validateItem (dataItem, table) { }) } -function deleteItem(store, data, table, itemDb, key, cb) { - itemDb.lock(key, function(release) { +function deleteItem (store, data, table, itemDb, key, cb) { + itemDb.lock(key, function (release) { cb = release(cb) - itemDb.get(key, function(err, existingItem) { + itemDb.get(key, function (err, existingItem) { if (err && err.name !== 'NotFoundError') return cb(err) - if ((err = checkConditional(data, existingItem)) != null) return cb(err) + let conditionalErr = checkConditional(data, existingItem) + if (conditionalErr) return cb(conditionalErr) var returnObj = {} @@ -260,10 +261,10 @@ function deleteItem(store, data, table, itemDb, key, cb) { returnObj.ConsumedCapacity = addConsumedCapacity(data, false, existingItem) - updateIndexes(store, table, existingItem, null, function(err) { + updateIndexes(store, table, existingItem, null, function (err) { if (err) return cb(err) - itemDb.del(key, function(err) { + itemDb.del(key, function (err) { if (err) return cb(err) cb(null, returnObj) }) @@ -272,7 +273,6 @@ function deleteItem(store, data, table, itemDb, key, cb) { }) } -function validateUpdates(attributeUpdates, expressionUpdates, table) { function validateUpdates (attributeUpdates, expressionUpdates, table) { if (attributeUpdates == null && expressionUpdates == null) return diff --git a/test/updateTimeToLive.js b/test/updateTimeToLive.js index 41b7567..12396ab 100644 --- a/test/updateTimeToLive.js +++ b/test/updateTimeToLive.js @@ -1,66 +1,66 @@ var helpers = require('./helpers') var target = 'UpdateTimeToLive', - request = helpers.request, - opts = helpers.opts.bind(null, target), - assertType = helpers.assertType.bind(null, target), - assertValidation = helpers.assertValidation.bind(null, target), - assertNotFound = helpers.assertNotFound.bind(null, target) + request = helpers.request, + opts = helpers.opts.bind(null, target), + assertType = helpers.assertType.bind(null, target), + assertValidation = helpers.assertValidation.bind(null, target), + assertNotFound = helpers.assertNotFound.bind(null, target) -describe('updateTimeToLive', function() { +describe('updateTimeToLive', function () { - describe('serializations', function() { + describe('serializations', function () { - it('should return SerializationException when TableName is not a string', function(done) { + it('should return SerializationException when TableName is not a string', function (done) { assertType('TableName', 'String', done) }) - it('should return SerializationException when TimeToLiveSpecification is not a struct', function(done) { + it('should return SerializationException when TimeToLiveSpecification is not a struct', function (done) { assertType('TimeToLiveSpecification', 'FieldStruct', done) }) - it('should return SerializationException when TimeToLiveSpecification.AttributeName is not a string', function(done) { + it('should return SerializationException when TimeToLiveSpecification.AttributeName is not a string', function (done) { assertType('TimeToLiveSpecification.AttributeName', 'String', done) }) - it('should return SerializationException when TimeToLiveSpecification.Enabled is not a boolean', function(done) { + it('should return SerializationException when TimeToLiveSpecification.Enabled is not a boolean', function (done) { assertType('TimeToLiveSpecification.Enabled', 'Boolean', done) }) }) - describe('validations', function() { + describe('validations', function () { - it('should return ValidationException for no TableName', function(done) { + it('should return ValidationException for no TableName', function (done) { assertValidation({}, 'The parameter \'TableName\' is required but was not present in the request', done) }) - it('should return ValidationException for empty TableName', function(done) { - assertValidation({TableName: ''}, + it('should return ValidationException for empty TableName', function (done) { + assertValidation({ TableName: '' }, 'TableName must be at least 3 characters long and at most 255 characters long', done) }) - it('should return ValidationException for short TableName', function(done) { - assertValidation({TableName: 'a;'}, + it('should return ValidationException for short TableName', function (done) { + assertValidation({ TableName: 'a;' }, 'TableName must be at least 3 characters long and at most 255 characters long', done) }) - it('should return ValidationException for long TableName', function(done) { + it('should return ValidationException for long TableName', function (done) { var name = new Array(256 + 1).join('a') - assertValidation({TableName: name}, + assertValidation({ TableName: name }, 'TableName must be at least 3 characters long and at most 255 characters long', done) }) - it('should return ValidationException for invalid chars', function(done) { - assertValidation({TableName: 'abc;'}, + it('should return ValidationException for invalid chars', function (done) { + assertValidation({ TableName: 'abc;' }, '1 validation error detected: ' + 'Value \'abc;\' at \'tableName\' failed to satisfy constraint: ' + 'Member must satisfy regular expression pattern: [a-zA-Z0-9_.-]+', done) }) - it('should return ValidationException for empty TimeToLiveSpecification', function(done) { - assertValidation({TableName: 'abc', TimeToLiveSpecification: {}}, [ + it('should return ValidationException for empty TimeToLiveSpecification', function (done) { + assertValidation({ TableName: 'abc', TimeToLiveSpecification: {} }, [ 'Value null at \'timeToLiveSpecification.enabled\' failed to satisfy constraint: ' + 'Member must not be null', 'Value null at \'timeToLiveSpecification.attributeName\' failed to satisfy constraint: ' + @@ -68,8 +68,8 @@ describe('updateTimeToLive', function() { ], done) }) - it('should return ValidationException for null members in TimeToLiveSpecification', function(done) { - assertValidation({TableName: 'abc', TimeToLiveSpecification: {AttributeName: null, Enabled: null}}, [ + it('should return ValidationException for null members in TimeToLiveSpecification', function (done) { + assertValidation({ TableName: 'abc', TimeToLiveSpecification: { AttributeName: null, Enabled: null } }, [ 'Value null at \'timeToLiveSpecification.attributeName\' failed to satisfy constraint: ' + 'Member must not be null', 'Value null at \'timeToLiveSpecification.enabled\' failed to satisfy constraint: ' + @@ -77,59 +77,59 @@ describe('updateTimeToLive', function() { ], done) }) - it('should return ValidationException for empty TimeToLiveSpecification.AttributeName', function(done) { - assertValidation({TableName: 'abc', - TimeToLiveSpecification: {AttributeName: "", Enabled: true}}, - 'TimeToLiveSpecification.AttributeName must be non empty', done) + it('should return ValidationException for empty TimeToLiveSpecification.AttributeName', function (done) { + assertValidation({ TableName: 'abc', + TimeToLiveSpecification: { AttributeName: '', Enabled: true } }, + 'TimeToLiveSpecification.AttributeName must be non empty', done) }) - it('should return ResourceNotFoundException if table does not exist', function(done) { + it('should return ResourceNotFoundException if table does not exist', function (done) { var name = helpers.randomString() - assertNotFound({TableName: name, - TimeToLiveSpecification: {AttributeName: "id", Enabled: true}}, - 'Requested resource not found: Table: ' + name + ' not found', done) + assertNotFound({ TableName: name, + TimeToLiveSpecification: { AttributeName: 'id', Enabled: true } }, + 'Requested resource not found: Table: ' + name + ' not found', done) }) - it('should return ValidationException for false TimeToLiveSpecification.Enabled when already disabled', function(done) { - assertValidation({TableName: helpers.testHashTable, - TimeToLiveSpecification: {AttributeName: "a", Enabled: false}}, - 'TimeToLive is already disabled', done) + it('should return ValidationException for false TimeToLiveSpecification.Enabled when already disabled', function (done) { + assertValidation({ TableName: helpers.testHashTable, + TimeToLiveSpecification: { AttributeName: 'a', Enabled: false } }, + 'TimeToLive is already disabled', done) }) - it('should return ValidationException for true TimeToLiveSpecification.Enabled when already enabled', function(done) { - request(opts({TableName: helpers.testHashTable, TimeToLiveSpecification: {AttributeName: "a", Enabled: true}}), function(err, res) { + it('should return ValidationException for true TimeToLiveSpecification.Enabled when already enabled', function (done) { + request(opts({ TableName: helpers.testHashTable, TimeToLiveSpecification: { AttributeName: 'a', Enabled: true } }), function (err, res) { if (err) return done(err) res.statusCode.should.equal(200) - assertValidation({TableName: helpers.testHashTable, - TimeToLiveSpecification: {AttributeName: "a", Enabled: true}}, - 'TimeToLive is already enabled', function(err){ + assertValidation({ TableName: helpers.testHashTable, + TimeToLiveSpecification: { AttributeName: 'a', Enabled: true } }, + 'TimeToLive is already enabled', function (err){ + if (err) return done(err) + // teardown + request(opts({ TableName: helpers.testHashTable, TimeToLiveSpecification: { AttributeName: 'a', Enabled: false } }), function (err, res) { if (err) return done(err) - // teardown - request(opts({TableName: helpers.testHashTable, TimeToLiveSpecification: {AttributeName: "a", Enabled: false}}), function(err, res) { - if (err) return done(err) - res.statusCode.should.equal(200) - done() - }) + res.statusCode.should.equal(200) + done() }) + }) }) }) }) - describe('functionality', function() { - it('should enable when disabled', function(done) { - request(opts({TableName: helpers.testHashTable, TimeToLiveSpecification: {AttributeName: "a", Enabled: true}}), function(err, res) { + describe('functionality', function () { + it('should enable when disabled', function (done) { + request(opts({ TableName: helpers.testHashTable, TimeToLiveSpecification: { AttributeName: 'a', Enabled: true } }), function (err, res) { if (err) return done(err) res.statusCode.should.equal(200) - res.body.should.eql({TimeToLiveSpecification: {AttributeName: "a", Enabled: true}}) + res.body.should.eql({ TimeToLiveSpecification: { AttributeName: 'a', Enabled: true } }) - request(helpers.opts('DescribeTimeToLive', {TableName: helpers.testHashTable}), function(err, res) { + request(helpers.opts('DescribeTimeToLive', { TableName: helpers.testHashTable }), function (err, res) { if (err) return done(err) res.statusCode.should.equal(200) - res.body.should.eql({TimeToLiveDescription: {TimeToLiveStatus: "ENABLED", AttributeName: "a"}}) + res.body.should.eql({ TimeToLiveDescription: { TimeToLiveStatus: 'ENABLED', AttributeName: 'a' } }) // teardown - request(opts({TableName: helpers.testHashTable, TimeToLiveSpecification: {AttributeName: "a", Enabled: false}}), function(err, res) { + request(opts({ TableName: helpers.testHashTable, TimeToLiveSpecification: { AttributeName: 'a', Enabled: false } }), function (err, res) { if (err) return done(err) res.statusCode.should.equal(200) done() @@ -138,102 +138,102 @@ describe('updateTimeToLive', function() { }) }) - it('should disable when enabled', function(done) { - request(opts({TableName: helpers.testHashTable, TimeToLiveSpecification: {AttributeName: "a", Enabled: true}}), function(err, res) { + it('should disable when enabled', function (done) { + request(opts({ TableName: helpers.testHashTable, TimeToLiveSpecification: { AttributeName: 'a', Enabled: true } }), function (err, res) { if (err) return done(err) res.statusCode.should.equal(200) - res.body.should.eql({TimeToLiveSpecification: {AttributeName: "a", Enabled: true}}) + res.body.should.eql({ TimeToLiveSpecification: { AttributeName: 'a', Enabled: true } }) - request(opts({TableName: helpers.testHashTable, TimeToLiveSpecification: {AttributeName: "a", Enabled: false}}), function(err, res) { + request(opts({ TableName: helpers.testHashTable, TimeToLiveSpecification: { AttributeName: 'a', Enabled: false } }), function (err, res) { if (err) return done(err) res.statusCode.should.equal(200) - res.body.should.eql({TimeToLiveSpecification: {Enabled: false}}) + res.body.should.eql({ TimeToLiveSpecification: { Enabled: false } }) - request(helpers.opts('DescribeTimeToLive', {TableName: helpers.testHashTable}), function(err, res) { + request(helpers.opts('DescribeTimeToLive', { TableName: helpers.testHashTable }), function (err, res) { if (err) return done(err) res.statusCode.should.equal(200) - res.body.should.eql({TimeToLiveDescription: {TimeToLiveStatus: "DISABLED"}}) + res.body.should.eql({ TimeToLiveDescription: { TimeToLiveStatus: 'DISABLED' } }) done() }) }) }) }) - it('should delete the expired items from Tables and Indices when TTL is enabled', function(done) { - request(opts({TableName: helpers.testRangeTable, TimeToLiveSpecification: {AttributeName: "TTL", Enabled: true}}), function(err, res) { + it('should delete the expired items from Tables and Indices when TTL is enabled', function (done) { + request(opts({ TableName: helpers.testRangeTable, TimeToLiveSpecification: { AttributeName: 'TTL', Enabled: true } }), function (err, res) { if (err) return done(err) res.statusCode.should.equal(200) - res.body.should.eql({TimeToLiveSpecification: {AttributeName: "TTL", Enabled: true}}) + res.body.should.eql({ TimeToLiveSpecification: { AttributeName: 'TTL', Enabled: true } }) - var timestampNow = Math.round(Date.now() / 1000); + var timestampNow = Math.round(Date.now() / 1000) var sharedPk = helpers.randomString() var sharedGsiPk = helpers.randomString() var expiredItem = { - a: {S: sharedPk}, - b: {S: helpers.randomString()}, - c: {S: sharedGsiPk}, - d: {S: helpers.randomString()}, - TTL: {N: (timestampNow + 1).toString()}, + a: { S: sharedPk }, + b: { S: helpers.randomString() }, + c: { S: sharedGsiPk }, + d: { S: helpers.randomString() }, + TTL: { N: (timestampNow + 1).toString() }, } var livingItem = { - a: {S: sharedPk}, - b: {S: helpers.randomString()}, - c: {S: sharedGsiPk}, - d: {S: helpers.randomString()}, - TTL: {N: (timestampNow + 1000000).toString()}, + a: { S: sharedPk }, + b: { S: helpers.randomString() }, + c: { S: sharedGsiPk }, + d: { S: helpers.randomString() }, + TTL: { N: (timestampNow + 1000000).toString() }, } var batchWriteItemInput = { RequestItems: {}, } batchWriteItemInput.RequestItems[helpers.testRangeTable] = [ - { PutRequest: { Item: expiredItem }}, - { PutRequest: { Item: livingItem }}, + { PutRequest: { Item: expiredItem } }, + { PutRequest: { Item: livingItem } }, ] - request(helpers.opts('BatchWriteItem', batchWriteItemInput), function(err, res) { + request(helpers.opts('BatchWriteItem', batchWriteItemInput), function (err, res) { if (err) return done(err) res.statusCode.should.equal(200) - setTimeout(function(){ + setTimeout(function (){ request(helpers.opts('Query', { TableName: helpers.testRangeTable, - KeyConditionExpression: "a = :a", + KeyConditionExpression: 'a = :a', ExpressionAttributeValues: { - ":a": expiredItem.a, + ':a': expiredItem.a, }, - }), function(err, res) { + }), function (err, res) { if (err) return done(err) res.statusCode.should.equal(200) - res.body.Items.should.eql([livingItem], 'Expired item should be deleted from table') + res.body.Items.should.eql([ livingItem ], 'Expired item should be deleted from table') request(helpers.opts('Query', { TableName: helpers.testRangeTable, IndexName: 'index1', - KeyConditionExpression: "a = :a", + KeyConditionExpression: 'a = :a', ExpressionAttributeValues: { - ":a": expiredItem.a, + ':a': expiredItem.a, }, - }), function(err, res) { + }), function (err, res) { if (err) return done(err) res.statusCode.should.equal(200) - res.body.Items.should.eql([livingItem], "Expired Item should be deleted from LSI") + res.body.Items.should.eql([ livingItem ], 'Expired Item should be deleted from LSI') request(helpers.opts('Query', { TableName: helpers.testRangeTable, IndexName: 'index3', - KeyConditionExpression: "c = :c", + KeyConditionExpression: 'c = :c', ExpressionAttributeValues: { - ":c": expiredItem.c, + ':c': expiredItem.c, }, - }), function(err, res) { + }), function (err, res) { if (err) return done(err) res.statusCode.should.equal(200) - res.body.Items.should.eql([livingItem], "Expired Item should be deleted from GSI") + res.body.Items.should.eql([ livingItem ], 'Expired Item should be deleted from GSI') // teardown request(opts({ TableName: helpers.testRangeTable, - TimeToLiveSpecification: {AttributeName: "TTL", Enabled: false} + TimeToLiveSpecification: { AttributeName: 'TTL', Enabled: false } }), function (err, res) { if (err) return done(err) res.statusCode.should.equal(200) diff --git a/validations/updateTimeToLive.js b/validations/updateTimeToLive.js index 944f8ab..d1f35b9 100644 --- a/validations/updateTimeToLive.js +++ b/validations/updateTimeToLive.js @@ -23,8 +23,8 @@ exports.types = { } -exports.custom = function(data) { +exports.custom = function (data) { if (data.TimeToLiveSpecification.AttributeName === '') { - return 'TimeToLiveSpecification.AttributeName must be non empty'; + return 'TimeToLiveSpecification.AttributeName must be non empty' } }