{ TableName: 'dynamo-client-integration-test' } { __type: 'com.amazon.dynamodb.v20111205#ResourceNotFoundException', message: 'Requested resource not found: Table: dynamo-client-integration-test not found' } { TableName: 'dynamo-client-integration-test' } { __type: 'com.amazon.dynamodb.v20111205#ResourceNotFoundException', message: 'Requested resource not found: Table: dynamo-client-integration-test not found' } { TableName: 'dynamo-client-integration-test', LocalSecondaryIndexes: [ { IndexName: 'postIx', KeySchema: [Object], Projection: [Object] } ], KeySchema: [ { AttributeName: 'forumName', KeyType: 'HASH' }, { AttributeName: 'subject', KeyType: 'RANGE' } ], AttributeDefinitions: [ { AttributeName: 'forumName', AttributeType: 'S' }, { AttributeName: 'subject', AttributeType: 'S' }, { AttributeName: 'lastPostTime', AttributeType: 'S' } ], ProvisionedThroughput: { ReadCapacityUnits: 1, WriteCapacityUnits: 1 } } { TableDescription: { AttributeDefinitions: [ [Object], [Object], [Object] ], CreationDateTime: 1370223503, KeySchema: [ [Object], [Object] ], ProvisionedThroughput: { NumberOfDecreasesToday: 0, ReadCapacityUnits: 1, WriteCapacityUnits: 1 }, TableName: 'dynamo-client-integration-test', TableStatus: 'CREATING', ItemCount: 0, TableSizeBytes: 0, LocalSecondaryIndexes: [ [Object] ] } } { TableName: 'dynamo-client-integration-test' } { Table: { AttributeDefinitions: [ [Object], [Object], [Object] ], CreationDateTime: 1370223503, KeySchema: [ [Object], [Object] ], ProvisionedThroughput: { NumberOfDecreasesToday: 0, ReadCapacityUnits: 1, WriteCapacityUnits: 1 }, TableName: 'dynamo-client-integration-test', TableStatus: 'ACTIVE', ItemCount: 0, TableSizeBytes: 0, LocalSecondaryIndexes: [ [Object] ] } } { TableName: 'dynamo-client-integration-test' } { Count: 0, ScannedCount: 0, Items: [] } { TableName: 'dynamo-client-integration-test', Item: { forumName: { S: 'a' }, subject: { S: 'b' }, lastPostTime: { S: '2013-06-03T01:38:23.047Z' } } } {} { TableName: 'dynamo-client-integration-test' } { Count: 1, ScannedCount: 1, Items: [ { forumName: [Object], subject: [Object], lastPostTime: [Object] } ] } { RequestItems: { 'dynamo-client-integration-test': [ [Object] ] } } { UnprocessedItems: {} } { ConsistentRead: true, TableName: 'dynamo-client-integration-test', Key: { forumName: { S: 'a' }, subject: { S: 'b' } } } {} { TableName: 'dynamo-client-integration-test' } { Count: 0, ScannedCount: 0, Items: [] } { TableName: 'dynamo-client-integration-test', Item: { forumName: { S: 'a' }, subject: { S: 'b' }, lastPostTime: { S: '2013-06-03T01:38:23.102Z' } } } {} { ConsistentRead: true, TableName: 'dynamo-client-integration-test', Key: { forumName: { S: 'a' }, subject: { S: 'b' } } } { Item: { forumName: { S: 'a' }, subject: { S: 'b' }, lastPostTime: { S: '2013-06-03T01:38:23.102Z' } } } { TableName: 'dynamo-client-integration-test' } { Count: 1, ScannedCount: 1, Items: [ { forumName: [Object], subject: [Object], lastPostTime: [Object] } ] } { RequestItems: { 'dynamo-client-integration-test': [ [Object] ] } } { UnprocessedItems: {} } { TableName: 'dynamo-client-integration-test', Item: { forumName: { S: 'a' }, subject: { S: 'a' }, lastPostTime: { S: '2013-06-03T01:38:23.138Z' } } } {} { TableName: 'dynamo-client-integration-test', Item: { forumName: { S: 'a' }, subject: { S: 'b' }, lastPostTime: { S: '2013-06-03T01:38:23.138Z' } } } {} { TableName: 'dynamo-client-integration-test', Item: { forumName: { S: 'a' }, subject: { S: 'c' }, lastPostTime: { S: '2013-06-03T01:38:23.138Z' } } } {} { ConsistentRead: true, TableName: 'dynamo-client-integration-test', KeyConditions: { forumName: { ComparisonOperator: 'EQ', AttributeValueList: [Object] }, subject: { ComparisonOperator: 'GT', AttributeValueList: [Object] } } } { Count: 2, Items: [ { forumName: [Object], subject: [Object], lastPostTime: [Object] }, { forumName: [Object], subject: [Object], lastPostTime: [Object] } ] } { ConsistentRead: true, Select: 'COUNT', TableName: 'dynamo-client-integration-test', KeyConditions: { forumName: { ComparisonOperator: 'EQ', AttributeValueList: [Object] }, subject: { ComparisonOperator: 'GT', AttributeValueList: [Object] } } } { Count: 2 } { TableName: 'dynamo-client-integration-test' } { Count: 3, ScannedCount: 3, Items: [ { forumName: [Object], subject: [Object], lastPostTime: [Object] }, { forumName: [Object], subject: [Object], lastPostTime: [Object] }, { forumName: [Object], subject: [Object], lastPostTime: [Object] } ] } { RequestItems: { 'dynamo-client-integration-test': [ [Object], [Object], [Object] ] } } { UnprocessedItems: {} } { TableName: 'dynamo-client-integration-test', Item: { forumName: { S: 'a' }, subject: { S: 'a' }, lastPostTime: { S: '2013-06-03T01:38:23.223Z' } } } {} { TableName: 'dynamo-client-integration-test', Item: { forumName: { S: 'a' }, subject: { S: 'b' }, lastPostTime: { S: '2013-06-03T01:38:23.224Z' } } } {} { TableName: 'dynamo-client-integration-test', Item: { forumName: { S: 'a' }, subject: { S: 'c' }, lastPostTime: { S: '2013-06-03T01:38:23.225Z' } } } {} { IndexName: 'postIx', ConsistentRead: true, TableName: 'dynamo-client-integration-test', KeyConditions: { forumName: { ComparisonOperator: 'EQ', AttributeValueList: [Object] }, lastPostTime: { ComparisonOperator: 'GT', AttributeValueList: [Object] } } } { Count: 2, Items: [ { forumName: [Object], subject: [Object], lastPostTime: [Object] }, { forumName: [Object], subject: [Object], lastPostTime: [Object] } ] } { IndexName: 'postIx', ConsistentRead: true, Select: 'COUNT', TableName: 'dynamo-client-integration-test', KeyConditions: { forumName: { ComparisonOperator: 'EQ', AttributeValueList: [Object] }, lastPostTime: { ComparisonOperator: 'GT', AttributeValueList: [Object] } } } { Count: 2 } { TableName: 'dynamo-client-integration-test' } { Count: 3, ScannedCount: 3, Items: [ { forumName: [Object], subject: [Object], lastPostTime: [Object] }, { forumName: [Object], subject: [Object], lastPostTime: [Object] }, { forumName: [Object], subject: [Object], lastPostTime: [Object] } ] } { RequestItems: { 'dynamo-client-integration-test': [ [Object], [Object], [Object] ] } } { UnprocessedItems: {} } { TableName: 'dynamo-client-integration-test', Item: { forumName: { S: 'a' }, subject: { S: 'a' }, lastPostTime: { S: '2013-06-03T01:38:23.309Z' } } } {} { TableName: 'dynamo-client-integration-test', Item: { forumName: { S: 'a' }, subject: { S: 'b' }, lastPostTime: { S: '2013-06-03T01:38:23.309Z' } } } {} { TableName: 'dynamo-client-integration-test', Item: { forumName: { S: 'a' }, subject: { S: 'c' }, lastPostTime: { S: '2013-06-03T01:38:23.309Z' } } } {} { TableName: 'dynamo-client-integration-test', ScanFilter: { forumName: { ComparisonOperator: 'EQ', AttributeValueList: [Object] }, subject: { ComparisonOperator: 'GT', AttributeValueList: [Object] } } } { Count: 2, ScannedCount: 3, Items: [ { forumName: [Object], subject: [Object], lastPostTime: [Object] }, { forumName: [Object], subject: [Object], lastPostTime: [Object] } ] } { Select: 'COUNT', TableName: 'dynamo-client-integration-test', ScanFilter: { forumName: { ComparisonOperator: 'EQ', AttributeValueList: [Object] }, subject: { ComparisonOperator: 'GT', AttributeValueList: [Object] } } } { Count: 2, ScannedCount: 3 } { TableName: 'dynamo-client-integration-test' } { Count: 3, ScannedCount: 3, Items: [ { forumName: [Object], subject: [Object], lastPostTime: [Object] }, { forumName: [Object], subject: [Object], lastPostTime: [Object] }, { forumName: [Object], subject: [Object], lastPostTime: [Object] } ] } { RequestItems: { 'dynamo-client-integration-test': [ [Object], [Object], [Object] ] } } { UnprocessedItems: {} } { TableName: 'dynamo-client-integration-test', Item: { forumName: { S: 'a' }, subject: { S: 'a' }, lastPostTime: { S: '2013-06-03T01:38:23.387Z' } } } {} { TableName: 'dynamo-client-integration-test', Item: { forumName: { S: 'a' }, subject: { S: 'b' }, lastPostTime: { S: '2013-06-03T01:38:23.388Z' } } } {} { TableName: 'dynamo-client-integration-test', Item: { forumName: { S: 'a' }, subject: { S: 'c' }, lastPostTime: { S: '2013-06-03T01:38:23.389Z' } } } {} { Segment: 0, TotalSegments: 3, TableName: 'dynamo-client-integration-test', ScanFilter: { forumName: { ComparisonOperator: 'EQ', AttributeValueList: [Object] }, lastPostTime: { ComparisonOperator: 'GT', AttributeValueList: [Object] } } } { Segment: 1, TotalSegments: 3, TableName: 'dynamo-client-integration-test', ScanFilter: { forumName: { ComparisonOperator: 'EQ', AttributeValueList: [Object] }, lastPostTime: { ComparisonOperator: 'GT', AttributeValueList: [Object] } } } { Segment: 2, TotalSegments: 3, TableName: 'dynamo-client-integration-test', ScanFilter: { forumName: { ComparisonOperator: 'EQ', AttributeValueList: [Object] }, lastPostTime: { ComparisonOperator: 'GT', AttributeValueList: [Object] } } } { Count: 0, ScannedCount: 1, Items: [] } { Count: 1, ScannedCount: 1, Items: [ { forumName: [Object], subject: [Object], lastPostTime: [Object] } ] } { Count: 1, ScannedCount: 1, Items: [ { forumName: [Object], subject: [Object], lastPostTime: [Object] } ] } { Segment: 0, TotalSegments: 3, Select: 'COUNT', TableName: 'dynamo-client-integration-test', ScanFilter: { forumName: { ComparisonOperator: 'EQ', AttributeValueList: [Object] }, lastPostTime: { ComparisonOperator: 'GT', AttributeValueList: [Object] } } } { Segment: 1, TotalSegments: 3, Select: 'COUNT', TableName: 'dynamo-client-integration-test', ScanFilter: { forumName: { ComparisonOperator: 'EQ', AttributeValueList: [Object] }, lastPostTime: { ComparisonOperator: 'GT', AttributeValueList: [Object] } } } { Segment: 2, TotalSegments: 3, Select: 'COUNT', TableName: 'dynamo-client-integration-test', ScanFilter: { forumName: { ComparisonOperator: 'EQ', AttributeValueList: [Object] }, lastPostTime: { ComparisonOperator: 'GT', AttributeValueList: [Object] } } } { Count: 1, ScannedCount: 1 } { Count: 0, ScannedCount: 1 } { Count: 1, ScannedCount: 1 } { TableName: 'dynamo-client-integration-test' } { Count: 3, ScannedCount: 3, Items: [ { forumName: [Object], subject: [Object], lastPostTime: [Object] }, { forumName: [Object], subject: [Object], lastPostTime: [Object] }, { forumName: [Object], subject: [Object], lastPostTime: [Object] } ] } { RequestItems: { 'dynamo-client-integration-test': [ [Object], [Object], [Object] ] } } { UnprocessedItems: {} } {} { TableNames: [ 'dynamo-client-integration-test' ] } { TableName: 'dynamo-client-integration-test' } { Count: 0, ScannedCount: 0, Items: [] } { TableName: 'dynamo-client-integration-test', ProvisionedThroughput: { ReadCapacityUnits: 2, WriteCapacityUnits: 2 } } { TableDescription: { AttributeDefinitions: [ [Object], [Object], [Object] ], CreationDateTime: 1370223503, KeySchema: [ [Object], [Object] ], ProvisionedThroughput: { NumberOfDecreasesToday: 0, ReadCapacityUnits: 2, WriteCapacityUnits: 2, LastIncreaseDateTime: 1370223503 }, TableName: 'dynamo-client-integration-test', TableStatus: 'UPDATING', ItemCount: 0, TableSizeBytes: 0, LocalSecondaryIndexes: [ [Object] ] } } { TableName: 'dynamo-client-integration-test' } { Count: 0, ScannedCount: 0, Items: [] } { TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Value: [Object] } } } {} { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { Attributes: { lastId: { N: '1' } } } { TableName: 'dynamo-client-integration-test' } { Count: 1, ScannedCount: 1, Items: [ { forumName: [Object], subject: [Object], lastId: [Object] } ] } { RequestItems: { 'dynamo-client-integration-test': [ [Object] ] } } { UnprocessedItems: {} } { TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Value: [Object] } } } {} { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { Attributes: { lastId: { N: '1' } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { Attributes: { lastId: { N: '2' } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { Attributes: { lastId: { N: '3' } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { Attributes: { lastId: { N: '4' } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { Attributes: { lastId: { N: '5' } } } { TableName: 'dynamo-client-integration-test' } { Count: 1, ScannedCount: 1, Items: [ { forumName: [Object], subject: [Object], lastId: [Object] } ] } { RequestItems: { 'dynamo-client-integration-test': [ [Object] ] } } { UnprocessedItems: {} } { TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Value: [Object] } } } {} { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { ReturnValues: 'UPDATED_NEW', TableName: 'dynamo-client-integration-test', Key: { forumName: { S: '0' }, subject: { S: '0' } }, AttributeUpdates: { lastId: { Action: 'ADD', Value: [Object] } } } { Attributes: { lastId: { N: '1' } } } { Attributes: { lastId: { N: '2' } } } { Attributes: { lastId: { N: '3' } } } { Attributes: { lastId: { N: '4' } } } { Attributes: { lastId: { N: '5' } } } { Attributes: { lastId: { N: '6' } } } { Attributes: { lastId: { N: '7' } } } { Attributes: { lastId: { N: '8' } } } { Attributes: { lastId: { N: '9' } } } { Attributes: { lastId: { N: '10' } } } { Attributes: { lastId: { N: '11' } } } { Attributes: { lastId: { N: '12' } } } { Attributes: { lastId: { N: '14' } } } { Attributes: { lastId: { N: '13' } } } { Attributes: { lastId: { N: '15' } } } { Attributes: { lastId: { N: '16' } } } { Attributes: { lastId: { N: '17' } } } { Attributes: { lastId: { N: '18' } } } { Attributes: { lastId: { N: '19' } } } { Attributes: { lastId: { N: '20' } } } { TableName: 'dynamo-client-integration-test' } { Table: { AttributeDefinitions: [ [Object], [Object], [Object] ], CreationDateTime: 1370223503, KeySchema: [ [Object], [Object] ], ProvisionedThroughput: { NumberOfDecreasesToday: 0, ReadCapacityUnits: 2, WriteCapacityUnits: 2, LastIncreaseDateTime: 1370223503 }, TableName: 'dynamo-client-integration-test', TableStatus: 'ACTIVE', ItemCount: 1, TableSizeBytes: 0, LocalSecondaryIndexes: [ [Object] ] } } { TableName: 'dynamo-client-integration-test' } { TableDescription: { AttributeDefinitions: [ [Object], [Object], [Object] ], CreationDateTime: 1370223503, KeySchema: [ [Object], [Object] ], ProvisionedThroughput: { NumberOfDecreasesToday: 0, ReadCapacityUnits: 2, WriteCapacityUnits: 2, LastIncreaseDateTime: 1370223503 }, TableName: 'dynamo-client-integration-test', TableStatus: 'DELETING', ItemCount: 1, TableSizeBytes: 0, LocalSecondaryIndexes: [ [Object] ] } } { TableName: 'dynamo-client-integration-test' } { __type: 'com.amazon.dynamodb.v20111205#ResourceNotFoundException', message: 'Requested resource not found: Table: dynamo-client-integration-test not found' }
Line | Hits | Source |
---|---|---|
1 | 1 | var dynamo |
2 | ||
3 | 1 | try { |
4 | 1 | dynamo = require('dynamo-client') |
5 | } catch (e) { | |
6 | // Assume consumer will pass in client | |
7 | } | |
8 | ||
9 | 1 | module.exports = function(name, options) { |
10 | 95 | return new DynamoTable(name, options) |
11 | } | |
12 | 1 | module.exports.DynamoTable = DynamoTable |
13 | ||
14 | 1 | function DynamoTable(name, options) { |
15 | 97 | if (!name) throw new Error('Table must have a name') |
16 | 93 | options = options || {} |
17 | 93 | this.name = name |
18 | 93 | this.client = options.client |
19 | 93 | if (!this.client) { |
20 | 35 | if (!dynamo) throw new Error('dynamo-client module is not installed') |
21 | 35 | this.client = dynamo.createClient(options.region, options.credentials) |
22 | } | |
23 | 93 | this.mappings = options.mappings || {} |
24 | 93 | this.key = options.key || Object.keys(options.keyTypes || this.mappings).slice(0, 2) |
25 | 97 | if (!Array.isArray(this.key)) this.key = [this.key] |
26 | 152 | if (!this.key.length) this.key = ['id'] |
27 | 93 | this.keyTypes = options.keyTypes || {} |
28 | 93 | this.readCapacity = options.readCapacity |
29 | 93 | this.writeCapacity = options.writeCapacity |
30 | 93 | this.indexes = options.indexes |
31 | 93 | this.scanSegments = options.scanSegments |
32 | 93 | this.preMapFromDb = options.preMapFromDb |
33 | 93 | this.postMapFromDb = options.postMapFromDb |
34 | 93 | this.preMapToDb = options.preMapToDb |
35 | 93 | this.postMapToDb = options.postMapToDb |
36 | } | |
37 | ||
38 | 1 | DynamoTable.prototype.mapAttrToDb = function(val, key, jsObj) { |
39 | 639 | var mapping = this.mappings[key] |
40 | 639 | if (mapping) { |
41 | 182 | if (typeof mapping.to === 'function') return mapping.to(val, key, jsObj) |
42 | 189 | if (mapping === 'json') return {S: JSON.stringify(val)} |
43 | 173 | if (val == null || val === '') return |
44 | 169 | switch (mapping) { |
45 | 135 | case 'S': return {S: String(val)} |
46 | 2 | case 'N': return {N: String(val)} |
47 | 1 | case 'B': return {B: val.toString('base64')} |
48 | 1 | case 'SS': return {SS: typeof val[0] === 'string' ? val : val.map(function(x) { return String(x) })} |
49 | 4 | case 'NS': return {NS: val.map(function(x) { return String(x) })} |
50 | 4 | case 'BS': return {BS: val.map(function(x) { return x.toString('base64') })} |
51 | 5 | case 'bignum': return Array.isArray(val) ? {NS: val} : {N: val} |
52 | 19 | case 'isodate': return {S: val.toISOString()} |
53 | 1 | case 'timestamp': return {N: String(+val)} |
54 | 1 | case 'mapS': return {SS: Object.keys(val)} |
55 | 1 | case 'mapN': return {NS: Object.keys(val)} |
56 | 1 | case 'mapB': return {BS: Object.keys(val)} |
57 | } | |
58 | } | |
59 | 468 | if (val == null || val === '') return |
60 | 448 | switch (typeof val) { |
61 | 80 | case 'string': return {S: val} |
62 | 2 | case 'boolean': return {S: String(val)} |
63 | 350 | case 'number': return {N: String(val)} |
64 | } | |
65 | 16 | if (Buffer.isBuffer(val)) { |
66 | 7 | if (!val.length) return |
67 | 3 | return {B: val.toString('base64')} |
68 | } | |
69 | 11 | if (Array.isArray(val)) { |
70 | 11 | if (!val.length) return |
71 | 9 | if (typeof val[0] === 'string') return {SS: val} |
72 | 16 | if (typeof val[0] === 'number') return {NS: val.map(function(x) { return String(x) })} |
73 | 10 | if (Buffer.isBuffer(val[0])) return {BS: val.map(function(x) { return x.toString('base64') })} |
74 | } | |
75 | 2 | return {S: JSON.stringify(val)} |
76 | } | |
77 | ||
78 | 1 | DynamoTable.prototype.mapAttrFromDb = function(val, key, dbItem) { |
79 | 421 | var mapping = this.mappings[key] |
80 | 421 | if (mapping) { |
81 | 101 | if (typeof mapping.from === 'function') return mapping.from(val, key, dbItem) |
82 | 97 | switch (mapping) { |
83 | 51 | case 'S': return val.S |
84 | 0 | case 'N': return +val.N |
85 | 1 | case 'B': return new Buffer(val.B, 'base64') |
86 | 1 | case 'SS': return val.SS |
87 | 4 | case 'NS': return val.NS.map(function(x) { return +x }) |
88 | 4 | case 'BS': return val.BS.map(function(x) { return new Buffer(x, 'base64') }) |
89 | 9 | case 'json': return JSON.parse(val.S) |
90 | 5 | case 'bignum': return val.N != null ? val.N : val.NS |
91 | 24 | case 'isodate': return new Date(val.S) |
92 | 1 | case 'timestamp': return new Date(val.N) |
93 | case 'mapS': | |
94 | case 'mapN': | |
95 | case 'mapB': | |
96 | 3 | return (val.SS || val.NS || val.BS).reduce(function(mapObj, val) { |
97 | 9 | mapObj[val] = 1 |
98 | 9 | return mapObj |
99 | }, {}) | |
100 | } | |
101 | } | |
102 | 322 | if (val.S != null) { |
103 | 41 | if (val.S === 'true') return true |
104 | 40 | if (val.S === 'false') return false |
105 | 38 | if (val.S[0] === '{' || val.S[0] === '[') |
106 | 6 | try { return JSON.parse(val.S) } catch (e) {} |
107 | 35 | return val.S |
108 | } | |
109 | 555 | if (val.N != null) return +val.N |
110 | 11 | if (val.B != null) return new Buffer(val.B, 'base64') |
111 | 9 | if (val.SS != null) return val.SS |
112 | 12 | if (val.NS != null) return val.NS.map(function(x) { return +x }) |
113 | 11 | if (val.BS != null) return val.BS.map(function(x) { return new Buffer(x, 'base64') }) |
114 | 1 | throw new Error('Unknown DynamoDB type: ' + JSON.stringify(val)) |
115 | } | |
116 | ||
117 | 1 | DynamoTable.prototype.mapToDb = function(jsObj) { |
118 | 78 | if (this.preMapToDb) jsObj = this.preMapToDb(jsObj) |
119 | 78 | var self = this, |
120 | dbItem = jsObj != null ? {} : null | |
121 | ||
122 | 78 | if (dbItem != null && jsObj != null) { |
123 | 77 | Object.keys(jsObj).forEach(function(key) { |
124 | 176 | var dbAttr = self.mapAttrToDb(jsObj[key], key, jsObj) |
125 | 176 | if (!self._isEmpty(dbAttr)) |
126 | 172 | dbItem[key] = dbAttr |
127 | }) | |
128 | } | |
129 | 78 | if (this.postMapToDb) dbItem = this.postMapToDb(dbItem) |
130 | 78 | return dbItem |
131 | } | |
132 | ||
133 | 1 | DynamoTable.prototype.mapFromDb = function(dbItem) { |
134 | 268 | if (this.preMapFromDb) dbItem = this.preMapFromDb(dbItem) |
135 | 268 | var self = this, |
136 | jsObj = dbItem != null ? {} : null | |
137 | ||
138 | 268 | if (dbItem != null && jsObj != null) { |
139 | 266 | Object.keys(dbItem).forEach(function(key) { |
140 | 349 | var jsAttr = self.mapAttrFromDb(dbItem[key], key, dbItem) |
141 | 348 | if (typeof jsAttr !== 'undefined') |
142 | 347 | jsObj[key] = jsAttr |
143 | }) | |
144 | } | |
145 | 267 | if (this.postMapFromDb) jsObj = this.postMapFromDb(jsObj) |
146 | 267 | return jsObj |
147 | } | |
148 | ||
149 | 1 | DynamoTable.prototype.resolveKey = function(key) { |
150 | 288 | var self = this |
151 | 288 | if (arguments.length > 1) |
152 | 1 | key = [].slice.call(arguments) |
153 | 287 | else if (typeof key !== 'object' || Buffer.isBuffer(key)) |
154 | 231 | key = [key] |
155 | ||
156 | 288 | if (Array.isArray(key)) { |
157 | 240 | return key.reduce(function(dbKey, val, ix) { |
158 | 245 | dbKey[self.key[ix]] = self.mapAttrToDb(val, self.key[ix]) |
159 | 245 | return dbKey |
160 | }, {}) | |
161 | } | |
162 | 48 | return Object.keys(key).reduce(function(dbKey, attr) { |
163 | 95 | dbKey[attr] = self.mapAttrToDb(key[attr], attr) |
164 | 95 | return dbKey |
165 | }, {}) | |
166 | } | |
167 | ||
168 | 1 | DynamoTable.prototype._isEmpty = function(attr) { |
169 | 188 | return attr == null || attr.S === '' || attr.N === '' || attr.B === '' || |
170 | attr.SS === '[]' || attr.NS === '[]' || attr.BS === '[]' | |
171 | } | |
172 | ||
173 | 1 | DynamoTable.prototype._getKeyType = function(attr) { |
174 | 36 | var type = this.keyTypes[attr] || this.mappings[attr] || 'S' |
175 | 36 | switch (type) { |
176 | case 'N': | |
177 | case 'S': | |
178 | case 'B': | |
179 | 34 | return type |
180 | case 'json': | |
181 | case 'isodate': | |
182 | 1 | return 'S' |
183 | case 'bignum': | |
184 | case 'timestamp': | |
185 | 1 | return 'N' |
186 | } | |
187 | 0 | throw new Error('Unsupported key type (' + type + ') for attr ' + attr) |
188 | } | |
189 | ||
190 | 1 | DynamoTable.prototype.get = function(key, options, cb) { |
191 | 17 | if (!cb) { cb = options; options = {} } |
192 | 9 | if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') |
193 | 9 | options = this._getDefaultOptions(options) |
194 | 9 | var self = this |
195 | ||
196 | 9 | options.Key = options.Key || this.resolveKey(key) |
197 | 9 | this.client.request('GetItem', options, function(err, data) { |
198 | 10 | if (err) return cb(err) |
199 | 8 | cb(null, self.mapFromDb(data.Item)) |
200 | }) | |
201 | } | |
202 | ||
203 | 1 | DynamoTable.prototype.put = function(jsObj, options, cb) { |
204 | 45 | if (!cb) { cb = options; options = {} } |
205 | 15 | if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') |
206 | 15 | options.TableName = options.TableName || this.name |
207 | ||
208 | 15 | options.Item = options.Item || this.mapToDb(jsObj) |
209 | 15 | this.client.request('PutItem', options, cb) |
210 | } | |
211 | ||
212 | 1 | DynamoTable.prototype.delete = function(key, options, cb) { |
213 | 3 | if (!cb) { cb = options; options = {} } |
214 | 1 | if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') |
215 | 1 | options.TableName = options.TableName || this.name |
216 | ||
217 | 1 | options.Key = options.Key || this.resolveKey(key) |
218 | 1 | this.client.request('DeleteItem', options, cb) |
219 | } | |
220 | ||
221 | 1 | DynamoTable.prototype.update = function(key, actions, options, cb) { |
222 | 58 | if (!cb) { cb = options; options = {} } |
223 | 44 | if (!cb) { cb = actions; actions = key; key = null } |
224 | 39 | if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') |
225 | 37 | options = this._getDefaultOptions(options) |
226 | 37 | var self = this, pick, attrUpdates |
227 | ||
228 | // If actions is a string or array, then it's a whitelist for attributes to update | |
229 | 39 | if (typeof actions === 'string') actions = [actions] |
230 | 46 | if (Array.isArray(actions)) { pick = actions; actions = key; key = null } |
231 | ||
232 | // If key is null, assume actions has a full object to put so clone it (without keys) | |
233 | 37 | if (key == null) { |
234 | 8 | key = this.key.map(function(attr) { return actions[attr] }) |
235 | 4 | pick = pick || Object.keys(actions) |
236 | 4 | actions = {put: pick.reduce(function(attrsObj, attr) { |
237 | 15 | if (!~self.key.indexOf(attr)) attrsObj[attr] = actions[attr] |
238 | 8 | return attrsObj |
239 | }, {})} | |
240 | } | |
241 | ||
242 | // If we have some attributes that are not actions (put, add, delete), then throw | |
243 | 76 | if (Object.keys(actions).some(function(attr) { return !~['put', 'add', 'delete'].indexOf(attr) })) |
244 | 0 | throw new Error('actions must only contain put/add/delete attributes') |
245 | ||
246 | 37 | options.Key = options.Key || this.resolveKey(key) |
247 | 37 | attrUpdates = options.AttributeUpdates = options.AttributeUpdates || {} |
248 | ||
249 | 37 | if (actions.put != null) { |
250 | 8 | Object.keys(actions.put).forEach(function(attr) { |
251 | 12 | attrUpdates[attr] = attrUpdates[attr] || {Value: self.mapAttrToDb(actions.put[attr], attr)} |
252 | 12 | if (self._isEmpty(attrUpdates[attr].Value)) { |
253 | 4 | attrUpdates[attr].Action = 'DELETE' // "empty" attributes should actually be deleted |
254 | 4 | delete attrUpdates[attr].Value |
255 | } | |
256 | }) | |
257 | } | |
258 | ||
259 | 37 | if (actions.add != null) { |
260 | 29 | Object.keys(actions.add).forEach(function(attr) { |
261 | 29 | attrUpdates[attr] = attrUpdates[attr] || |
262 | {Action: 'ADD', Value: self.mapAttrToDb(actions.add[attr], attr)} | |
263 | }) | |
264 | } | |
265 | ||
266 | 37 | if (actions.delete != null) { |
267 | 3 | if (!Array.isArray(actions.delete)) actions.delete = [actions.delete] |
268 | 2 | actions.delete.forEach(function(attr) { |
269 | 3 | if (typeof attr === 'string') { |
270 | 2 | attrUpdates[attr] = attrUpdates[attr] || {Action: 'DELETE'} |
271 | } else { | |
272 | 1 | Object.keys(attr).forEach(function(setKey) { |
273 | 1 | attrUpdates[setKey] = attrUpdates[setKey] || |
274 | {Action: 'DELETE', Value: self.mapAttrToDb(attr[setKey], setKey)} | |
275 | }) | |
276 | } | |
277 | }) | |
278 | } | |
279 | ||
280 | 37 | if (!Object.keys(attrUpdates).length) return process.nextTick(cb) |
281 | ||
282 | 37 | this.client.request('UpdateItem', options, cb) |
283 | } | |
284 | ||
285 | 1 | DynamoTable.prototype.query = function(conditions, options, cb) { |
286 | 14 | if (!cb) { cb = options; options = {} } |
287 | 8 | if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') |
288 | 8 | options = this._getDefaultOptions(options) |
289 | 8 | var self = this, nonKeys |
290 | ||
291 | 8 | options.KeyConditions = options.KeyConditions || this.conditions(conditions) |
292 | 8 | if (!options.IndexName) { |
293 | 16 | nonKeys = Object.keys(options.KeyConditions).filter(function(attr) { return !~self.key.indexOf(attr) }) |
294 | 6 | if (nonKeys.length) { |
295 | // we have a non-key attribute, must find an IndexName | |
296 | 2 | this.resolveIndexes(this.indexes).forEach(function(index) { |
297 | 1 | if (index.key === nonKeys[0]) |
298 | 1 | options.IndexName = index.name |
299 | }) | |
300 | 2 | options.IndexName = options.IndexName || nonKeys[0] |
301 | } | |
302 | } | |
303 | 8 | this._listRequest('Query', options, cb) |
304 | } | |
305 | ||
306 | 1 | DynamoTable.prototype.scan = function(conditions, options, cb) { |
307 | 69 | if (!cb) { cb = options; options = {} } |
308 | 63 | if (!cb) { cb = conditions; conditions = null } |
309 | 37 | if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') |
310 | 37 | options = this._getDefaultOptions(options) |
311 | 37 | var totalSegments, segment, allItems |
312 | ||
313 | 45 | if (conditions != null && !options.ScanFilter) options.ScanFilter = this.conditions(conditions) |
314 | 37 | options.TotalSegments = options.TotalSegments || this.scanSegments |
315 | ||
316 | 37 | if (options.Segment == null && options.TotalSegments) { |
317 | 5 | totalSegments = options.TotalSegments |
318 | 5 | allItems = new Array(totalSegments) |
319 | 5 | for (segment = 0; segment < totalSegments; segment++) |
320 | 15 | this.scan(null, cloneWithSegment(options, segment), checkDone(segment)) |
321 | } else { | |
322 | 32 | this._listRequest('Scan', options, cb) |
323 | } | |
324 | 37 | function cloneWithSegment(options, segment) { |
325 | 15 | return Object.keys(options).reduce(function(clone, key) { |
326 | 51 | clone[key] = options[key] |
327 | 51 | return clone |
328 | }, {Segment: segment}) | |
329 | } | |
330 | 37 | function checkDone(segment) { |
331 | 15 | return function (err, items) { |
332 | 15 | if (err) return cb(err) |
333 | 15 | allItems[segment] = items |
334 | 15 | if (!--totalSegments) { |
335 | 5 | if (options.Select === 'COUNT') |
336 | 6 | return cb(null, allItems.reduce(function(sum, count) { return sum + count })) |
337 | 3 | cb(null, [].concat.apply([], allItems)) |
338 | } | |
339 | } | |
340 | } | |
341 | } | |
342 | ||
343 | // http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html | |
344 | 1 | DynamoTable.MAX_GET = 100 |
345 | 1 | DynamoTable.prototype.batchGet = function(keys, options, tables, cb) { |
346 | 13 | if (!cb) { cb = tables; tables = [] } |
347 | 11 | if (!cb) { cb = options; options = {} } |
348 | 5 | if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') |
349 | 5 | var self = this, |
350 | onlyThis = false, | |
351 | tablesByName = {}, | |
352 | allKeys, numRequests, allResults, i, j, key, requestItems, requestItem, opt | |
353 | ||
354 | 5 | if (keys && keys.length) { |
355 | 5 | tables.unshift({table: this, keys: keys, options: options}) |
356 | 5 | onlyThis = tables.length === 1 |
357 | } | |
358 | 5 | allKeys = tables.map(function(tableObj) { |
359 | 6 | var table = tableObj.table, keys = tableObj.keys, options = tableObj.options |
360 | 6 | tablesByName[table.name] = table |
361 | 6 | if (Array.isArray(options)) |
362 | 0 | options = {AttributesToGet: options} |
363 | 6 | else if (typeof options === 'string') |
364 | 2 | options = {AttributesToGet: [options]} |
365 | 6 | return keys.map(function(key) { |
366 | 215 | var dbKey = table.resolveKey(key) |
367 | 215 | dbKey._table = table.name |
368 | 215 | dbKey._options = options || {} |
369 | 215 | return dbKey |
370 | }) | |
371 | }) | |
372 | 5 | allKeys = [].concat.apply([], allKeys) |
373 | 5 | numRequests = Math.ceil(allKeys.length / DynamoTable.MAX_GET) |
374 | 5 | allResults = new Array(numRequests) |
375 | ||
376 | 5 | for (i = 0; i < allKeys.length; i += DynamoTable.MAX_GET) { |
377 | 7 | requestItems = {} |
378 | 7 | for (j = i; j < i + DynamoTable.MAX_GET && j < allKeys.length; j++) { |
379 | 215 | key = allKeys[j] |
380 | 215 | requestItem = requestItems[key._table] = (requestItems[key._table] || {}) |
381 | 215 | for (opt in key._options) |
382 | 6 | requestItem[opt] = key._options[opt] |
383 | 215 | requestItem.Keys = requestItem.Keys || [] |
384 | 215 | requestItem.Keys.push(key) |
385 | 215 | delete key._table |
386 | 215 | delete key._options |
387 | } | |
388 | 7 | batchRequest(requestItems, checkDone(i / DynamoTable.MAX_GET)) |
389 | } | |
390 | ||
391 | 5 | function batchRequest(requestItems, results, cb) { |
392 | 23 | if (!cb) { cb = results; results = {} } |
393 | 9 | self.client.request('BatchGetItem', {RequestItems: requestItems}, function(err, data) { |
394 | 9 | if (err) return cb(err) |
395 | 9 | for (var name in data.Responses) { |
396 | 10 | results[name] = (results[name] || []).concat( |
397 | data.Responses[name].map(tablesByName[name].mapFromDb.bind(tablesByName[name]))) | |
398 | } | |
399 | 9 | if (Object.keys(data.UnprocessedKeys || {}).length) |
400 | 2 | return batchRequest(data.UnprocessedKeys, results, cb) |
401 | 7 | cb(null, results) |
402 | }) | |
403 | } | |
404 | ||
405 | 5 | function checkDone(ix) { |
406 | 7 | return function (err, results) { |
407 | 7 | if (err) return cb(err) |
408 | 7 | allResults[ix] = results |
409 | 7 | if (!--numRequests) { |
410 | 5 | var merged = {} |
411 | 5 | allResults.forEach(function(results) { |
412 | 7 | for (var name in results) |
413 | 8 | merged[name] = (merged[name] || []).concat(results[name]) |
414 | }) | |
415 | 5 | cb(null, onlyThis ? merged[self.name] : merged) |
416 | } | |
417 | } | |
418 | } | |
419 | } | |
420 | ||
421 | // http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html | |
422 | 1 | DynamoTable.MAX_WRITE = 25 |
423 | 1 | DynamoTable.prototype.batchWrite = function(operations, tables, cb) { |
424 | 40 | if (!cb) { cb = tables; tables = [] } |
425 | 14 | if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') |
426 | 14 | var self = this, |
427 | allOperations, numRequests, i, j, requestItems, operation | |
428 | ||
429 | 14 | if (operations && Object.keys(operations).length) |
430 | 14 | tables.unshift({table: this, operations: operations}) |
431 | ||
432 | 14 | allOperations = tables.map(function(tableObj) { |
433 | 15 | var table = tableObj.table, operations = tableObj.operations || [], ops |
434 | 20 | if (Array.isArray(operations)) operations = {puts: operations, deletes: []} |
435 | 15 | ops = (operations.puts || []).map(function(jsObj) { |
436 | 60 | return {PutRequest: {Item: table.mapToDb(jsObj)}, _table: table.name} |
437 | }) | |
438 | 15 | return ops.concat((operations.deletes || []).map(function(key) { |
439 | 20 | return {DeleteRequest: {Key: table.resolveKey(key)}, _table: table.name} |
440 | })) | |
441 | }) | |
442 | 14 | allOperations = [].concat.apply([], allOperations) |
443 | 14 | numRequests = Math.ceil(allOperations.length / DynamoTable.MAX_WRITE) |
444 | ||
445 | 14 | for (i = 0; i < allOperations.length; i += DynamoTable.MAX_WRITE) { |
446 | 16 | requestItems = {} |
447 | 16 | for (j = i; j < i + DynamoTable.MAX_WRITE && j < allOperations.length; j++) { |
448 | 80 | operation = allOperations[j] |
449 | 80 | requestItems[operation._table] = requestItems[operation._table] || [] |
450 | 80 | requestItems[operation._table].push(operation) |
451 | 80 | delete operation._table |
452 | } | |
453 | 16 | batchRequest(requestItems, checkDone) |
454 | } | |
455 | ||
456 | 14 | function batchRequest(requestItems, cb) { |
457 | 18 | self.client.request('BatchWriteItem', {RequestItems: requestItems}, function(err, data) { |
458 | 18 | if (err) return cb(err) |
459 | 18 | if (Object.keys(data.UnprocessedItems || {}).length) |
460 | 2 | return batchRequest(data.UnprocessedItems, cb) |
461 | 16 | cb() |
462 | }) | |
463 | } | |
464 | ||
465 | 14 | function checkDone(err) { |
466 | 16 | if (err) return cb(err) |
467 | 16 | if (!--numRequests) |
468 | 14 | cb() |
469 | } | |
470 | } | |
471 | ||
472 | 1 | DynamoTable.prototype.createTable = function(readCapacity, writeCapacity, indexes, options, cb) { |
473 | 43 | if (!cb) { cb = options; options = {} } |
474 | 27 | if (!cb) { cb = indexes; indexes = this.indexes } |
475 | 25 | if (!cb) { cb = writeCapacity; writeCapacity = this.writeCapacity || 1 } |
476 | 25 | if (!cb) { cb = readCapacity; readCapacity = this.readCapacity || 1 } |
477 | 15 | if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') |
478 | 15 | options.TableName = options.TableName || this.name |
479 | 15 | var self = this, |
480 | attrMap = this.key.reduce(function(namesObj, attr) { | |
481 | 25 | namesObj[attr] = true |
482 | 25 | return namesObj |
483 | }, {}) | |
484 | ||
485 | 15 | if (indexes && !options.LocalSecondaryIndexes) { |
486 | 10 | options.LocalSecondaryIndexes = this.resolveIndexes(indexes).map(function(index) { |
487 | 13 | var lsi = { |
488 | IndexName: index.name, | |
489 | KeySchema: [self.key[0], index.key].map(function(attr, ix) { | |
490 | 26 | return { AttributeName: attr, KeyType: ix === 0 ? 'HASH' : 'RANGE' } |
491 | }), | |
492 | } | |
493 | 13 | if (typeof index.projection === 'string') |
494 | 12 | lsi.Projection = {ProjectionType: index.projection} |
495 | 1 | else if (Array.isArray(index.projection)) |
496 | 1 | lsi.Projection = {ProjectionType: 'INCLUDE', NonKeyAttributes: index.projection} |
497 | ||
498 | 13 | attrMap[index.key] = true |
499 | ||
500 | 13 | return lsi |
501 | }) | |
502 | } | |
503 | 15 | options.KeySchema = options.KeySchema || this.key.map(function(attr, ix) { |
504 | 25 | return {AttributeName: attr, KeyType: ix === 0 ? 'HASH' : 'RANGE'} |
505 | }) | |
506 | 15 | options.AttributeDefinitions = options.AttributeDefinitions || Object.keys(attrMap).map(function(attr) { |
507 | 36 | return {AttributeName: attr, AttributeType: self._getKeyType(attr)} |
508 | }) | |
509 | 15 | options.ProvisionedThroughput = options.ProvisionedThroughput || { |
510 | ReadCapacityUnits: readCapacity, | |
511 | WriteCapacityUnits: writeCapacity, | |
512 | } | |
513 | 15 | this.client.request('CreateTable', options, function(err, data) { |
514 | 15 | if (err) return cb(err) |
515 | 15 | cb(null, data.TableDescription) |
516 | }) | |
517 | } | |
518 | ||
519 | 1 | DynamoTable.prototype.describeTable = function(options, cb) { |
520 | 18 | if (!cb) { cb = options; options = {} } |
521 | 6 | if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') |
522 | 6 | options.TableName = options.TableName || this.name |
523 | ||
524 | 6 | this.client.request('DescribeTable', options, function(err, data) { |
525 | 9 | if (err) return cb(err) |
526 | 3 | cb(null, data.Table) |
527 | }) | |
528 | } | |
529 | ||
530 | 1 | DynamoTable.prototype.updateTable = function(readCapacity, writeCapacity, options, cb) { |
531 | 6 | if (!cb) { cb = options; options = {} } |
532 | 2 | if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') |
533 | 2 | options.TableName = options.TableName || this.name |
534 | ||
535 | 2 | options.ProvisionedThroughput = options.ProvisionedThroughput || { |
536 | ReadCapacityUnits: readCapacity, | |
537 | WriteCapacityUnits: writeCapacity, | |
538 | } | |
539 | 2 | this.client.request('UpdateTable', options, function(err, data) { |
540 | 2 | if (err) return cb(err) |
541 | 2 | cb(null, data.TableDescription) |
542 | }) | |
543 | } | |
544 | ||
545 | 1 | DynamoTable.prototype.deleteTable = function(options, cb) { |
546 | 4 | if (!cb) { cb = options; options = {} } |
547 | 2 | if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') |
548 | 2 | options.TableName = options.TableName || this.name |
549 | ||
550 | 2 | this.client.request('DeleteTable', options, function(err, data) { |
551 | 2 | if (err) return cb(err) |
552 | 2 | cb(null, data.TableDescription) |
553 | }) | |
554 | } | |
555 | ||
556 | // TODO: Support ExclusiveStartTableName/LastEvaluatedTableName | |
557 | 1 | DynamoTable.prototype.listTables = function(options, cb) { |
558 | 6 | if (!cb) { cb = options; options = {} } |
559 | 2 | if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') |
560 | ||
561 | 2 | this.client.request('ListTables', options, function(err, data) { |
562 | 2 | if (err) return cb(err) |
563 | 2 | cb(null, data.TableNames) |
564 | }) | |
565 | } | |
566 | ||
567 | 1 | DynamoTable.prototype.deleteTableAndWait = function(options, cb) { |
568 | 7 | if (!cb) { cb = options; options = {} } |
569 | 3 | if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') |
570 | 3 | var self = this |
571 | 3 | this.describeTable(function(err, data) { |
572 | 5 | if (err && err.name === 'ResourceNotFoundException') return cb() |
573 | 1 | if (err) return cb(err) |
574 | ||
575 | 1 | if (data.TableStatus !== 'ACTIVE') return setTimeout(self.deleteTableAndWait.bind(self, options, cb), 1000) |
576 | ||
577 | 1 | self.deleteTable(options, function(err) { |
578 | 1 | if (err) return cb(err) |
579 | 1 | self.deleteTableAndWait(options, cb) |
580 | }) | |
581 | }) | |
582 | } | |
583 | ||
584 | 1 | DynamoTable.prototype.createTableAndWait = function(readCapacity, writeCapacity, indexes, options, cb) { |
585 | 4 | if (!cb) { cb = options; options = {} } |
586 | 4 | if (!cb) { cb = indexes; indexes = this.indexes } |
587 | 4 | if (!cb) { cb = writeCapacity; writeCapacity = this.writeCapacity || 1 } |
588 | 4 | if (!cb) { cb = readCapacity; readCapacity = this.readCapacity || 1 } |
589 | 2 | if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') |
590 | 2 | var self = this |
591 | 2 | this.describeTable(function(err, data) { |
592 | 2 | if (err && err.name === 'ResourceNotFoundException') { |
593 | 1 | return self.createTable(readCapacity, writeCapacity, indexes, options, function(err) { |
594 | 1 | if (err) return cb(err) |
595 | 1 | self.createTableAndWait(readCapacity, writeCapacity, indexes, options, cb) |
596 | }) | |
597 | } | |
598 | 1 | if (err) return cb(err) |
599 | ||
600 | 2 | if (data.TableStatus === 'ACTIVE') return cb() |
601 | 0 | if (data.TableStatus !== 'CREATING') return cb(new Error(data.TableStatus)) |
602 | ||
603 | 0 | setTimeout(self.createTableAndWait.bind(self, readCapacity, writeCapacity, indexes, options, cb), 1000) |
604 | }) | |
605 | } | |
606 | ||
607 | 1 | DynamoTable.prototype.increment = function(key, attr, incrAmt, options, cb) { |
608 | 28 | var self = this, actions |
609 | 84 | if (!cb) { cb = options; options = {} } |
610 | 82 | if (!cb) { cb = incrAmt; incrAmt = 1 } |
611 | 28 | if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') |
612 | ||
613 | 28 | options.ReturnValues = options.ReturnValues || 'UPDATED_NEW' |
614 | 28 | actions = {add: {}} |
615 | 28 | actions.add[attr] = incrAmt |
616 | 28 | this.update(key, actions, options, function(err, data) { |
617 | 28 | if (err) return cb(err) |
618 | 28 | var newVal = (data.Attributes != null ? data.Attributes[attr] : null) |
619 | 28 | if (newVal == null) return cb() |
620 | 28 | cb(null, self.mapAttrFromDb(newVal, attr)) |
621 | }) | |
622 | } | |
623 | ||
624 | 1 | DynamoTable.prototype._listRequest = function(operation, items, options, cb) { |
625 | 164 | if (!cb) { cb = options; options = items; items = [] } |
626 | 44 | if (typeof cb !== 'function') throw new Error('Last parameter must be a callback function') |
627 | 44 | var self = this |
628 | ||
629 | 44 | this.client.request(operation, options, function(err, data) { |
630 | 44 | if (err) return cb(err) |
631 | 55 | if (options.Select === 'COUNT') return cb(null, data.Count) |
632 | ||
633 | 73 | items = items.concat(data.Items.map(function(item) { return self.mapFromDb(item) })) |
634 | 33 | if (data.LastEvaluatedKey != null && (!options.Limit || options.Limit !== (data.ScannedCount || data.Count))) { |
635 | 4 | options.ExclusiveStartKey = data.LastEvaluatedKey |
636 | 4 | return self._listRequest(operation, items, options, cb) |
637 | } | |
638 | 29 | cb(null, items) |
639 | }) | |
640 | } | |
641 | ||
642 | 1 | DynamoTable.prototype.conditions = function(conditionExprObj) { |
643 | 16 | var self = this |
644 | 16 | return Object.keys(conditionExprObj).reduce(function(condObj, attr) { |
645 | 30 | condObj[attr] = self.condition(attr, conditionExprObj[attr]) |
646 | 30 | return condObj |
647 | }, {}) | |
648 | } | |
649 | ||
650 | 1 | DynamoTable.prototype.condition = function(key, conditionExpr) { |
651 | 30 | var self = this, |
652 | type = typeof conditionExpr, | |
653 | comparison, attrVals, cond | |
654 | ||
655 | 30 | if (conditionExpr === null) { |
656 | 0 | comparison = 'NULL' |
657 | 30 | } else if (conditionExpr === 'notNull' || conditionExpr === 'NOT_NULL') { |
658 | 0 | comparison = 'NOT_NULL' |
659 | 30 | } else if (type === 'string' || type === 'number' || Buffer.isBuffer(conditionExpr)) { |
660 | 16 | comparison = 'EQ' |
661 | 16 | attrVals = [conditionExpr] |
662 | 14 | } else if (Array.isArray(conditionExpr)) { |
663 | 0 | comparison = 'IN' |
664 | 0 | attrVals = conditionExpr |
665 | } else { | |
666 | 14 | comparison = Object.keys(conditionExpr)[0] |
667 | 14 | attrVals = conditionExpr[comparison] |
668 | 28 | if (!Array.isArray(attrVals)) attrVals = [attrVals] |
669 | 14 | comparison = this.comparison(comparison) |
670 | } | |
671 | 30 | cond = {ComparisonOperator: comparison} |
672 | 30 | if (attrVals != null) |
673 | 60 | cond.AttributeValueList = attrVals.map(function(val) { return self.mapAttrToDb(val, key) }) |
674 | 30 | return cond |
675 | } | |
676 | ||
677 | 1 | DynamoTable.prototype.comparison = function(comparison) { |
678 | 14 | switch (comparison) { |
679 | 0 | case '=': return 'EQ' |
680 | 0 | case '==': return 'EQ' |
681 | 0 | case '!=': return 'NE' |
682 | 0 | case '<=': return 'LE' |
683 | 0 | case '<': return 'LT' |
684 | 14 | case '>': return 'GT' |
685 | 0 | case '>=': return 'GE' |
686 | 0 | case '>=<=': return 'BETWEEN' |
687 | case 'beginsWith': | |
688 | case 'startsWith': | |
689 | 0 | return 'BEGINS_WITH' |
690 | case 'notContains': | |
691 | case 'doesNotContain': | |
692 | 0 | return 'NOT_CONTAINS' |
693 | } | |
694 | 0 | return comparison.toUpperCase() |
695 | } | |
696 | ||
697 | // indexes: | |
698 | // [attr1/name1, attr2/name2] | |
699 | // {name: attr1} | |
700 | // {name: {key: attr1, projection: 'KEYS_ONLY'}} | |
701 | // {name: {key: attr1, projection: [attr1, attr2]}} | |
702 | // [{name: name1, key: attr1}, {name: name2, key: attr2}] | |
703 | 1 | DynamoTable.prototype.resolveIndexes = function(indexes) { |
704 | 13 | if (!indexes) return [] |
705 | 11 | if (!Array.isArray(indexes)) { |
706 | 7 | indexes = Object.keys(indexes).map(function(name) { |
707 | 8 | var index = indexes[name] |
708 | 14 | if (typeof index === 'string') index = {key: index} |
709 | 8 | return {name: name, key: index.key, projection: index.projection} |
710 | }) | |
711 | } | |
712 | 11 | return indexes.map(function(index) { |
713 | 18 | if (typeof index === 'string') index = {name: index, key: index} |
714 | 14 | index.projection = index.projection || 'ALL' |
715 | 14 | return index |
716 | }) | |
717 | } | |
718 | ||
719 | 1 | DynamoTable.prototype._getDefaultOptions = function(options) { |
720 | 91 | if (options == null) |
721 | 0 | options = {} |
722 | 91 | else if (Array.isArray(options)) |
723 | 1 | options = {AttributesToGet: options} |
724 | 90 | else if (typeof options === 'string') |
725 | 1 | options = {AttributesToGet: [options]} |
726 | 91 | options.TableName = options.TableName || this.name |
727 | 91 | return options |
728 | } | |
729 | ||
730 |