Skip to content

Commit

Permalink
Add support for TTL, Closes #116
Browse files Browse the repository at this point in the history
  • Loading branch information
brandongoode committed May 27, 2017
1 parent 4ecf3b4 commit 92994f1
Show file tree
Hide file tree
Showing 7 changed files with 301 additions and 19 deletions.
51 changes: 37 additions & 14 deletions docs/_docs/schema.md
Expand Up @@ -7,7 +7,7 @@ Schemas are used to define DynamoDB table attributes and their constraints.

### Creating a new Schema

Schemas are created using `new Schema(attrDefObj, options)`.
Schemas are created using `new Schema(attrDefObj, options)`.

The first argument (`attrDefObj`) is an object containing attribute definitions. Keys of this object correspond to attributes in the resulting DynamoDB table. The values of these keys define constraints on those attributes (as well as a few handy features...). See [Attribute Definitions](#attribute-definitions) for a more thorough description.

Expand Down Expand Up @@ -55,7 +55,7 @@ var dogSchema = new Schema({

### Attribute Types

Attribute Types define the domain of a particular attribute. For example, a `name` might be set to `String` or `age` to `Number`.
Attribute Types define the domain of a particular attribute. For example, a `name` might be set to `String` or `age` to `Number`.

The following table describes valid Attribute Types, and their translation to DynamoDB types:

Expand Down Expand Up @@ -102,7 +102,7 @@ Sets the attribute as a 'required' attribute. Required attributes must not be sa

Defines the attribute as a local or global secondary index. Index can either be true, an index definition object or and array of index definition objects. The array is used define multiple indexes for a single attribute. The index definition object can contain the following keys:

- _name: 'string'_ - Name of index (Default is `attribute.name + (global ? 'GlobalIndex' : 'LocalIndex')``).
- _name: 'string'_ - Name of index (Default is `attribute.name + (global ? 'GlobalIndex' : 'LocalIndex')`).
- _global: boolean_ - Set the index to be a global secondary index. Attribute will be the hash key for the Index.
- _rangeKey: 'string'_ - The range key for a global secondary index.
- _project: boolean | ['string', ...]_ - Sets the attributes to be projected for the index. `true` projects all attributes, `false` projects only the key attributes, and ['string', ...] projects the attributes listed. Default is `true`.
Expand Down Expand Up @@ -174,10 +174,10 @@ var schema = new Schema({...}, {
throughput: 5
});
var schema = new Schema({...}, {
throughput: {
read: 5,
write: 2
}
throughput: {
read: 5,
write: 2
}
});
```

Expand All @@ -189,7 +189,7 @@ Store Boolean values as Boolean ('BOOL') in DynamoDB. Default to `false` (i.e s

```js
var schema = new Schema({...}, {
useNativeBooleans: true
useNativeBooleans: true
});
```

Expand All @@ -199,7 +199,7 @@ Store Objects and Arrays as Maps ('M') and Lists ('L') types in DynamoDB. Defau

```js
var schema = new Schema({...}, {
useDocumentTypes: true
useDocumentTypes: true
});
```

Expand All @@ -214,25 +214,48 @@ var schema = new Schema({...}, {
});
```

Also it is possible to specify wich names that field will use, like in the following example:
You can specify the names that the fields will use, like in the following example:

```js
var schema = new Schema({...}, {
throughput: 5,
throughput: 5,
timestamps: {
createdAt: 'creationDate',
createdAt: 'creationDate',
updatedAt: 'lastUpdateDate'
}
});
```

**expires**: number | {ttl: number, attribute: string}

Defines that _schema_ must contain and expires attribute. This field is configured in DynamoDB as the TTL attribute. If set to a `number`, an attribute named "expires" will be added to the schema. The default value of the attribute will be the current time plus the expires value. The expires value is in seconds.

The attribute will be a standard javascript `Date` in the object, and will be stored as number ('N') in the DyanmoDB table. The stored number is in seconds. [More information about DynamoDB TTL](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html)

```js
var schema = new Schema({...}, {
expires: 7*24*60*60 // 1 week in seconds
});
```

You can specify the attribute name by passing an object:

```js
var schema = new Schema({...}, {
expires: {
ttl: 7*24*60*60, // 1 week in seconds
attribute: 'ttl' // ttl will be used as the attribute name
}
});
```

**saveUnknown**: boolean

Specifies that attributes not defined in the _schema_ will be saved and retrieved. This defaults to false.

```js
var schema = new Schema({...}, {
saveUnknown: true
saveUnknown: true
});
```

Expand Down Expand Up @@ -304,7 +327,7 @@ Model.getAll(function(err, models)=>{

#### Instance Methods

Can be accessed from a newly created model. `this` will refer to the instace of the model within
Can be accessed from a newly created model. `this` will refer to the instace of the model within
the definition of the function.

```js
Expand Down
34 changes: 34 additions & 0 deletions examples/ttl.js
@@ -0,0 +1,34 @@
'use strict';

var dynamoose = require('../');

dynamoose.setDefaults({
prefix: 'example-'
});

var Cat = dynamoose.model('Cat', {
id: Number,
name: String
}, {
expires: {
ttl: 1 * 24 * 60 * 60,
attribute: 'ttl'
}
});

var garfield = new Cat({id: 1, name: 'Fluffy'});

garfield.save()
.then(function () {
return Cat.get(1);
})
.then(function (fluffy) {
console.log(JSON.stringify(fluffy, null, ' '));
/*
{
"id": 3,
"name": "Fluffy",
"ttl": "2017-05-28T01:35:01.000Z"
}
*/
});
41 changes: 41 additions & 0 deletions lib/Schema.js
Expand Up @@ -60,6 +60,47 @@ function Schema(obj, options) {
this.timestamps = { createdAt: createdAt, updatedAt: updatedAt };
}


/*
* Added support for expires attribute
*/
if (this.options.expires !== null && this.options.expires !== undefined ) {
var expires = {
attribute: 'expires'
};

if (typeof this.options.expires === 'number') {
expires.ttl = this.options.expires;

} else if (typeof this.options.expires === 'object') {
if (typeof this.options.expires.ttl === 'number') {
expires.ttl = this.options.expires.ttl;
} else {
throw new errors.SchemaError('Missing or invalided ttl for expires attribute.');
}
if(typeof this.options.expires.attribute === 'string') {
expires.attribute = this.options.expires.attribute;
}
} else {
throw new errors.SchemaError('Invalid syntax for expires: ' + name);
}

var defaultExpires = function () {
return new Date(Date.now() + (expires.ttl * 1000));
};

obj[expires.attribute] = {
type: Number,
default: defaultExpires,
set: function(v) {
return Math.floor(v.getTime() / 1000);
},
get: function (v) {
return new Date(v * 1000);
}
};
this.expires = expires;
}
this.useDocumentTypes = !!this.options.useDocumentTypes;
this.useNativeBooleans = !!this.options.useNativeBooleans;
this.attributeFromDynamo = this.options.attributeFromDynamo;
Expand Down
68 changes: 65 additions & 3 deletions lib/Table.js
Expand Up @@ -168,9 +168,10 @@ Table.prototype.init = function (next) {
}
table.initialized = true;
return table.waitForActive()
.then(function() {
return table.updateTTL();
})
.then(function () {
//table.active = data.Table.TableStatus === 'ACTIVE';

return deferred.resolve();
});
})
Expand All @@ -187,6 +188,9 @@ Table.prototype.init = function (next) {
return table.waitForActive();
}
})
.then(function () {
return table.updateTTL();
})
);
}
if (err) {
Expand All @@ -196,7 +200,8 @@ Table.prototype.init = function (next) {
});
} else {
table.initialized = true;
return deferred.resolve();
return table.updateTTL();

}
return deferred.promise.nodeify(next);
};
Expand Down Expand Up @@ -266,6 +271,63 @@ Table.prototype.waitForActive = function (timeout, next) {
return deferred.promise.nodeify(next);
};

