Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

dynamodb-toolbox

Package Overview
Dependencies
Maintainers
1
Versions
151
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

dynamodb-toolbox - npm Package Compare versions

Comparing version 0.2.0-alpha to 0.2.0-beta

2

__tests__/bootstrap-tests.js

@@ -36,4 +36,4 @@ // Load simple table creation parameters

region: 'us-east-1',
credentials: new AWS.SharedIniFileCredentials({profile: ''}),
credentials: new AWS.SharedIniFileCredentials({profile: 'madlucas'}),
// convertEmptyValues: false
})

@@ -73,2 +73,19 @@ const { Table, Entity } = require('../index')

const TestTable3 = new Table({
name: 'TestTable3',
partitionKey: 'pk',
sortKey: 'sk',
DocumentClient
})
const TestEntity4 = new Entity({
name: 'TestEntity4',
attributes: {
id: { partitionKey: true},
sk: { hidden: true, sortKey: true, default: (data) => data.id },
test: { alias: 'xyz' }
},
table: TestTable3
});
describe('put',()=>{

@@ -370,2 +387,10 @@

it('correctly aliases pks', () => {
let { Item } = TestEntity4.putParams({ id: 3, xyz: '123' })
expect(Item.sk).toBe('3')
// expect(TableName).toBe('test-table')
})
})

@@ -8,3 +8,6 @@ const { Table, Entity } = require('../index')

sortKey: 'sk',
DocumentClient
DocumentClient,
indexes: {
GSI1: { partitionKey: 'GSI1pk' }
}
})

@@ -82,3 +85,17 @@

