Skip to content

Commit 9d43fec

Browse files
committed
add scan support, misc updates
1 parent c5a5294 commit 9d43fec

File tree

4 files changed

+415
-19
lines changed

4 files changed

+415
-19
lines changed

README.md

+55
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,61 @@
99

1010
# DOCUMENTATION IS NOT COMPLETE FOR v0.2
1111

12+
### TODOs:
13+
14+
**QUERY** implementation:
15+
- [x] Select (ALL_ATTRIBUTES, ALL_PROJECTED_ATTRIBUTES, SPECIFIC_ATTRIBUTES, COUNT)
16+
- [x] Sync generateQueryParams - how to handle entity projections?
17+
18+
**GET** implemention:
19+
- [x] Consistent Read
20+
- [x] Consumed Capacity (INDEXES, TOTAL, NONE)
21+
- [x] Projections
22+
- [x] Update tests
23+
24+
**DELETE** implemention:
25+
- [x] Conditions
26+
- [x] Consumed Capacity (INDEXES, TOTAL, NONE)
27+
- [x] Collection Metrics (SIZE, NONE)
28+
- [x] Return Values (NONE, ALL_OLD, UPDATED_OLD, ALL_NEW, UPDATED_NEW)
29+
30+
**PUT** implemention:
31+
- [x] Conditions
32+
- [x] Consumed Capacity (INDEXES, TOTAL, NONE)
33+
- [x] Collection Metrics (SIZE, NONE)
34+
- [x] Return Values (NONE, ALL_OLD, UPDATED_OLD, ALL_NEW, UPDATED_NEW)
35+
- [x] Null values
36+
37+
**UPDATE** implemention:
38+
- [x] Conditions
39+
- [x] Consumed Capacity (INDEXES, TOTAL, NONE)
40+
- [x] Collection Metrics (SIZE, NONE)
41+
- [x] Return Values (NONE, ALL_OLD, UPDATED_OLD, ALL_NEW, UPDATED_NEW)
42+
- [x] Null values
43+
44+
**BATCH GET**
45+
- [ ] Implement interface
46+
47+
**BATCH WRITE**
48+
- [ ] Implement interface
49+
50+
**SCAN**
51+
- [x] Specify index
52+
- [x] Limit
53+
- [x] Consistent Read
54+
- [x] Select (ALL_ATTRIBUTES, ALL_PROJECTED_ATTRIBUTES, SPECIFIC_ATTRIBUTES, COUNT)
55+
- [x] Start Key
56+
- [x] Consumed Capacity (INDEXES, TOTAL, NONE)
57+
- [x] Segments
58+
- [x] Segment
59+
- [x] Projections
60+
- [x] Filters
61+
62+
**MISC:**
63+
- [x] convertEmptyValues (update DocumentClient options)
64+
- [x] Make `convertEmptyValues` optional - set it to false in your DocumentClient options
65+
- [ ] Check param merging
66+
1267
### **NOTE:** This project is in BETA. Please submit [issues/feedback](https://github.com/jeremydaly/dynamodb-toolbox/issues) or feel free to contact me on Twitter [@jeremy_daly](https://twitter.com/jeremy_daly).
1368