Table.prototype.describeTTL = function (next) {
debug('Describing ttl for table, %s', this.name);
var deferred = Q.defer();

var ddb = this.base.ddb();

var params = {
TableName: this.name
};
ddb.describeTimeToLive(params, function(err, ttlDescription) {
if (err) {
return deferred.reject(err);
}

return deferred.resolve(ttlDescription);
});

return deferred.promise.nodeify(next);
};

Table.prototype.updateTTL = function (next) {
debug('Updating ttl for table, %s', this.name);
var deferred = Q.defer();

var table = this;

if(this.schema.expires && !this.base.endpointURL) {

this.describeTTL()
.then(function (ttlDesc) {
var status = ttlDesc.TimeToLiveDescription.TimeToLiveStatus;
if(status === 'ENABLING' || status === 'ENABLED') {
return deferred.resolve();
}
var params = {
TableName: table.name,
TimeToLiveSpecification: {
AttributeName: table.schema.expires.attribute,
Enabled: true
}
};

var ddb = table.base.ddb();
ddb.updateTimeToLive(params, function(err) {
if (err) {
return deferred.reject(err);
}
return deferred.resolve();
});
});
} else {
deferred.resolve();
}

return deferred.promise.nodeify(next);
};

Table.prototype.describe = function (next) {
var describeTableReq = {
TableName: this.name
Expand Down
1 change: 0 additions & 1 deletion package.json
Expand Up @@ -47,7 +47,6 @@
},
"dependencies": {
"debug": "*",
"aws-sdk": "^2.1.0",
"q": "~1.0.1",
"hooks": "0.3.2",
"underscore": "^1.8.3",
Expand Down

0 comments on commit 92994f1

Please sign in to comment.