electrodb
Advanced tools
Comparing version 0.9.0 to 0.9.1
{ | ||
"name": "electrodb", | ||
"version": "0.9.00", | ||
"version": "0.9.1", | ||
"description": "A library to more easily create and interact with multiple entities and heretical relationships in dynamodb", | ||
@@ -8,3 +8,4 @@ "main": "index.js", | ||
"test": "mocha ./test/offline**.spec.js", | ||
"test-all": "mocha ./test/**.spec.js" | ||
"test-all": "mocha ./test/**.spec.js", | ||
"coverage": "nyc npm run test-all && nyc report --reporter=text-lcov | coveralls" | ||
}, | ||
@@ -24,3 +25,7 @@ "repository": { | ||
"chai": "^4.2.0", | ||
"coveralls": "^3.0.13", | ||
"istanbul": "^0.4.5", | ||
"jest": "^25.4.0", | ||
"mocha": "7.1.1", | ||
"mocha-lcov-reporter": "^1.3.0", | ||
"moment": "^2.24.0", | ||
@@ -27,0 +32,0 @@ "nyc": "^15.0.0", |
@@ -15,3 +15,3 @@ | ||
- **Simple Filter Composition**: Easily create complex readable filters for Dynamo queries without worrying about the implementation of `ExpressionAttributeNames`, `ExpressionAttributeValues`. | ||
- **Easily Cross Entity Queries**: Define "collections" to create powerful/peformant queries that return multiple entities in a single request. | ||
- **Easily Query Across Entities**: Define "collections" to create powerful/peformant queries that return multiple entities in a single request. | ||
@@ -159,3 +159,3 @@ Turn this: | ||
```javascript | ||
const {Entity, Service} = require("electrodb"); | ||
const {Entity} = require("electrodb"); | ||
``` | ||
@@ -166,2 +166,8 @@ | ||
Require or import `Service` from `electrodb`: | ||
```javascript | ||
const {Service} = require("electrodb"); | ||
``` | ||
## Model | ||
@@ -168,0 +174,0 @@ |
@@ -5,11 +5,11 @@ const { QueryTypes, MethodTypes } = require("./types"); | ||
index: { | ||
action(entity, state = {}, facets = {}) { | ||
// todo: maybe all key info is passed on the subsequent query identifiers? | ||
// todo: look for article/list of all dynamodb query limitations | ||
return state; | ||
}, | ||
// action(entity, state, facets = {}) { | ||
// // todo: maybe all key info is passed on the subsequent query identifiers? | ||
// // todo: look for article/list of all dynamodb query limitations | ||
// // return state; | ||
// }, | ||
children: ["get", "delete", "update", "query", "put", "scan", "collection"], | ||
}, | ||
collection: { | ||
action(entity, state = {}, collection = "", facets = {}) { | ||
action(entity, state, collection /* istanbul ignore next */ = "", facets /* istanbul ignore next */ = {}) { | ||
state.query.keys.pk = entity._expectFacets(facets, state.query.facets.pk); | ||
@@ -32,3 +32,3 @@ entity._expectFacets(facets, Object.keys(facets), `"query" facets`); | ||
get: { | ||
action(entity, state = {}, facets = {}) { | ||
action(entity, state, facets = {}) { | ||
state.query.keys.pk = entity._expectFacets(facets, state.query.facets.pk); | ||
@@ -52,3 +52,3 @@ state.query.method = MethodTypes.get; | ||
delete: { | ||
action(entity, state = {}, facets = {}) { | ||
action(entity, state, facets = {}) { | ||
state.query.keys.pk = entity._expectFacets(facets, state.query.facets.pk); | ||
@@ -72,3 +72,3 @@ state.query.method = MethodTypes.delete; | ||
put: { | ||
action(entity, state = {}, payload = {}) { | ||
action(entity, state, payload = {}) { | ||
let record = entity.model.schema.checkCreate({ ...payload }); | ||
@@ -95,3 +95,3 @@ state.query.keys.pk = entity._expectFacets(record, state.query.facets.pk); | ||
update: { | ||
action(entity, state = {}, facets = {}) { | ||
action(entity, state, facets = {}) { | ||
state.query.keys.pk = entity._expectFacets(facets, state.query.facets.pk); | ||
@@ -115,3 +115,3 @@ state.query.method = MethodTypes.update; | ||
set: { | ||
action(entity, state = {}, data) { | ||
action(entity, state, data) { | ||
let record = entity.model.schema.checkUpdate({ ...data }); | ||
@@ -128,3 +128,3 @@ state.query.update.set = Object.assign( | ||
query: { | ||
action(entity, state = {}, facets = {}, options = {}) { | ||
action(entity, state, facets = {}, options = {}) { | ||
state.query.keys.pk = entity._expectFacets(facets, state.query.facets.pk); | ||
@@ -144,3 +144,3 @@ entity._expectFacets(facets, Object.keys(facets), `"query" facets`); | ||
between: { | ||
action(entity, state = {}, startingFacets = {}, endingFacets = {}) { | ||
action(entity, state, startingFacets = {}, endingFacets = {}) { | ||
entity._expectFacets( | ||
@@ -178,3 +178,3 @@ startingFacets, | ||
gt: { | ||
action(entity, state = {}, facets = {}) { | ||
action(entity, state, facets = {}) { | ||
entity._expectFacets(facets, Object.keys(facets), `"gt" facets`); | ||
@@ -192,3 +192,3 @@ state.query.type = QueryTypes.gt; | ||
gte: { | ||
action(entity, state = {}, facets = {}) { | ||
action(entity, state, facets = {}) { | ||
entity._expectFacets(facets, Object.keys(facets), `"gte" facets`); | ||
@@ -206,3 +206,3 @@ state.query.type = QueryTypes.gte; | ||
lt: { | ||
action(entity, state = {}, facets = {}) { | ||
action(entity, state, facets = {}) { | ||
entity._expectFacets(facets, Object.keys(facets), `"lt" facets`); | ||
@@ -220,3 +220,3 @@ state.query.type = QueryTypes.lt; | ||
lte: { | ||
action(entity, state = {}, facets = {}) { | ||
action(entity, state, facets = {}) { | ||
entity._expectFacets(facets, Object.keys(facets), `"lte" facets`); | ||
@@ -234,3 +234,3 @@ state.query.type = QueryTypes.lte; | ||
params: { | ||
action(entity, state = {}, options) { | ||
action(entity, state, options) { | ||
if (state.query.method === MethodTypes.query) { | ||
@@ -245,3 +245,3 @@ return entity._queryParams(state.query, options); | ||
go: { | ||
action(entity, state = {}, options) { | ||
action(entity, state, options) { | ||
if (entity.client === undefined) { | ||
@@ -263,3 +263,3 @@ throw new Error("No client defined on model"); | ||
module.exports = { | ||
clauses | ||
}; | ||
clauses, | ||
}; |
@@ -11,3 +11,3 @@ "use strict"; | ||
structure, | ||
{ index, type, name } = {}, | ||
{ index, type, name } /* istanbul ignore next */ = {}, | ||
i, | ||
@@ -32,3 +32,3 @@ attributes, | ||
class Entity { | ||
constructor(model = {}, config = {}) { | ||
constructor(model /* istanbul ignore next */ = {}, config /* istanbul ignore next */ = {}) { | ||
this._validateModel(model); | ||
@@ -47,8 +47,8 @@ this.client = config.client; | ||
); | ||
this.find = (facets = {}) => { | ||
let index = this._findBestIndexKeyMatch(facets); | ||
return this._makeChain(index, clausesWithFilters, clauses.index).query( | ||
facets, | ||
); | ||
}; | ||
// this.find = (facets = {}) => { | ||
// let index = this._findBestIndexKeyMatch(facets); | ||
// return this._makeChain(index, clausesWithFilters, clauses.index).query( | ||
// facets, | ||
// ); | ||
// }; | ||
this.scan = this._makeChain("", clausesWithFilters, clauses.index).scan(); | ||
@@ -65,28 +65,3 @@ for (let accessPattern in this.model.indexes) { | ||
// _rearrangeModel(model = {}, config = {}) { | ||
// model.client = model.client || {}; | ||
// model.service = model.service || {}; | ||
// if (model.table) { | ||
// console.log(`Warning: Defining the "table" directly on the model will be depricated in version 1.0. Please see the README for additional direction.`) | ||
// model.client.table = model.table; | ||
// } | ||
// if (config.client) { | ||
// console.log(`Warning: Defining the "version" directly on the model will be depricated in version 1.0. Please see the README for additional direction.`) | ||
// model.client.docClient = config.client; | ||
// } | ||
// if (model.service) { | ||
// console.log(`Warning: Defining the "service" directly on the model will be depricated in version 1.0. Please see the README for additional direction.`) | ||
// model.service.name = model.service; | ||
// } | ||
// if (model.version) { | ||
// console.log(`Warning: Defining the "version" directly on the model will be depricated in version 1.0. Please see the README for additional direction.`) | ||
// model.service.version = model.version; | ||
// } | ||
// } | ||
collection(collection = "", clauses = {}, facets = {}) { | ||
collection(collection /* istanbul ignore next */ = "", clauses /* istanbul ignore next */ = {}, facets /* istanbul ignore next */ = {}) { | ||
let index = this.model.translations.collections.fromCollectionToIndex[ | ||
@@ -197,3 +172,3 @@ collection | ||
if (response.Item) { | ||
data = this.cleanseRetrievedData(response.Item); | ||
data = this.cleanseRetrievedData(response.Item, config); | ||
} else if (response.Items) { | ||
@@ -295,2 +270,3 @@ data = response.Items.map((item) => | ||
let indexBase = ""; | ||
let hasSortKey = this.model.lookup.indexHasSortKeys[indexBase]; | ||
let facets = this.model.facets.byIndex[indexBase]; | ||
@@ -313,4 +289,7 @@ let keys = this._makeParameterKey( | ||
), | ||
FilterExpression: `(begins_with(#pk, :pk) AND begins_with(#sk, :sk))`, | ||
FilterExpression: `(begins_with(#pk, :pk)`, | ||
}; | ||
if (hasSortKey) { | ||
params.FilterExpression = `${params.FilterExpression} AND begins_with(#sk, :sk))`; | ||
} | ||
if (filter.FilterExpression) { | ||
@@ -317,0 +296,0 @@ params.FilterExpression = `${params.FilterExpression} AND ${filter.FilterExpression}`; |
@@ -103,9 +103,2 @@ let queryChildren = [ | ||
for (let value of values) { | ||
if (strict) { | ||
let [isValid, errMessage] = attribute.isValid(value); | ||
if (!isValid) { | ||
throw new Error(errMessage); | ||
} | ||
} | ||
let valueCount = getValueCount(name); | ||
@@ -119,9 +112,3 @@ let attrValue = `:${name}${valueCount}`; | ||
let expression = template(attrName, ...attrValues); | ||
if (typeof expression !== "string") { | ||
throw new Error( | ||
"Invalid filter response. Expected result to be of type string", | ||
); | ||
} else { | ||
return expression.trim(); | ||
} | ||
}; | ||
@@ -180,2 +167,5 @@ }, | ||
let expression = filterFn(attributes, ...params); | ||
if (typeof expression !== "string") { | ||
throw new Error("Invalid filter response. Expected result to be of type string"); | ||
} | ||
state.query.filter.FilterExpression = this._concatFilterExpression( | ||
@@ -182,0 +172,0 @@ state.query.filter.FilterExpression, |
@@ -1,4 +0,3 @@ | ||
const { KeyTypes } = require("./types"); | ||
const { KeyTypes, CastTypes } = require("./types"); | ||
const AttributeTypes = ["string", "number", "boolean", "enum"]; | ||
const CastTypes = ["string", "number"]; | ||
@@ -27,3 +26,3 @@ class Attribute { | ||
} else if (get === undefined) { | ||
return attr => attr; | ||
return (attr) => attr; | ||
} else { | ||
@@ -40,3 +39,3 @@ throw new Error( | ||
} else if (set === undefined) { | ||
return attr => attr; | ||
return (attr) => attr; | ||
} else { | ||
@@ -57,3 +56,3 @@ throw new Error( | ||
} else if (cast === "string") { | ||
return val => { | ||
return (val) => { | ||
if (val === undefined) { | ||
@@ -70,3 +69,3 @@ throw new Error( | ||
} else if (cast === "number") { | ||
return val => { | ||
return (val) => { | ||
if (val === undefined) { | ||
@@ -90,3 +89,3 @@ throw new Error( | ||
} else { | ||
return val => val; | ||
return (val) => val; | ||
} | ||
@@ -97,3 +96,3 @@ } | ||
if (typeof definition === "function") { | ||
return val => { | ||
return (val) => { | ||
let reason = definition(val); | ||
@@ -103,3 +102,3 @@ return [!reason, reason || ""]; | ||
} else if (definition instanceof RegExp) { | ||
return val => { | ||
return (val) => { | ||
let isValid = definition.test(val); | ||
@@ -110,3 +109,3 @@ let reason = isValid ? "" : "Failed user defined regex"; | ||
} else { | ||
return val => [true, ""]; | ||
return (val) => [true, ""]; | ||
} | ||
@@ -223,3 +222,3 @@ } | ||
} | ||
let isKey = !!facets.byIndex[""].all.find(facet => facet.name === name); | ||
let isKey = !!facets.byIndex[""].all.find((facet) => facet.name === name); | ||
let definition = { | ||
@@ -259,3 +258,3 @@ name, | ||
}) | ||
.map(facet => `"${facet.type}: ${facet.name}"`); | ||
.map((facet) => `"${facet.type}: ${facet.name}"`); | ||
if (missingFacetAttributes.length) { | ||
@@ -270,3 +269,3 @@ throw new Error( | ||
let message = invalidProperties.map( | ||
prop => | ||
(prop) => | ||
`Schema Validation Error: Attribute "${prop.name}" property "${prop.property}". Received: "${prop.value}", Expected: "${prop.expected}"`, | ||
@@ -285,10 +284,2 @@ ); | ||
getValidate(payload = {}) { | ||
let record = {}; | ||
for (let [name, value] of Object.entries(payload)) { | ||
record[name] = this.attributes[name].getValidate(value); | ||
} | ||
return record; | ||
} | ||
applyAttributeGetters(payload = {}) { | ||
@@ -356,4 +347,4 @@ let attributes = { ...payload }; | ||
return Object.values(this.attributes) | ||
.filter(attribute => attribute.readOnly) | ||
.map(attribute => attribute.name); | ||
.filter((attribute) => attribute.readOnly) | ||
.map((attribute) => attribute.name); | ||
} | ||
@@ -365,2 +356,3 @@ } | ||
Attribute, | ||
CastTypes, | ||
}; |
@@ -80,4 +80,2 @@ const { Entity } = require("./entity"); | ||
); | ||
} else { | ||
results.push(record); | ||
} | ||
@@ -84,0 +82,0 @@ } |
@@ -15,3 +15,3 @@ const KeyTypes = { | ||
between: "between", | ||
collection: "collection" | ||
collection: "collection", | ||
}; | ||
@@ -36,2 +36,4 @@ | ||
const CastTypes = ["string", "number"]; | ||
module.exports = { | ||
@@ -41,3 +43,4 @@ KeyTypes, | ||
MethodTypes, | ||
CastTypes, | ||
Comparisons, | ||
}; |
@@ -0,1 +1,2 @@ | ||
process.env.AWS_NODEJS_CONNECTION_REUSE_ENABLED = 1; | ||
const { Entity } = require("../src/entity"); | ||
@@ -277,2 +278,90 @@ const { expect } = require("chai"); | ||
describe("Delete records", async () => { | ||
it("Should create then delete a record", async () => { | ||
let record = new Entity( | ||
{ | ||
service: "testservice", | ||
entity: "testentity", | ||
table: "electro", | ||
version: "1", | ||
attributes: { | ||
prop1: { | ||
type: "string", | ||
}, | ||
prop2: { | ||
type: "string", | ||
}, | ||
}, | ||
indexes: { | ||
main: { | ||
pk: { | ||
field: "pk", | ||
facets: ["prop1"], | ||
}, | ||
sk: { | ||
field: "sk", | ||
facets: ["prop2"], | ||
}, | ||
}, | ||
}, | ||
}, | ||
{ client }, | ||
); | ||
let prop1 = uuidv4(); | ||
let prop2 = uuidv4(); | ||
await record.put({ prop1, prop2 }).go(); | ||
let recordExists = await record.get({ prop1, prop2 }).go(); | ||
await record.delete({ prop1, prop2 }).go(); | ||
await sleep(150); | ||
let recordNoLongerExists = await record.get({ prop1, prop2 }).go(); | ||
expect(!!Object.keys(recordExists).length).to.be.true; | ||
expect(!!Object.keys(recordNoLongerExists).length).to.be.false; | ||
}); | ||
}); | ||
// describe("scan", async () => { | ||
// it ("Should scan for created records", async () => { | ||
// let entity = uuidv4(); | ||
// let db = new Entity({ | ||
// service: "testing", | ||
// entity: entity, | ||
// table: "electro", | ||
// version: "1", | ||
// attributes: { | ||
// id: { | ||
// type: "string" | ||
// }, | ||
// bb: { | ||
// type: "string" | ||
// } | ||
// }, | ||
// indexes: { | ||
// main: { | ||
// pk: { | ||
// field: "pk", | ||
// facets: ["id"] | ||
// }, | ||
// sk: { | ||
// field: "sk", | ||
// facets: ["id"] | ||
// } | ||
// } | ||
// } | ||
// }, {client}); | ||
// let puts = []; | ||
// for (let i = 0; i < 5; i++) { | ||
// console.log("putz", db.put({id: `${i}`, bb: `${i}`}).params()); | ||
// puts.push(db.put({id: `${i}`, bb: `${i}`}).go({})); | ||
// } | ||
// await Promise.all(puts); | ||
// await sleep(250); | ||
// let recordparams = db.scan.filter(({id}) => id.gte("3")).params(); | ||
// let records = await db.scan.filter(({id}) => id.gte("3")).go(); | ||
// if (!records.length) { | ||
// console.log("ENTITYz", recordparams); | ||
// } | ||
// expect(records).to.be.an("array").and.to.have.lengthOf(3); | ||
// }) | ||
// }) | ||
describe("Getters/Setters", async () => { | ||
@@ -436,2 +525,11 @@ let db = new Entity( | ||
}); | ||
let recordWithKeys = await db.get({id, date}).go({includeKeys: true}); | ||
expect(recordWithKeys).to.deep.equal({ | ||
id, | ||
date, | ||
someValue: "ABDEF wham bam", | ||
__edb_e__: entity, | ||
sk: `$${entity}#id_${id}`.toLowerCase(), | ||
pk: `$testing_1#date_${date}`.toLowerCase(), | ||
}) | ||
}).timeout(10000); | ||
@@ -438,0 +536,0 @@ }); |
@@ -0,1 +1,2 @@ | ||
process.env.AWS_NODEJS_CONNECTION_REUSE_ENABLED = 1; | ||
const { Entity, clauses } = require("../src/entity"); | ||
@@ -71,3 +72,2 @@ const { expect } = require("chai"); | ||
}) | ||
it("Should filter 'eq'", async () => { | ||
@@ -74,0 +74,0 @@ let animals = await FilterTests.query |
@@ -0,1 +1,2 @@ | ||
process.env.AWS_NODEJS_CONNECTION_REUSE_ENABLED = 1; | ||
const { Entity, clauses } = require("../src/entity"); | ||
@@ -2,0 +3,0 @@ const { Service } = require("../src/service"); |
@@ -166,2 +166,31 @@ const { Entity, clauses } = require("../src/entity"); | ||
}); | ||
it("should recognize when an attribute's field property is duplicated", () => { | ||
let schema = { | ||
service: "MallStoreDirectory", | ||
entity: "MallStores", | ||
table: "StoreDirectory", | ||
version: "1", | ||
attributes: { | ||
id: { | ||
type: "string", | ||
field: "id", | ||
}, | ||
duplicateFieldName: { | ||
type: "string", | ||
field: "id", | ||
}, | ||
}, | ||
indexes: { | ||
main: { | ||
pk: { | ||
field: "pk", | ||
facets: ["id"], | ||
}, | ||
}, | ||
}, | ||
}; | ||
expect(() => new Entity(schema)).to.throw( | ||
`Schema Validation Error: Attribute "duplicateFieldName" property "field". Received: "id", Expected: "Unique field property, already used by attribute id"`, | ||
); | ||
}); | ||
it("Should validate regex", () => { | ||
@@ -375,2 +404,20 @@ let Test = new Entity({ | ||
}); | ||
it("Should make scan parameters", () => { | ||
let scan = MallStores.scan.filter(({store}) => store.eq("Starblix")).params(); | ||
expect(scan).to.deep.equal({ | ||
"ExpressionAttributeNames": { | ||
"#pk": "pk", | ||
"#store": "storeId" | ||
}, | ||
"ExpressionAttributeValues": { | ||
":pk": "$mallstoredirectory_1#id_", | ||
":store1": "Starblix" | ||
}, | ||
"FilterExpression": "(begins_with(#pk, :pk) AND #store = :store1", | ||
"TableName": "StoreDirectory" | ||
}) | ||
}) | ||
it("Should check if filter returns string", () => { | ||
expect(() => MallStores.scan.filter(() => 1234)).to.throw("Invalid filter response. Expected result to be of type string"); | ||
}) | ||
it("Should create parameters for a given chain", () => { | ||
@@ -377,0 +424,0 @@ let mall = "EastPointe"; |
@@ -166,18 +166,2 @@ const moment = require("moment"); | ||
}); | ||
// it("Should validate the attributes passed when strict", () => { | ||
// function byCategory(attr, { category }) { | ||
// return attr.category.eq(category); | ||
// } | ||
// let filter = new FilterFactory( | ||
// MallStores.model.schema.attributes, | ||
// FilterTypes, | ||
// ); | ||
// let clause = filter.buildClause(byCategory); | ||
// let category = "BAD_CATEGORY"; | ||
// let results = () => | ||
// clause(MallStores, { query: { filter: {} } }, { category }); | ||
// expect(results).to.throw( | ||
// "Value not found in set of acceptable values: food/coffee, food/meal, clothing, electronics, department, misc", | ||
// ); | ||
// }); | ||
it("Shouldnt validate the attributes passed when not strict", () => { | ||
@@ -184,0 +168,0 @@ function byCategory(attr, { category }) { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
283718
21
5729
1837
0
10
4