Skip to content

Commit e00459f

Browse files
committed
Merge pull request #1 from serg-io/v0.1.0
Version 0.1.0
2 parents 9d05c8c + 70b01c7 commit e00459f

File tree

5 files changed

+164
-12
lines changed

5 files changed

+164
-12
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Change Log
2+
3+
## [0.0.3] - 2015-05-10
4+
### Added
5+
- New `getLastValue` method that retrieves a previously generated value for an
6+
specified counter.
7+
- Request parameters sent to DynamoDB can be extended using `options.dynamodb`.

README.md

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,43 @@ To configure the internal instance of [AWS.DynamoDB](http://docs.aws.amazon.com/
2222
you can follow one of the many methods described [here](http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/node-configuring.html) or
2323
manually using this `config` instance.
2424

25-
`increment`
26-
-----------
25+
`increment( counterId, options )`
26+
---------------------------------
2727

2828
This method increments the counter for the specified `counterId`.
2929
It returns an AWS-SDK [request](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Request.html)
3030
instance with a jQuery style promise interface applied to it.
3131
See the [jQuery documentation](http://api.jquery.com/category/deferred-object/) for details on how the promises work.
3232
[underscore.deferred](https://github.com/wookiehangover/underscore.deferred) is used to add the Promise interface to the returned request object.
3333
Additionally, success, error, and complete callback can be provided in the second argument.
34-
The increment method takes the following arguments:
34+
The `increment` method takes the following arguments:
3535

3636
* `counterId`: The unique name/identifier of the counter.
3737
* `options` (optional): An options object to overwrite some of the default behaviour of the increment operation. All attributes in this object are optional.
38+
* `options.tableName`: The name of the DynamoDB table that stores the counters. If not specified, it uses "AtomicCounters" by default.
3839
* `options.keyAttribute`: The name of the attribute that stores the counter name/identifier. If not specified, it uses "id" by default.
3940
* `options.countAttribute`: The name of the attribute that stores the last value generated for the specified `counterId`. If not specified, it uses "lastValue" by default.
41+
* `options.increment`: Specifies by how much the counter should be incremented. If not specified, it uses 1 by default.
4042
* `options.success`: Success callback function. It receives a single argument: the value (integer) generated by this increment operation for the specified `counterId`.
4143
* `options.error`: Error callback function. If the DynamoDB UpdateItem request fails, the error callback is executed. It receives a single argument: the error object returned from AWS-SDK.
4244
* `options.complete`: Complete callback function. This callback is executed when the increment operation is completed, whether or not it was successful. It receives a single argument: an integer, if the operation was successful, or an error object if it failed.
4345
* `options.context`: The context object to use in all callbacks. If specified, the value of `this` within all callbacks will be `options.context`.
4446

47+
`getLastValue( counterId, options )`
48+
------------------------------------
49+
50+
This method retrieves, from DynamoDB, the last generated value for the specified `counterId`. If the counter doesn't exist,
51+
the success callback would receive 0 as the first argument.
52+
It returns an AWS-SDK [request](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Request.html)
53+
instance with a jQuery style promise interface applied to it.
54+
See the [jQuery documentation](http://api.jquery.com/category/deferred-object/) for details on how the promises work.
55+
[underscore.deferred](https://github.com/wookiehangover/underscore.deferred) is used to add the Promise interface to the returned request object.
56+
Additionally, success, error, and complete callback can be provided in the second argument.
57+
The `getLastValue` method takes the following arguments:
58+
59+
* `counterId`: The unique name/identifier of the counter.
60+
* `options` (optional): The same options supported by the `increment` method are also supported by this method.
61+
4562

4663
Basic Usage
4764
-----------
@@ -64,4 +81,16 @@ The following few lines of code demonstrate basic usage of this library. Additio
6481
// An error occurred
6582
}).always(function (valueOrError) {
6683
// Executed whether or not the increment operation was successful
84+
});
85+
86+
/**
87+
* Retrieve the last value generated for the "Clients" counter.
88+
*/
89+
atomicCounter.getLastValue( 'Clients' ).done(function (lastValue) {
90+
// `lastValue` is the last value generated for the "Clients" counter.
91+
// If a values has not been generated before, `lastValue` would be 0.
92+
}).fail(function (error) {
93+
// An error occurred
94+
}).always(function (valueOrError) {
95+
// Executed whether or not the request was successful
6796
});

atomic-counter.js

Lines changed: 102 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,22 @@ exports.config = dynamo.config;
4848
*
4949
* @method increment
5050
* @param {String} counterId The name or identifier of the counter to increment.
51-
* @Param {Object} options An options object to overwrite some of the default behaviour of the increment operation.
52-
* @Param {String} options.keyAttribute The name of the attribute that stores the counter name/identifier. If not specified, it uses "id" by default.
53-
* @Param {String} options.countAttribute The name of the attribute that stores the last value generated for the specified `counterId`.
51+
* @param {Object} options An options object to overwrite some of the default behaviour of the increment operation.
52+
* @param {String} options.tableName The name of the DynamoDB table that stores the counters. If not specified, it uses "AtomicCounters" by default.
53+
* @param {String} options.keyAttribute The name of the attribute that stores the counter name/identifier. If not specified, it uses "id" by default.
54+
* @param {String} options.countAttribute The name of the attribute that stores the last value generated for the specified `counterId`.
5455
* If not specified, it uses "lastValue" by default.
55-
* @Param {Function} options.success Success callback function. It receives a single argument: the value (integer) generated by this
56+
* @param {Integer} options.increment Specifies by how much the counter should be incremented. If not specified, it uses 1 by default.
57+
* @param {Function} options.success Success callback function. It receives a single argument: the value (integer) generated by this
5658
* increment operation for the specified `counterId`.
57-
* @Param {Function} options.error Error callback function. If the DynamoDB UpdateItem request fails, the error callback is executed.
59+
* @param {Function} options.error Error callback function. If the DynamoDB UpdateItem request fails, the error callback is executed.
5860
* It receives a single argument: the error object returned from AWS-SDK.
59-
* @Param {Function} options.complete Complete callback function. This callback is executed when the increment operation is completed,
61+
* @param {Function} options.complete Complete callback function. This callback is executed when the increment operation is completed,
6062
* whether or not it was successful. It receives a single argument: a number, if the operation was successful, or an error object if it failed.
61-
* @Param {Function} options.context The context object to use in all callbacks. If specified, the value of `this`
63+
* @param options.context The context object to use in all callbacks. If specified, the value of `this`
6264
* within all callbacks will be `options.context`.
65+
* @param {Object} options.dynamodb Additional DynamoDB parameters. These parameters will be added to the parameters sent in the
66+
* [update item](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#updateItem-property) request.
6367
* @return {Request} A DynamoDB UpdateItem [request](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Request.html) object,
6468
* with a [jQuery](http://api.jquery.com/category/deferred-object/) style promise interface applied to it.
6569
*/
@@ -87,6 +91,7 @@ exports.increment = function ( counterId, options ) {
8791
N: '' + ( options.increment || DEFAULT_INCREMENT )
8892
}
8993
};
94+
_.extend( params, options.dynamodb );
9095

9196
request = dynamo.updateItem(params, function (error, data) {
9297
var newCountValue;
@@ -119,6 +124,96 @@ exports.increment = function ( counterId, options ) {
119124
}
120125
});
121126

127+
/**
128+
* Apply a promise interface to `request`, set the success, error, and complete callback, and return the promise.
129+
*/
130+
return deferred.promise( request ).done( successFn ).fail( errorFn ).always( completeFn );
131+
};
132+
133+
/**
134+
* Gets the last value previously generated for the specified `counterId`.
135+
* It returns an AWS-SDK request instance with a jQuery style promise interface applied to it.
136+
* See [jQuery documentation](http://api.jquery.com/category/deferred-object/) to find out how to attach callbacks
137+
* to the returned object using the methods: done, fail, always, and then.
138+
*
139+
* @method getLastValue
140+
* @param {String} counterId The name or identifier of the counter.
141+
* @param {Object} options An options object to overwrite some of the default options.
142+
* @param {String} options.tableName The name of the DynamoDB table that stores the counters. If not specified, it uses "AtomicCounters" by default.
143+
* @param {String} options.keyAttribute The name of the attribute that stores the counter name/identifier. If not specified, it uses "id" by default.
144+
* @param {String} options.countAttribute The name of the attribute that stores the last value generated for the specified `counterId`.
145+
* If not specified, it uses "lastValue" by default.
146+
* @param {Function} options.success Success callback function. It receives a single argument: the last value (integer) previously generated
147+
* for the specified `counterId`.
148+
* @param {Function} options.error Error callback function. If the DynamoDB GetItem request fails, the error callback is executed.
149+
* It receives a single argument: the error object returned from AWS-SDK or the exception thrown when attempting to parse the response.
150+
* @param {Function} options.complete Complete callback function. This callback is executed when the GetItem request is completed,
151+
* whether or not it was successful. It receives a single argument: a number, if it was successful, or an error object if it failed.
152+
* @param options.context The context object to use in all callbacks. If specified, the value of `this`
153+
* within all callbacks will be `options.context`.
154+
* @param {Object} options.dynamodb Additional DynamoDB parameters. These parameters will be added to the parameters sent in the
155+
* [get item](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#getItem-property) request.
156+
* @return {Request} A DynamoDB GetItem [request](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Request.html) object,
157+
* with a [jQuery](http://api.jquery.com/category/deferred-object/) style promise interface applied to it.
158+
*/
159+
exports.getLastValue = function ( counterId, options ) {
160+
options || ( options = {} );
161+
162+
var request,
163+
deferred = new _.Deferred(),
164+
keyAttribute = options.keyAttribute || DEFAULT_KEY_ATTRIBUTE,
165+
countAttribute = options.countAttribute || DEFAULT_COUNT_ATTRIBUTE,
166+
params = {
167+
Key: {},
168+
AttributesToGet: [ countAttribute ],
169+
TableName: options.tableName || DEFAULT_TABLE_NAME
170+
},
171+
errorFn = _.isFunction( options.error ) ? options.error : noop,
172+
successFn = _.isFunction( options.success ) ? options.success : noop,
173+
completeFn = _.isFunction( options.complete ) ? options.complete : noop;
174+
175+
params.Key[ keyAttribute ] = { S: counterId };
176+
_.extend( params, options.dynamodb );
177+
178+
request = dynamo.getItem(params, function (errorObject, data) {
179+
var error, lastValue;
180+
181+
if ( errorObject ) {
182+
error = errorObject;
183+
} else if ( _.isEmpty( data ) ) {
184+
/**
185+
* If the item doesn't exist, the response would be empty.
186+
* Set `lastValue` to 0 when the item doesn't exist.
187+
*/
188+
lastValue = 0;
189+
} else {
190+
try {
191+
// Try to parse the count value. An exception will be thrown if it's not a valid number.
192+
lastValue = parseInt( data.Item[ countAttribute ].N, 10 );
193+
194+
if ( !_.isNumber( lastValue ) || _.isNaN( lastValue ) ) {
195+
throw 'Could not parse incremented value (' + lastValue + ').';
196+
}
197+
} catch ( e ) {
198+
error = e;
199+
}
200+
}
201+
202+
if ( error ) {
203+
if ( options.context ) {
204+
deferred.rejectWith( options.context, [ e ] );
205+
} else {
206+
deferred.reject( e );
207+
}
208+
} else {
209+
if ( options.context ) {
210+
deferred.resolveWith( options.context, [ lastValue ] );
211+
} else {
212+
deferred.resolve( lastValue );
213+
}
214+
}
215+
});
216+
122217
/**
123218
* Apply a promise interface to `request`, set the success, error, and complete callback, and return the promise.
124219
*/

examples.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,25 @@ exports[ 'Concurrent increments/requests' ] = function (test) {
120120
test.ok( true ); // always was executed
121121
test.done();
122122
});
123+
};
124+
125+
/**
126+
* This function demonstrates how to use the getLastValue method.
127+
*/
128+
exports[ 'Get last value generated for a counter using ConsistentRead' ] = function (test) {
129+
var options = {
130+
dynamodb: {
131+
ConsistentRead: true
132+
}
133+
};
134+
test.expect( 2 );
135+
136+
atomicCounter.getLastValue( 'Users', options ).done(function (lastValue) {
137+
test.ok( true ); // success was executed
138+
}).fail(function (error) {
139+
test.ok( false, 'Failed to retrieve the last value used: ' + JSON.stringify( error ) );
140+
}).always(function (valueOrError) {
141+
test.ok( true ); // complete was executed
142+
test.done();
143+
});
123144
};

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "dynamodb-atomic-counter",
33
"author": "Sergio Alcantara <[email protected]> (https://github.com/serg-io)",
44
"description": "This library provides atomic counters using Amazon DynamoDB.",
5-
"version": "0.0.2",
5+
"version": "0.1.0",
66
"homepage": "https://github.com/serg-io/dynamodb-atomic-counter",
77
"keywords": [ "atomic-counter", "atomic", "counter", "dynamodb", "aws", "amazon" ],
88
"repository": {
@@ -18,4 +18,4 @@
1818
"devDependencies": {
1919
"nodeunit": "0.9.1"
2020
}
21-
}
21+
}

0 commit comments

Comments
 (0)