1469
The **DynamoDB Toolbox** is a simple set of tools for working with [Amazon DynamoDB](https://aws.amazon.com/dynamodb/) and the [DocumentClient](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/dynamodb-example-document-client.html). It lets you define your data models (with typings and aliases) and map them to your DynamoDB table. You can then **generate parameters** to `put`, `get`, `delete`, and `update` data by passing in a JavaScript object. The DynamoDB Toolbox will map aliases, validate and coerce types, and even write complex `UpdateExpression`s for you. 😉

__tests__/projectionBuilder.unit.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ describe('projectionBuilder',() => {
4949
{ User: ['family','name','roles','test.subpath','friends[1]'] }
5050
]
5151

52-
const result = projectionBuilder(proj, DefaultTable)
52+
// Get projection with type
53+
const result = projectionBuilder(proj, DefaultTable,null,true)
5354

5455
// console.log(JSON.stringify(result.names,null,2))
55-
// console.log(result)
5656
expect(result.names).toEqual({
5757
'#proj1': 'pk',
5858
'#proj2': 'sk',

classes/Entity.js

+126-2
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,62 @@ class Entity {
392392
// Merge defaults
393393
const data = normalizeData(this.DocumentClient)(schema.attributes,linked,Object.assign({},defaults,item))
394394

395+
// Extract valid options
396+
const {
397+
conditions, // ConditionExpression
398+
capacity, // ReturnConsumedCapacity (none, total, or indexes)
399+
metrics, // ReturnItemCollectionMetrics: (size or none)
400+
returnValues, // Return Values (none, all_old, updated_old, all_new, updated_new)
401+
..._args
402+
} = options
403+
404+
// Remove other valid options from options
405+
const args = Object.keys(_args).filter(x => !['execute','parse'].includes(x))
406+
407+
// Error on extraneous arguments
408+
if (args.length > 0)
409+
error(`Invalid delete options: ${args.join(', ')}`)
410+
411+
// Verify metrics
412+
if (metrics !== undefined
413+
&& (typeof metrics !== 'string' || !['NONE','SIZE'].includes(metrics.toUpperCase())))
414+
error(`'metrics' must be one of 'NONE' OR 'SIZE'`)
415+
416+
// Verify capacity
417+
if (capacity !== undefined
418+
&& (typeof capacity !== 'string' || !['NONE','TOTAL','INDEXES'].includes(capacity.toUpperCase())))
419+
error(`'capacity' must be one of 'NONE','TOTAL', OR 'INDEXES'`)
420+
421+
// Verify returnValues
422+
if (returnValues !== undefined
423+
&& (typeof returnValues !== 'string'
424+
|| !['NONE', 'ALL_OLD', 'UPDATED_OLD', 'ALL_NEW', 'UPDATED_NEW'].includes(returnValues.toUpperCase())))
425+
error(`'returnValues' must be one of 'NONE', 'ALL_OLD', 'UPDATED_OLD', 'ALL_NEW', OR 'UPDATED_NEW'`)
426+
427+
let ConditionExpression // init ConditionExpression
428+
429+
// If conditions
430+
if (conditions) {
431+
432+
// Parse the conditions
433+
const {
434+
expression,
435+
names,
436+
values
437+
} = parseConditions(conditions,this.table,this.name)
438+
439+
if (Object.keys(names).length > 0) {
440+
441+
// TODO: alias attribute field names
442+
// Add names, values and condition expression
443+
ExpressionAttributeNames = Object.assign(ExpressionAttributeNames,names)
444+
ExpressionAttributeValues = Object.assign(ExpressionAttributeValues,values)
445+
ConditionExpression = expression
446+
} // end if names
447+
448+
} // end if conditions
449+
450+
395451
// Check for required fields
396452
Object.keys(required).forEach(field =>
397453
required[field] && !data[field] && error(`'${field}' is a required field`)
@@ -555,7 +611,7 @@ class Entity {
555611
).trim()
556612

557613
// Merge attribute values
558-
const attr_values = Object.assign(values,ExpressionAttributeValues)
614+
ExpressionAttributeValues = Object.assign(values,ExpressionAttributeValues)
559615

560616
// Generate the payload
561617
const payload = Object.assign(
@@ -566,13 +622,16 @@ class Entity {
566622
ExpressionAttributeNames: Object.assign(names,ExpressionAttributeNames)
567623
},
568624
typeof params === 'object' ? params : {},
569-
Object.keys(attr_values).length > 0 ? { ExpressionAttributeValues: attr_values } : {}
625+
Object.keys(ExpressionAttributeValues).length > 0 ? { ExpressionAttributeValues } : {},
626+
ConditionExpression ? { ConditionExpression } : {}
570627
) // end assign
571628

572629
// console.log(payload)
573630

574631
return payload
575632

633+
// TODO: Check why primary/secondary GSIs are using if_not_exists
634+
576635
} // end updateSync
577636

578637

@@ -607,6 +666,65 @@ class Entity {
607666
// Merge defaults
608667
const data = normalizeData(this.DocumentClient)(schema.attributes,linked,Object.assign({},defaults,item))
609668

669+
// Extract valid options
670+
const {
671+
conditions, // ConditionExpression
672+
capacity, // ReturnConsumedCapacity (none, total, or indexes)
673+
metrics, // ReturnItemCollectionMetrics: (size or none)
674+
returnValues, // Return Values (none, all_old, updated_old, all_new, updated_new)
675+
..._args
676+
} = options
677+
678+
// Remove other valid options from options
679+
const args = Object.keys(_args).filter(x => !['execute','parse'].includes(x))
680+
681+
// Error on extraneous arguments
682+
if (args.length > 0)
683+
error(`Invalid delete options: ${args.join(', ')}`)
684+
685+
// Verify metrics
686+
if (metrics !== undefined
687+
&& (typeof metrics !== 'string' || !['NONE','SIZE'].includes(metrics.toUpperCase())))
688+
error(`'metrics' must be one of 'NONE' OR 'SIZE'`)
689+
690+
// Verify capacity
691+
if (capacity !== undefined
692+
&& (typeof capacity !== 'string' || !['NONE','TOTAL','INDEXES'].includes(capacity.toUpperCase())))
693+
error(`'capacity' must be one of 'NONE','TOTAL', OR 'INDEXES'`)
694+
695+
// Verify returnValues
696+
// TODO: Check this, conflicts with dynalite
697+
if (returnValues !== undefined
698+
&& (typeof returnValues !== 'string'
699+
|| !['NONE', 'ALL_OLD', 'UPDATED_OLD', 'ALL_NEW', 'UPDATED_NEW'].includes(returnValues.toUpperCase())))
700+
error(`'returnValues' must be one of 'NONE', 'ALL_OLD', 'UPDATED_OLD', 'ALL_NEW', or 'UPDATED_NEW'`)
701+
702+
let ExpressionAttributeNames // init ExpressionAttributeNames
703+
let ExpressionAttributeValues // init ExpressionAttributeValues
704+
let ConditionExpression // init ConditionExpression
705+
706+
// If conditions
707+
if (conditions) {
708+
709+
// Parse the conditions
710+
const {
711+
expression,
712+
names,
713+
values
714+
} = parseConditions(conditions,this.table,this.name)
715+
716+
if (Object.keys(names).length > 0) {
717+
718+
// TODO: alias attribute field names
719+
// Add names, values and condition expression
720+
ExpressionAttributeNames = names
721+
ExpressionAttributeValues = values
722+
ConditionExpression = expression
723+
} // end if names
724+
725+
} // end if filters
726+
727+
610728
// Check for required fields
611729
Object.keys(required).forEach(field =>
612730
required[field] !== undefined && !data[field] && error(`'${field}' is a required field`)
@@ -631,6 +749,12 @@ class Entity {
631749
}) : acc
632750
},{})
633751
},
752+
ExpressionAttributeNames ? { ExpressionAttributeNames } : null,
753+
ExpressionAttributeValues ? { ExpressionAttributeValues } : null,
754+
ConditionExpression ? { ConditionExpression } : null,
755+
capacity ? { ReturnConsumedCapacity: capacity.toUpperCase() } : null,
756+
metrics ? { ReturnItemCollectionMetrics: metrics.toUpperCase() } : null,
757+
returnValues ? { ReturnValues: returnValues.toUpperCase() } : null,
634758
typeof params === 'object' ? params : {}
635759
)
636760

0 commit comments

Comments
 (0)