Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
electrodb
Advanced tools
A library to more easily create and interact with multiple entities and heretical relationships in dynamodb
ElectroDB is a DynamoDB library that simplifies the process of defining and interacting with DynamoDB tables. It provides a high-level abstraction for defining entities, managing relationships, and performing CRUD operations, making it easier to work with DynamoDB's complex data modeling and querying capabilities.
Entity Definition
This feature allows you to define an entity with its attributes and indexes. The code sample demonstrates how to create a User entity with attributes like userId, name, and email, and a primary index.
const { Entity } = require('electrodb');
const UserEntity = new Entity({
model: {
entity: 'User',
version: '1',
service: 'UserService'
},
attributes: {
userId: { type: 'string', required: true },
name: { type: 'string', required: true },
email: { type: 'string', required: true }
},
indexes: {
primary: {
pk: { field: 'pk', composite: ['userId'] },
sk: { field: 'sk', composite: [] }
}
}
});
CRUD Operations
This feature provides methods for performing CRUD operations on the defined entities. The code sample shows how to create, read, update, and delete a user entity.
const user = await UserEntity.put({
userId: '123',
name: 'John Doe',
email: 'john.doe@example.com'
}).go();
const fetchedUser = await UserEntity.get({ userId: '123' }).go();
const updatedUser = await UserEntity.update({ userId: '123' })
.set({ name: 'Jane Doe' })
.go();
const deletedUser = await UserEntity.delete({ userId: '123' }).go();
Querying
This feature allows you to perform complex queries on your entities. The code sample demonstrates how to query the User entity using the primary index.
const users = await UserEntity.query.primary({ userId: '123' }).go();
Relationships
This feature allows you to define and manage relationships between different entities. The code sample shows how to fetch a user along with their related orders using the Service class.
const { Service } = require('electrodb');
const UserService = new Service({
User: UserEntity,
Order: OrderEntity
});
const userWithOrders = await UserService.User.get({ userId: '123' }).include(OrderEntity).go();
DynamoDB Toolbox is a set of tools that makes it easier to work with Amazon DynamoDB. It provides a simple and consistent way to define and interact with DynamoDB tables and items. Compared to ElectroDB, DynamoDB Toolbox offers a more lightweight and flexible approach but may require more manual setup for complex data models.
The AWS SDK for JavaScript provides a comprehensive set of tools for interacting with AWS services, including DynamoDB. While it offers low-level access to DynamoDB's API, it lacks the high-level abstractions and convenience features provided by ElectroDB, making it more suitable for developers who need fine-grained control over their DynamoDB interactions.
Dynogels is a DynamoDB data mapper for Node.js that simplifies the process of defining and interacting with DynamoDB tables. It offers a similar high-level abstraction as ElectroDB but is less actively maintained and may not support some of the latest DynamoDB features.
ElectroDB is a dynamodb library to ease the use of having multiple entities and complex heretical relationships in a single dynamodb table.
This library is a work in progress, please submit issues/feedback or reach out on twitter @tinkertamper.
ExpressionAttributeNames
, ExpressionAttributeValues
.Install from NPM
npm install electrodb --save
Unlike in traditional sql databases, a single DynamoDB table will include multiple entities along side each other. Additionally DynamoDB utilizes Partition and Sort Keys to query records and allow for heretical relationships.
In ElectroDB an Entity
is a single record that represents a single business object. For example, in a simple contact application, one entity might represent a Person and another entity my represent a Contact method for that person (email, phone, etc.).
Require or import Entity
from electrodb
:
const {Entity} = require("electrodb");
Create an Entity's schema
const DynamoDB = require("aws-sdk/clients/dynamodb");
const {Entity} = require("electrodb");
const client = new DynamoDB.DocumentClient();
let model = {
service: "ClientRelationshipApp",
entity: "UserContacts",
table: "ClientContactTable",
version: "1",
attributes: {
clientId: {
type: "string",
required: true,
},
userId: {
type: "string",
required: true,
},
type: {
type: ["email", "phone", "social"],
required: true,
},
value: {
type: "string",
required: true,
},
canContactWeekends: {
type: "boolean",
required: false
},
maxContactFrequency: {
type: "number",
required: true,
default: 5
}
},
indexes: {
contact: {
pk: {
field: "PK",
facets: "value"
},
sk: {
field: "SK",
facets: ["clientId", "userId"]
}
},
clientContact: {
index: "GSI1PK-GSI1SK-Index",
pk: {
field: "GSI1PK",
facets: ["clientId"]
},
sk: {
field: "GSI1SK",
facets: ["value", "userId"]
}
},
userContact: {
index: "GSI2PK-GSI2SK-Index",
pk: {
field: "GSI2PK",
facets: ["userId"]
},
sk: {
field: "GSI2SK",
facets: ["userId", "value"]
}
}
},
filters: {
weekendFrequency: (attributes, max, canContact) => {
let {maxContactFrequency, canContactWeekends} = attributes;
return `
${maxContactFrequency.lte(max)} AND ${canContactWeekends.eq(canContact)}
`
}
}
};
const UserContacts = new Entity(model, {client});
Property | Description |
---|---|
service | Name of the application using the entity, used to namespace all entities |
entity | Name of the entity that is the schema represents |
table | Name of the dynamodb table in aws |
version | (optional) The version number of the schema, used to namespace keys |
attributes | An object containing each attribute that makes up the schema |
indexes | An object containing table indexes, including the values for the table's default Partition Key and Sort Key |
filters | An object containing user defined filter template functions. |
Attributes define an Entity record. The propertyName
represents the value your code will use to represent an attribute.
Pro-Tip: Using the
field
property it is possible to map anAttributeName
to a different field name in your table. This can be useful to utilize existing tables, existing models, or even to reduce record sizes via shorter field names.
attributes: {
<AttributeName>: {
"type": <string|string[]>,
"required": [boolean],
"default": [value|() => value]
"validate": [RegExp|() => boolean]
"field": [string]
"readOnly": [boolean]
"label": [string]
"cast": ["number"|"string"|"boolean"]
}
}
Property | Type | Required | Description |
---|---|---|---|
type | `string | string[]` | yes |
required | boolean | no | Whether or not the value is required when creating a new record. |
default | `value | () => value` | no |
validate | `RegExp | () => boolean` | no |
field | string | no | The name of the attribute as it exists dynamo, if named differently in the schema attributes. Defaults to the AttributeName as defined in the schema. |
readOnly | boolean | no | Prevents update of the property after the record has been created. Attributes used in the composition of the table's primary Partition Key and Sort Key are by read-only by default. |
label | string | no | Used in index composition to prefix key facets. By default, the AttributeName is used as the label. |
cast | `"number" | "string" | "boolean"` |
The indexes object requires at least the definition of the tables natural Partition Key and (if applicable) Sort Key.
Indexes are defined, and later referenced by their accessPatternName
. These defined via a facets
array that is made up of attributes names as listed the model.
indexes: {
<accessPatternName>: {
"pk": {
"field": <string>
"facets": <AttributeName[]>
},
"sk": {
"field": <string>
"facets": <AttributesName[]>
},
"index": [string]
}
}
Property | Type | Required | Description |
---|---|---|---|
pk | object | yes | Configuration for the pk of that index or table |
pk.facets | boolean | no | An array that represents the order in which attributes are concatenated to facets the key (see Facets below for more on this functionality). |
pk.field | string | yes | The name of the attribute as it exists dynamo, if named differently in the schema attributes. |
sk | object | no | Configuration for the sk of that index or table |
sk.facets | boolean | no | An array that represents the order in which attributes are concatenated to facets the key (see Facets below for more on this functionality). |
sk.field | string | yes | The name of the attribute as it exists dynamo, if named differently in the schema attributes. |
index | string | yes | Used only when the Index defined is a Global Secondary Index, this is left blank for the table's primary index. |
A Facet is a segment of a key based on one of the attributes. Facets are concatenated together form either a Partition Key or an Sort Key key, which define an index
.
For example, in the following Access Pattern "locations
" is made up of the facets storeId
, mallId
, buildingId
and unitId
which map to defined attributes in the schema
:
indexes: {
locations: {
pk: {
field: "pk",
facets: ["storeId"]
},
sk: {
field: "sk",
facets: ["mallId", "buildingId", "unitId"]
}
}
}
Building thoughtful indexes can make queries simple and performant. Sometimes you need to filter results down further. By adding Filters to your model you can extend your queries with custom filters. Below is the traditional way you would add a filter to Dynamo's DocumentClient directly along side how you would accomplish the same using a Filter function.
{
IndexName: 'gsi2pk-gsi2sk-index',
TableName: 'electro',
ExpressionAttributeNames: {
'#rent': 'rent',
'#discount': 'discount',
'#pk': 'gsi2pk',
'#sk1': 'gsi2sk'
},
ExpressionAttributeValues: {
':rent1': '2000.00',
':rent2': '5000.00',
':discount1': '1000.00',
':pk': '$mallstoredirectory_1#mallid_eastpointe',
':sk1': '$mallstore#leaseenddate_2020-04-01#rent_',
':sk2': '$mallstore#leaseenddate_2020-07-01#rent_'
},
KeyConditionExpression: '#pk = :pk and #sk1 BETWEEN :sk1 AND :sk2',
FilterExpression: '(#rent between :rent1 and :rent2) AND (#discount <= :discount1)'
}
// Filter object located on the entity model
filters: {
/**
* Filter by low rent a specific mall or a leaseEnd withing a specific range
* @param {Object} attributes - All attributes from the model with methods for each filter operation
* @param {...*} values - Values passed when calling the filter in a query chain.
*/
rentPromotions: function(attributes, minRent, maxRent, promotion) {
let {rent, discount} = attributes;
return `
${rent.between(minRent, maxRent)} AND ${discount.lte(promotion)}
`
}
}
let MallStores = new Entity(model, client);
let mallId = "EastPointe";
let stateDate = "2020-04-01";
let endDate = "2020-07-01";
let maxRent = "5000.00";
let minRent = "2000.00";
let promotion = "1000.00";
let stores = MallStores.query
.stores({ mallId })
.between({ leaseEndDate: stateDate }, { leaseEndDate: endDate })
.rentPromotions(minRent, maxRent, promotion)
.params();
// Results
{
IndexName: 'gsi2pk-gsi2sk-index',
TableName: 'electro',
ExpressionAttributeNames: {
'#rent': 'rent',
'#discount': 'discount',
'#pk': 'gsi2pk',
'#sk1': 'gsi2sk'
},
ExpressionAttributeValues: {
':rent1': '2000.00',
':rent2': '5000.00',
':discount1': '1000.00',
':pk': '$mallstoredirectory_1#mallid_eastpointe',
':sk1': '$mallstore#leaseenddate_2020-04-01#rent_',
':sk2': '$mallstore#leaseenddate_2020-07-01#rent_'
},
KeyConditionExpression: '#pk = :pk and #sk1 BETWEEN :sk1 AND :sk2',
FilterExpression: '(#rent between :rent1 and :rent2) AND (#discount <= :discount1)'
}
let MallStores = new Entity(model, client);
let mallId = "EastPointe";
let stateDate = "2020-04-01";
let endDate = "2020-07-01";
let maxRent = "5000.00";
let minRent = "2000.00";
let promotion = "1000.00";
let stores = MallStores.query
.leases({ mallId })
.between({ leaseEndDate: stateDate }, { leaseEndDate: endDate })
.filter(({rent, discount}) => `
${rent.between(minRent, maxRent)} AND ${discount.lte(promotion)}
`)
.params();
// Results
{
IndexName: 'gsi2pk-gsi2sk-index',
TableName: 'electro',
ExpressionAttributeNames: {
'#rent': 'rent',
'#discount': 'discount',
'#pk': 'gsi2pk',
'#sk1': 'gsi2sk'
},
ExpressionAttributeValues: {
':rent1': '2000.00',
':rent2': '5000.00',
':discount1': '1000.00',
':pk': '$mallstoredirectory_1#mallid_eastpointe',
':sk1': '$mallstore#leaseenddate_2020-04-01#rent_',
':sk2': '$mallstore#leaseenddate_2020-07-01#rent_'
},
KeyConditionExpression: '#pk = :pk and #sk1 BETWEEN :sk1 AND :sk2',
FilterExpression: '(#rent between :rent1 and :rent2) AND (#discount <= :discount1)'
}
Filter functions allow you write a FilterExpression
without having to worry about the complexities of expression attributes. To accomplish this, ElectroDB injects an object attributes
as the first parameter to all Filter Functions. This object contains every Attribute defined in the Entity's Model with the following operators as methods:
operator | example | result |
---|---|---|
gte | rent.gte(maxRent) | ( #rent >= :rent1 ) |
gt | rent.gt(maxRent) | ( #rent > :rent1 ) |
lte | rent.lte(maxRent) | ( #rent <= :rent1 ) |
lt | rent.lt(maxRent) | ( #rent < :rent1 ) |
eq | rent.eq(maxRent) | ( #rent = :rent1) |
begins | rent.begins(maxRent) | ( begins_with(#rent, :rent1) ) |
exists | rent.exists(maxRent) | ( exists(#rent = :rent1) ) |
notExists | rent.notExists(maxRent) | ( not exists(#rent = :rent1) ) |
contains | rent.contains(maxRent) | ( contains(#rent = :rent1) ) |
notContains | rent.notContains(maxRent) | ( not contains(#rent = :rent1) ) |
between | rent.between(minRent, maxRent) | ( #rent between :rent1 and :rent2 ) |
This functionality allows you to write the remaining logic of your FilterExpression
with ease. Add complex nested and
/or
conditions or other FilterExpression
logic while ElectroDB handles the ExpressionAttributeNames
and ExpressionAttributeValues
.
Forming a composite Partition Key and Sort Key is a critical step in planning Access Patterns in DynamoDB. When planning composite keys it is critical to consider the order in which they are composed. As of the time of writing this documentation, DynamoDB has the following constraints that should be taken into account when planning your Access Patterns:
begins_with
, between
, >
, >=
, <
, <=
, and Equals
operator | use case |
---|---|
begins_with | Keys starting with a particular set of characters. |
between | Keys between a specified range. |
eq | Keys equal to some value |
gt | Keys less than some value |
gte | Keys less than or equal to some value |
lt | Keys greater than some value |
lte | Keys greater than or equal to some value |
Carefully considering your Facet order will allow *ElectroDB to express hierarchical relationships and unlock more available Access Patterns for your application.
Carefully considering your Facet order will allow *ElectroDB to express hierarchical relationships and unlock more available Access Patterns for your application.
For example, let's say you have a MallStore
Entity that represents Store Locations inside Malls:
let model = {
service: "MallStoreDirectory",
entity: "MallStore",
table: "StoreDirectory",
version: "1",
attributes: {
mallId: {
type: "string",
required: true,
},
storeId: {
type: "string",
required: true,
},
buildingId: {
type: "string",
required: true,
},
unitId: {
type: "string",
required: true,
},
category: {
type: [
"spite store",
"food/coffee",
"food/meal",
"clothing",
"electronics",
"department",
"misc"
],
required: true
},
leaseEndDate: {
type: "string",
required: true
},
rent: {
type: "string",
required: true,
validate: /^(\d+\.\d{2})$/
},
discount: {
type: "string",
required: false,
default: "0.00",
validate: /^(\d+\.\d{2})$/
}
},
indexes: {
stores: {
pk: {
field: "pk",
facets: ["storeId"]
},
sk: {
field: "sk",
facets: ["mallId", "buildingId", "unitId"]
}
},
malls: {
index: "gsi1pk-gsi1sk-index",
pk: {
field: "gsi1pk",
facets: ["mallId"]
},
sk: {
field: "gsi1sk",
facets: ["buildingId", "unitId", "storeId"]
}
},
leases: {
index: "gsi2pk-gsi2sk-index",
pk: {
field: "gsi3pk",
facets: ["mallId"]
},
sk: {
field: "gsi3sk",
facets: ["leaseEndDate", "storeId", "buildingId", "unitId"]
}
}
},
filters: {
byCategory: ({category}, name) => category.eq(name),
rentDiscount: (attributes, discount, max, min) => {
return `${attributes.discount.lte(discount)} AND ${attributes.rent.between(max, min)}`
}
}
};
Each record represents one Store location, all stores related to Malls we manage.
To satisfy requirements for searching based on location, you could use the following keys: Each MallStore
record would have a Partition Key with the store's storeId
. This key alone is not enough to identify a particular store. Now lets compose a Sort Key for the store's location attribute ordered hierarchically (mall/building/unit): ["mallId", "buildingId", "unitId"]
.
The MallStore
entity above, using just the stores
Index can now enables four Access Patterns:
LatteLarrys
locations in all MallsLatteLarrys
locations in one MallLatteLarrys
locations inside a specific MallLatteLarrys
inside of a Mall and BuildingQueries are in ElectroDB are built around the Access Patterns defined in the Schema and are capable of using partial key Facets to create performant lookups. To accomplish this, ElectroDB offers a predictable chainable API.
Examples in this section using the
MallStore
schema defined above.
The methods: Get (get
), Create (put
), Update (update
), and Delete (delete
) *require all facets described in the Entities' primary PK
and SK
.
Get
MethodProvide all facets in an object to the get
method
let storeId = "LatteLarrys";
let mallId = "EastPointe";
let buildingId = "BuildingA1";
let unitId = "B47";
await StoreLocations.get({storeId, mallId, buildingId, unitId}).go();
Delete
MethodProvide all facets in an object to the delete
method to delete a record
let storeId = "LatteLarrys";
let mallId = "EastPointe";
let buildingId = "BuildingA1";
let unitId = "B47";
await StoreLocations.delete({storeId, mallId, buildingId, unitId}).go();
Put
RecordProvide all required Attributes as defined in the model to create a new record. ElectroDB will enforce any defined defined validations, defaults, casting, and field aliasing.
let store = {
storeId: "LatteLarrys",
mallId: "EastPointe",
buildingId: "BuildingA1",
unitId: "B47",
category: "food/coffee",
leaseEndDate: "2020-03-22"
}
await StoreLocations.put(store).go();
Update
RecordTo update a record, pass all facets to the update method and then pass set
attributes that need to be updated.
Note: If your update includes changes to an attribute that is also a facet for a global secondary index, you must provide all facets for that index.
let storeId = "LatteLarrys";
let mallId = "EastPointe";
let buildingId = "BuildingA1";
let unitId = "B47";
let category = "food/meal";
await StoreLocations
.update({storeId, mallId, buildingId, unitId})
.set({category})
.go();
Query
RecordsExamples in this section using the
MallStore
schema defined above.
All queries start from the Access Pattern defined in the schema.
const MallStore = new Entity(schema);
// Each Access Pattern is available on the Entity instance
// MallStore.stores()
// MallStore.malls()
All queries require (at minimum) the Facets included in its defined Partition Key. They can be supplied in the order they are composed or in a single object when invoking the Access Pattern.
const MallStore = new Entity(schema);
// stores
// pk: ["storeId"]
// sk: ["mallId", "buildingId", "unitId"]
let storeId = "LatteLarrys";
let mallId = "EastPointe";
// Good: As an object
MallStore.stores({storeId});
// Bad: Facets missing, will throw
MallStore.stores(); // err: Params passed to ENTITY method, must only include storeId
// Bad: Facets not included, will throw
MallStore.stores({mallId}); // err: Params passed to ENTITY method, must only include storeId
After invoking the Access Pattern with the required Partition Key Facets, you can now choose what Sort Key Facets are applicable to your query. Examine the table in Sort Key Operations for more information on the available operations on a Sort Key.
.go() and .params()
Lastly, all query chains end with either a .go()
or a .params()
method invocation. These will either execute the query to DynamoDB (.go()
) or return formatted parameters for use with the DynamoDB docClient (.params()
).
Both .params()
and .go()
take a query configuration object which is detailed more in the section Query Options.
.params()
The params
method ends a query chain, and synchronously formats your query into an object ready for the DynamoDB docClient.
For more information on the options available in the
config
object, checkout the section Query Options.
let config = {};
let stores = MallStores.query
.leases({ mallId })
.between(
{ leaseEndDate: "2020-06-01" },
{ leaseEndDate: "2020-07-31" })
.filter(attr) => attr.rent.lte("5000.00"))
.params(config);
// Result:
{
IndexName: 'gsi2pk-gsi2sk-index',
TableName: 'electro',
ExpressionAttributeNames: { '#rent': 'rent', '#pk': 'gsi2pk', '#sk1': 'gsi2sk' },
ExpressionAttributeValues: {
':rent1': '5000.00',
':pk': '$mallstoredirectory_1#mallid_eastpointe',
':sk1': '$mallstore#leaseenddate_2020-06-01#rent_',
':sk2': '$mallstore#leaseenddate_2020-07-31#rent_'
},
KeyConditionExpression: '#pk = :pk and #sk1 BETWEEN :sk1 AND :sk2',
FilterExpression: '( #rent <= :rent1 )'
}
.go()
The go
method ends a query chain, and asynchronously queries DynamoDB with the client
provided in the model.
For more information on the options available in the
config
object, checkout the section Query Options.
let config = {};
let stores = MallStores.query
.leases({ mallId })
.between(
{ leaseEndDate: "2020-06-01" },
{ leaseEndDate: "2020-07-31" })
.filter(({rent}) => rent.lte("5000.00"))
.go(config);
Below are all chain possibilities available given the MallStore model.
// leases
// pk: ["mallId"]
// sk: ["buildingId", "unitId", "storeId"]
let mallId = "EastPointe";
// begins_with
MallStore.query.leases({mallId}).go()
MallStore.query.leases({mallId, leaseEndDate: "2020-03"}}).go();
MallStore.query.leases({mallId, leaseEndDate: "2020-03-22", rent: "2000.00"}).go();
// gt, gte, lt, lte
MallStore.query.leases({mallId}).gt({leaseEndDate}).go();
MallStore.query.leases({mallId}).gte({leaseEndDate}).go();
MallStore.query.leases({mallId}).lt({leaseEndDate}).go();
MallStore.query.leases({mallId}).lte({leaseEndDate}).go();
// between
MallStore.query.leases({mallId}).between({leaseEndDate: "2020-03"}, {leaseEndDate: "2020-04"}).go();
// filters -- applied after any of the sort key operators above
let june = "2020-06";
let july = "2020-07";
let discount = "500.00";
let maxRent = "2000.00";
let minRent = "5000.00";
MallStore.query.leases({mallId, leaseEndDate: june}).rentDiscount(discount, maxRent, minRent).go();
MallStore.query.leases({mallId}).between({leaseEndDate: june}, {leaseEndDate: july}).byCategory("food/coffee").go();
By default ElectroDB enables you to work with records as the names and properties defined in the model. Additionally it removes the need to deal directly with the docClient parameters which can be complex for a team without as much experience with DynamoDB. The Query Options object can be passed to both the .params()
and .go()
methods when building you query. Below are the options available:
let options = {
params: [object],
raw: [boolean],
includeKeys: [boolean],
originalErr: [boolean],
};
Option | Description |
---|---|
params | Properties added to this object will be merged onto the params sent to the document client. Any conflicts with ElectroDB will favor the params specified here. |
raw | Returns query results as they were returned by the docClient. |
includeKeys | By default ElectroDB does not return partition, sort, or global keys in its response. |
originalErr | By default ElectroDB alters stacktrace any returned exceptions received from the client to give better visibility to the developer. Set this value equal to true to turn off this functionality and return the error unchanged. |
For an example lets look at the needs of application used to manage Shopping Mall properties. The application assists employees in the day-to-day operations of multiple Shopping Malls
Create a new Entity using the MallStore
schema defined above
const DynamoDB = require("aws-sdk/clients/dynamodb");
const client = new DynamoDB.DocumentClient();
const MallStore = new Entity(model, {client});
PUT
Recordawait MallStore.create({
mallId: "EastPointe",
storeId: "LatteLarrys",
buildingId: "BuildingA1",
unitId: "B47",
category: "spite store",
leaseEndDate: "2020-02-29",
rent: "5000.00",
}).go();
Returns the following:
{
mallId: "EastPointe",
storeId: "LatteLarrys",
buildingId: "BuildingA1",
unitId: "B47",
category: "spite store",
leaseEndDate: "2020-02-29",
rent: "5000.00",
discount: "0.00",
}
UPDATE
RecordWhen updating a record, you must include all Facets associated with the table's primary PK and SK.
let storeId = "LatteLarrys";
let mallId = "EastPointe";
let buildingId = "BuildingA1";
let unitId = "B47";
await StoreLocations.update({storeId, mallId, buildingId, unitId}).set({
leaseEndDate: "2021-02-28"
}).go();
Returns the following:
{
leaseEndDate: "2021-02-28"
}
GET
RecordWhen retrieving a specific record, you must include all Facets associated with the table's primary PK and SK.
let storeId = "LatteLarrys";
let mallId = "EastPointe";
let buildingId = "BuildingA1";
let unitId = "B47";
await StoreLocations.get({storeId, mallId, buildingId, unitId}).go();
Returns the following:
{
mallId: "EastPointe",
storeId: "LatteLarrys",
buildingId: "BuildingA1",
unitId: "B47",
category: "spite store",
leaseEndDate: "2021-02-28",
rent: "5000.00",
discount: "0.00"
}
DELETE
RecordWhen removing a specific record, you must include all Facets associated with the table's primary PK and SK.
let storeId = "LatteLarrys";
let mallId = "EastPointe";
let buildingId = "BuildingA1";
let unitId = "B47";
let storeId = "LatteLarrys";
await StoreLocations.delete({storeId, mallId, buildingId, unitId}).go();
Returns the following:
{}
Query
RecordsAll Stores in a particular mall (Requirement #1)
let mallId = "EastPointe";
let stores = await StoreLocations.malls({mallId}).query().go();
All Stores in a particular mall building (Requirement #1)
let mallId = "EastPointe";
let buildingId = "BuildingA1";
let stores = await StoreLocations.malls({mallId}).query({buildingId}).go();
What store is located in unit "B47"? (Requirement #1)
let mallId = "EastPointe";
let buildingId = "BuildingA1";
let unitId = "B47";
let stores = await StoreLocations.malls({mallId}).query({buildingId, unitId}).go();
Stores by Category at Mall (Requirement #2)
let mallId = "EastPointe";
let category = "food/coffee";
let stores = await StoreLocations.malls({mallId}).byCategory(category).go();
Stores by upcoming lease (Requirement #3)
let mallId = "EastPointe";
let q2StartDate = "2020-04-01";
let stores = await StoreLocations.leases({mallId}).lt({leaseEndDate: q2StateDate}).go();
Stores will renewals for Q4 (Requirement #3)
let mallId = "EastPointe";
let q4StartDate = "2020-10-01";
let q4EndDate = "2020-12-31";
let stores = await StoreLocations.leases(mallId)
.between ({leaseEndDate: q4StartDate}, {leaseEndDate: q4EndDate})
.go();
Spite-stores with release renewals this year (Requirement #3)
let mallId = "EastPointe";
let yearStarDate = "2020-01-01";
let yearEndDate = "2020-12-31";
let storeId = "LatteLarrys";
let stores = await StoreLocations.leases(mallId)
.between ({leaseEndDate: yearStarDate}, {leaseEndDate: yearEndDate})
.filter(attr => attr.category.eq("Spite Store"))
.go();
All Latte Larry's in a particular mall building (crazy for any store except a coffee shop)
let mallId = "EastPointe";
let buildingId = "BuildingA1";
let unitId = "B47";
let storeId = "LatteLarrys";
let stores = await StoreLocations.malls({mallId}).query({buildingId, storeId}).go();
FAQs
A library to more easily create and interact with multiple entities and heretical relationships in dynamodb
The npm package electrodb receives a total of 411,753 weekly downloads. As such, electrodb popularity was classified as popular.
We found that electrodb demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.