const TestEntityGSI = new Entity({
name: 'TestEntityGSI',
autoExecute: false,
attributes: {
email: { type: 'string', partitionKey: true },
sk: { type: 'string', sortKey: true },
test: { type: 'string' },
test2: { type: 'string' },
GSI1pk: { partitionKey: 'GSI1'}
},
timestamps: false,
table: TestTable
})
describe('update',()=>{

@@ -101,3 +118,8 @@

it('creates update with GSI', () => {
let { TableName, Key, UpdateExpression, ExpressionAttributeNames, ExpressionAttributeValues } = TestEntityGSI.updateParams({ pk: 'test-pk', sk: 'test-sk', GSI1pk: 'test' })
expect(UpdateExpression).toBe('SET #_et = if_not_exists(#_et,:_et), #GSI1pk = :GSI1pk')
})
it('creates update with multiple fields (default types)', () => {

@@ -104,0 +126,0 @@ let { TableName, Key, UpdateExpression, ExpressionAttributeNames, ExpressionAttributeValues } = TestEntity.updateParams({

@@ -31,3 +31,9 @@ const formatItem = require('../lib/formatItem')

linked1: ['sk',0, { save: false }],
linked2: ['sk',1, { save: false }]
linked2: ['sk',1, { save: false }],
composite1: { type: 'string', alias: 'composite1_alias' },
linked3: ['composite1',0, { save: false }],
linked4: ['composite1',1, { save: false, alias: 'linked4_alias' }],
composite2_alias: { type: 'string', map: 'composite2' },
linked5: ['composite2_alias',0, { save: false, }],
linked6: ['composite2_alias',1, { save: false, alias: 'linked6_alias' }],
}

@@ -87,2 +93,17 @@ })

it('formats item with linked aliased composite field', () => {
let result = formatItem(DocumentClient)(DefaultTable.User.schema.attributes,DefaultTable.User.linked,{ composite1: 'test1#test2' })
expect(result).toEqual({ composite1_alias: 'test1#test2', linked3: 'test1', linked4_alias: 'test2' })
})
it('formats item with linked mapped composite field', () => {
let result = formatItem(DocumentClient)(DefaultTable.User.schema.attributes,DefaultTable.User.linked,{ composite2: 'test1#test2' })
expect(result).toEqual({ composite2_alias: 'test1#test2', linked5: 'test1', linked6_alias: 'test2' })
})
it('passes through attribute not specified in entity', () => {
let result = formatItem(DocumentClient)(DefaultTable.User.schema.attributes,DefaultTable.User.linked,{ unspecified: 'value' })
expect(result).toEqual({ unspecified: 'value' })
})
})

@@ -32,6 +32,299 @@ // Bootstrap testing

describe('Table creation', ()=> {
describe('Misc Tests (development only)', ()=> {
it('misc tests', async () => {
it('Function default dependency issue #68', async () => {
const { randomBytes } = require('crypto')
const table = new Table({
name: 'test-table',
partitionKey: 'pk',
sortKey: 'sk',
DocumentClient
})
const TestEntity = new Entity({
name: 'Test',
attributes: {
sk: {
sortKey: true,
// default: (data) => data.id,
default: (data) => `${data.id}---${data.test}---${data.modified}---${data.test3}`,
prefix:'SK#',
// dependsOn: 'id'
dependsOn: ['id','test','modified']//,'test3']
},
id: {
partitionKey: true,
default: (data) => `${randomBytes(32).toString('hex')}`,
prefix:'PK#',
// dependsOn: 'sk'
},
test: {
default: (data) => data.test2,
dependsOn: 'test2'
},
test2: { default: () => Math.random() },
test3: { default: 'STATICDEFAULT', map: 'testMap' },
test4: { default: (data) => data.sk + '###' + Math.random(), alias: 'test4alias' },
other: {}
// id: {
// partitionKey: true,
// prefix: 'USER_ID#',
// suffix:'#####SUFFIX',
// default: (data) => randomBytes(32).toString('hex'),// + ' ' + data.test,
// transform: (val) => val.toUpperCase(),
// // dependsOn: 'test'
// },
// sk: {
// sortKey: true,
// prefix: 'USER_ID#',
// default: (data) => `~~~${data.id}~~~#test#${data.test2}`,
// dependsOn: 'id'
// },
// test2: {
// default: (data) => `----ID2:${data.test}----`,
// dependsOn: 'test'
// },
// test3: {
// default: () => Math.random()
// },
// test: {
// default: (data) => `----TEST3:${data.test3}----`,
// dependsOn: 'test3'
// }
},
table
})
// console.log(TestEntity.schema.attributes);
console.log(
TestEntity.putParams({
other: 'test data',
test2: 'override test2',
test: 'overide test value'
})
)
})
it.skip('put fails when a field is required and mapped with map: \'destination\' #63', async () => {
const table = new Table({
name: 'test-table',
partitionKey: 'pk',
sortKey: 'sk',
DocumentClient
})
const TestEntity = new Entity({
name: 'Test',
attributes: {
email: { partitionKey: true, type: 'string' },
sk: { sortKey: true, type: 'string' }
},
table
})
console.log(
TestEntity.putParams({
pk: 'test',
sk: 'testsk'
}, {
conditions: [
{ attr: 'pk', exists: false },
{ attr: 'sk', exists: false }
]
})
)
})
it.skip('put fails when a field is required and mapped with map: \'destination\' #63', async () => {
const table = new Table({
name: 'test-table',
partitionKey: 'pk',
// sortKey: 'sk',
DocumentClient
})
const TestEntity = new Entity({
name: 'Test',
attributes: {
email: { partitionKey: true, type: 'string' },
// sk: { sortKey: true, type: 'string' },
mappedRequired: { required: 'always', map: 'GSIpk' },
},
table
})
console.log(
await TestEntity.updateParams({
email: 'test',
mappedRequired: 'test'
})
)
})
it.skip('Batch writes does not support conditions #65', async () => {
const table = new Table({
name: 'dynamodb-toolbox-test-test-table',
partitionKey: 'pk',
sortKey: 'sk',
DocumentClient: DocumentClient2
})
const TestEntity = new Entity({
name: 'Test',
attributes: {
pk: {
partitionKey: true,
type: 'string'
},
sk: {
sortKey: true,
type: 'string'
},
test: {}
},
table
})
console.log(
JSON.stringify(
await table.batchWrite([
TestEntity.putBatch(
{
pk: 'test',
sk: 'testsk',
test: 'some value'
},
{
// conditions: [{ attr: 'pk', exists: false }]
conditions: [{ attr: 'modified', lt: '020-07-10T16:40:50.575Z' }]
}
)
],
{ execute: false }
)
,null,2)
)
// console.log(await TestEntity.put(
// {
// pk: 'test',
// sk: 'test2',
// test: 'some value'
// },
// {
// // conditions: [{ attr: 'pk', exists: false }],
// conditions: [{ attr: 'modified', lt: '020-07-10T16:40:50.575Z' }],
// execute: true
// }
// ))
console.log(await table.scan());
})
// let test =
// {
// "RequestItems": {
// "test-table": [
// {
// "PutRequest": {
// "Item": {
// "_ct": "2020-07-10T16:40:57.575Z",
// "_md": "2020-07-10T16:40:57.575Z",
// "_et": "Test",
// "pk": "test",
// "sk": "testsk",
// "test": "some value"
// },
// "ExpressionAttributeNames": {
// "#attr1": "pk"
// },
// "ConditionExpression": "attribute_not_exists(#attr1)"
// }
// }
// ]
// }
// }
it.skip('Auto-generated property "entity" interferes with update #66', async () => {
const FoosTable = new Table({
name: 'test-table',
partitionKey: 'pk',
sortKey: 'sk',
DocumentClient,
});
const Foos = new Entity({
name: 'Foo',
table: FoosTable,
timestamps: true,
// typeAlias: 'testType',
attributes: {
pk: { hidden: true, partitionKey: true, default: (data) => (`FOO#${data.id}`) },
sk: { hidden: true, sortKey: true, default: (data) => (`FOO#${data.id}`) },
id: { required: 'always' },
name: 'string',
},
});
// console.log(await Foos.scan());
console.log(Foos.schema.attributes);
// console.log(Foos);
//const insertAndUpdate = async () => {
const foo = { id: 'abc', name: 'Alfred' };
await Foos.put(foo);
const { Item: fooFromDB } = await Foos.get({ id: foo.id });
console.log(fooFromDB);
fooFromDB.name = 'Bob'
await Foos.update(fooFromDB); // FAILS - "Error: Field 'entity' does not have a mapping or alias"
// };
})
it.skip('misc tests', async () => {
let Default = new Table({

@@ -38,0 +331,0 @@ // name: 'dynamodb-toolbox-test-test-table',

@@ -21,3 +21,3 @@ 'use strict'

// Import error handlers
const { error, transformAttr } = require('../lib/utils')
const { error, transformAttr, isEmpty } = require('../lib/utils')

@@ -57,8 +57,8 @@ // Declare Entity class

// If an entity tracking field is enabled, add the attribute and the default
// If an entity tracking field is enabled, add the attributes, alias and the default
if (table.Table.entityField) {
this.schema.attributes[table.Table.entityField] = {
type: 'string', alias: 'entity', default: this.name
}
this.schema.attributes[table.Table.entityField] = { type: 'string', alias: this._etAlias, default: this.name }
this.defaults[table.Table.entityField] = this.name
this.schema.attributes[this._etAlias] = { type: 'string', map: table.Table.entityField, default: this.name }
this.defaults[this._etAlias] = this.name
} // end if entity tracking

@@ -276,2 +276,3 @@

// Shortcut for batch operations
// Only Key is supported (e.g. no conditions) https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html
deleteBatch(item={}) {

@@ -351,3 +352,3 @@ const payload = this.deleteParams(item)

ExpressionAttributeNames ? { ExpressionAttributeNames } : null,
ExpressionAttributeValues ? { ExpressionAttributeValues } : null,
!isEmpty(ExpressionAttributeValues) ? { ExpressionAttributeValues } : null,
ConditionExpression ? { ConditionExpression } : null,

@@ -485,3 +486,4 @@ capacity ? { ReturnConsumedCapacity: capacity.toUpperCase() } : null,

Object.keys(required).forEach(field =>
required[field] && !data[field] && error(`'${field}' is a required field`)
required[field] && !data[field]
&& error(`'${field}${this.schema.attributes[field].alias ? `/${this.schema.attributes[field].alias}` : ''}' is a required field`)
) // end required field check

@@ -496,3 +498,2 @@

// Loop through valid fields and add appropriate action

@@ -521,4 +522,6 @@ Object.keys(data).forEach((field) => {

} else if (
!mapping.partitionKey
&& !mapping.sortKey
// !mapping.partitionKey
// && !mapping.sortKey
mapping.partitionKey !== true
&& mapping.sortKey !== true
&& (mapping.save === undefined || mapping.save === true)

@@ -664,3 +667,3 @@ && (!mapping.link || (mapping.link && mapping.save === true))

typeof params === 'object' ? params : {},
Object.keys(ExpressionAttributeValues).length > 0 ? { ExpressionAttributeValues } : {},
!isEmpty(ExpressionAttributeValues) ? { ExpressionAttributeValues } : {},
ConditionExpression ? { ConditionExpression } : {},

@@ -702,2 +705,3 @@ capacity ? { ReturnConsumedCapacity: capacity.toUpperCase() } : null,

// Shortcut for batch operations
// Only Item is supported (e.g. no conditions) https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html
putBatch(item={}) {

@@ -712,3 +716,3 @@ const payload = this.putParams(item)

const { schema, defaults, required, linked, _table } = this
// Initialize validateType with the DocumentClient

@@ -720,2 +724,5 @@ const validateType = validateTypes(this.DocumentClient)

// console.log(data);
// Extract valid options

@@ -781,4 +788,5 @@ const {

// Check for required fields
Object.keys(required).forEach(field =>
required[field] !== undefined && !data[field] && error(`'${field}' is a required field`)
Object.keys(required).forEach(field =>
required[field] !== undefined && !data[field]
&& error(`'${field}${this.schema.attributes[field].alias ? `/${this.schema.attributes[field].alias}` : ''}' is a required field`)
) // end required field check

@@ -807,3 +815,3 @@

ExpressionAttributeNames ? { ExpressionAttributeNames } : null,
ExpressionAttributeValues ? { ExpressionAttributeValues } : null,
!isEmpty(ExpressionAttributeValues) ? { ExpressionAttributeValues } : null,
ConditionExpression ? { ConditionExpression } : null,

@@ -810,0 +818,0 @@ capacity ? { ReturnConsumedCapacity: capacity.toUpperCase() } : null,

@@ -785,4 +785,4 @@ 'use strict'

result.UnprocessedKeys && Object.keys(result.UnprocessedKeys).length > 0 ? {
next: () => {
const nextResult = this.DocumentClient.batchGet(Object.assign(
next: async () => {
const nextResult = await this.DocumentClient.batchGet(Object.assign(
{ RequestItems: result.UnprocessedKeys },

@@ -959,7 +959,7 @@ options.capacity ? { ReturnConsumedCapacity: options.capacity.toUpperCase() } : null

result,
// If UnprocessedKeys, return a next function
result.UnprocessedKeys && Object.keys(result.UnprocessedKeys).length > 0 ? {
next: () => {
const nextResult = this.DocumentClient.batchWrite(Object.assign(
{ RequestItems: result.UnprocessedKeys },
// If UnprocessedItems, return a next function
result.UnprocessedItems && Object.keys(result.UnprocessedItems).length > 0 ? {
next: async () => {
const nextResult = await this.DocumentClient.batchWrite(Object.assign(
{ RequestItems: result.UnprocessedItems },
options.capacity ? { ReturnConsumedCapacity: options.capacity.toUpperCase() } : null,

@@ -966,0 +966,0 @@ options.metrics ? { ReturnItemCollectionMetrics: options.metrics.toUpperCase() } : null

@@ -21,5 +21,5 @@ 'use strict'

return Object.keys(item).reduce((acc,field) => {
if (linked[field] || linked[attributes[field].alias]) {
Object.assign(acc, (linked[field] || linked[attributes[field].alias]).reduce((acc,f,i) => {
const link = linked[field] || attributes[field] && attributes[field].alias && linked[attributes[field].alias]
if (link) {
Object.assign(acc, link.reduce((acc,f,i) => {
if (attributes[f].save || attributes[f].hidden || (include.length > 0 && !include.includes(f))) return acc

@@ -42,3 +42,3 @@ return Object.assign(acc,{

[(attributes[field] && attributes[field].alias) || field]: (
attributes[field].prefix || attributes[field].suffix
attributes[field] && (attributes[field].prefix || attributes[field].suffix)
? item[field]

@@ -45,0 +45,0 @@ .replace(new RegExp(`^${escapeRegExp(attributes[field].prefix)}`),'')

@@ -17,13 +17,64 @@ 'use strict'

const validateType = validateTypes(DocumentClient)
// Build and execute defaults dependency graph
const dependsOn = (map,attr) => {
// If the default depends on other attributes
if (schema[attr].dependsOn) {
(Array.isArray(schema[attr].dependsOn) ? schema[attr].dependsOn : [schema[attr].dependsOn]).forEach(dependent => {
// If the dependent is a valid attribute or alias
if (schema[dependent]) {
// If the dependent is a function
if (typeof map[dependent] === 'function') {
// Resolve the dependency graph
map = dependsOn(map,dependent)
}
} else {
error(`'${dependent}' is not a valid attribute or alias name`)
}
}) // end dependency loop
map[attr] = map[attr](map)
return map
} else {
map[attr] = map[attr](map)
if (schema[attr].alias) map[schema[attr].alias] = map[attr]
if (schema[attr].map) map[schema[attr].map] = map[attr]
return map
}
} // end dependsOn
let _data = Object.keys(data).reduce((acc,field) => {
// Generate normalized data object
let dataMap = Object.keys(data).reduce((acc,field) => {
// Return a map with normalized data and alias references
return Object.assign(acc,
schema[field] ? { [schema[field].map || field] : data[field] }
schema[field] ? {
data: { ...acc.data, [schema[field].map || field] : data[field] },
aliases: { ...acc.aliases, [schema[field].alias || field] : data[field] }
}
: filter ? {} // this will filter out non-mapped fields
: field === '$remove' ? { $remove: data[field] } // support for removes
: field === '$remove' ? { data: { ...acc.data, $remove: data[field] } } // support for removes
: error(`Field '${field}' does not have a mapping or alias`)
)
)
},{ data: {}, aliases: {} })
// Create a combined data object for defaults
let defaultMap = { ...dataMap.data, ...dataMap.aliases }
const defaults = Object.keys(defaultMap).reduce((acc,attr) => {
// If a function, resolve the dependency graph
if (typeof defaultMap[attr] === 'function') {
let map = dependsOn(defaultMap,attr)
defaultMap = map
}
return Object.assign(acc, { [attr]: defaultMap[attr] })
},{})
// Generate final data and evaluate function expressions
let _data = Object.keys(dataMap.data).reduce((acc,field) => {
return Object.assign(acc, {
[field]: defaults[field]
})
},{})
// Process linked

@@ -60,1 +111,22 @@ let composites = Object.keys(linked).reduce((acc,attr) => {

} // end normalizeData
// Generate final data and evaluate function expressions
// let _data = Object.keys(dataMap.data).reduce((acc,field) => {
// return Object.assign(acc, {
// [field]: typeof dataMap.data[field] === 'function' ? dataMap.data[field](defaults) : dataMap.data[field]
// })
// },{})
// console.log(_data)
// map[schema[attr].dependsOn] = typeof map[schema[attr].dependsOn] === 'function' ?
// dependsOn(map,schema[attr].dependsOn) // map[schema[attr].dependsOn](map)
// : map[schema[attr].dependsOn]
// return { map, val: map[attr](map) }
// return dependsOn(map,attr)

@@ -20,2 +20,6 @@ 'use strict'

break
case 'dependsOn':
if (typeof config[prop] !== 'string' && !Array.isArray(config[prop]))
error(`'dependsOn' must be the string name of an attribute or alias`)
break
case 'transform':

@@ -102,4 +106,4 @@ if (typeof config[prop] !== 'function') error(`'${prop}' must be a function`)

// Track required settings
if (config.required === true) track.required[field] = false
if (config.required === 'always') track.required[field] = true
if (config.required === true) track.required[config.map || field] = false
if (config.required === 'always') track.required[config.map || field] = true

@@ -106,0 +110,0 @@

@@ -24,2 +24,5 @@ 'use strict'

// isEmpty object shortcut
module.exports.isEmpty = val => val === undefined || (typeof val === 'object' && Object.keys(val).length === 0)
// Inline error handler

@@ -42,4 +45,3 @@ const error = err => { throw new Error(err) }

// Tranform atribute values
// Transform attribute values
module.exports.transformAttr = (mapping,value,data) => {

@@ -46,0 +48,0 @@ value = mapping.transform ? mapping.transform(value,data) : value

@@ -12,7 +12,7 @@ 'use strict'

// Performs type validation/coercian
module.exports = (DocumentClient) => (mapping,field,input,data={}) => {
module.exports = (DocumentClient) => (mapping,field,value) => {
// Evaluate function expressions
// TODO: should this happen here?
let value = typeof input === 'function' ? input(data) : input
// let value = typeof input === 'function' ? input(data) : input

@@ -19,0 +19,0 @@ // return if undefined or null

{
"name": "dynamodb-toolbox",
"version": "0.2.0-alpha",
"version": "0.2.0-beta",
"description": "A simple set of tools for working with Amazon DynamoDB and the DocumentClient.",

@@ -38,8 +38,8 @@ "author": "Jeremy Daly <jeremy@jeremydaly.com>",

"devDependencies": {
"aws-sdk": "^2.673.0",
"aws-sdk": "^2.713.0",
"coveralls": "^3.1.0",
"dynalite": "^3.1.6",
"dynalite": "^3.2.0",
"eslint": "^6.8.0",
"jest": "^24.9.0"
}
}
}

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc