electrodb
Advanced tools
Comparing version 0.8.18 to 0.8.19
{ | ||
"name": "electrodb", | ||
"version": "0.8.18", | ||
"version": "0.8.19", | ||
"description": "A library to more easily create and interact with multiple entities and heretical relationships in dynamodb", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -369,9 +369,16 @@ # ElectroDB | ||
### Facet Templates | ||
In a Facet Template, you provide a formatted template for ElectroDB to use when making keys. | ||
In a Facet Template, you provide a formatted template for ElectroDB to use when making keys. Facet Templates allow for potential ElectroDB adoption on already established tables and records. | ||
The syntax to a Facet Template is simple using the following rules: | ||
1. Only alphanumeric, underscore, colons, and hash symbols are valid. the following regex is used to determine validity: `/^[A-Z1-9:#_]+$/gi` | ||
2. Attributes are identified by a prefixed colon and the attributes name. For example, the syntax `:storeId` will matches `storeId` attribute in the `model` | ||
3. Convention for a composing a key use the `#` symbol to separate attributes, and for labels to attach with underscore. For example, when composing both the `mallId` and `buildingId` would be expressed as `mid_:mallId#bid_:buildingId`. | ||
Attributes are identified by a prefixed colon and the attributes name. For example, the syntax `:storeId` will matches `storeId` attribute in the `model`. | ||
Convention for a composing a key use the `#` symbol to separate attributes, and for labels to attach with underscore. For example, when composing both the `mallId` and `buildingId` would be expressed as `mid_:mallId#bid_:buildingId`. | ||
> ElectroDB will not prefix templated keys with the Entity, Project, Version, or Collection. This will give you greater control of your keys but will limit ElectroDBs ability to prevent leaking entities with some queries. | ||
Facet Templates have some "gotchas" to consider: | ||
1. Keys only allow for one instance of an attribute, the template `:prop1#:prop1` will be interpreted the same as `:prop1#`. | ||
2. ElectoDB will continue to always add a trailing delimiter to facets with keys are partially supplied. (More documentation coming on this soon) | ||
```javascript | ||
@@ -416,4 +423,4 @@ attributes: { | ||
{ | ||
pk: '$mallstoredirectory_1#sid_storevalue', | ||
sk: '$mallstores#mid_mallvalue#bid_buildingvalue#uid_unitvalue' | ||
pk: 'sid_storevalue', | ||
sk: 'mid_mallvalue#bid_buildingvalue#uid_unitvalue' | ||
} | ||
@@ -420,0 +427,0 @@ ``` |
@@ -1022,10 +1022,52 @@ "use strict"; | ||
_getPrefixes({collection = "", customFacets = {}} = {}) { | ||
/* | ||
Collections will prefix the sort key so they can be queried with | ||
a "begins_with" operator when crossing entities. It is also possible | ||
that the user defined a custom key on either the PK or SK. In the case | ||
of a customKey AND a collection, the collection is ignored to favor | ||
the custom key. | ||
*/ | ||
let keys = { | ||
pk: { | ||
prefix: "", | ||
isCustom: false | ||
}, | ||
sk: { | ||
prefix: "", | ||
isCustom: false | ||
} | ||
}; | ||
if (collection) { | ||
keys.pk.prefix = this.model.prefixes.pk | ||
keys.sk.prefix = `$${collection}#${this.model.entity}` | ||
} else { | ||
keys.pk.prefix = this.model.prefixes.pk; | ||
keys.sk.prefix = this.model.prefixes.sk; | ||
} | ||
if (customFacets.pk) { | ||
keys.pk.prefix = ""; | ||
keys.pk.isCustom = customFacets.pk; | ||
} | ||
if (customFacets.sk) { | ||
keys.sk.prefix = ""; | ||
keys.sk.isCustom = customFacets.sk; | ||
} | ||
return keys; | ||
} | ||
_makeIndexKeys(index = "", pkFacets = {}, ...skFacets) { | ||
this._validateIndex(index); | ||
let facets = this.model.facets.byIndex[index]; | ||
let pk = this._makeKey(this.model.prefixes.pk, facets.pk, pkFacets); | ||
let prefixes = this._getPrefixes(facets); | ||
let pk = this._makeKey(prefixes.pk.prefix, facets.pk, pkFacets, prefixes.pk); | ||
let sk = []; | ||
if (this.model.lookup.indexHasSortKeys[index]) { | ||
for (let skFacet of skFacets) { | ||
sk.push(this._makeKey(this.model.prefixes.sk, facets.sk, skFacet)); | ||
sk.push(this._makeKey(prefixes.sk.prefix, facets.sk, skFacet, prefixes.sk)); | ||
} | ||
@@ -1036,8 +1078,12 @@ } | ||
_makeKey(prefix = "", facets = [], supplied = {}) { | ||
_makeKey(prefix = "", facets = [], supplied = {}, {isCustom} = {}) { | ||
let key = prefix; | ||
for (let i = 0; i < facets.length; i++) { | ||
let facet = facets[i]; | ||
let facet = facets[i]; | ||
let { label, name } = this.model.schema.attributes[facet]; | ||
key = `${key}#${label || name}_`; | ||
if (isCustom) { | ||
key = `${key}${label}`; | ||
} else { | ||
key = `${key}#${label || name}_`; | ||
} | ||
if (supplied[name] !== undefined) { | ||
@@ -1104,7 +1150,2 @@ key = `${key}${supplied[name]}`; | ||
_parseComposedKey(key = "") { | ||
if (!key.match(/^[A-Z1-9:#_]+$/gi)) { | ||
throw new Error( | ||
`Invalid key facet template. Allowed characters include only "A-Z", "a-z", "1-9", ":", "_", "#". Received: ${key}`, | ||
); | ||
} | ||
let facets = {}; | ||
@@ -1122,3 +1163,3 @@ let names = key.match(/:[A-Z1-9]+/gi); | ||
if (name !== "") { | ||
facets[name] = label.replace(/#|_/gi, ""); | ||
facets[name] = label; | ||
} | ||
@@ -1130,13 +1171,16 @@ } | ||
_parseFacets(facets) { | ||
if (Array.isArray(facets)) { | ||
return { | ||
facetLabels: {}, | ||
facetArray: facets, | ||
}; | ||
} else { | ||
let isCustom = !Array.isArray(facets); | ||
if (isCustom) { | ||
let facetLabels = this._parseComposedKey(facets); | ||
return { | ||
isCustom, | ||
facetLabels, | ||
facetArray: Object.keys(facetLabels), | ||
}; | ||
} else { | ||
return { | ||
isCustom, | ||
facetLabels: {}, | ||
facetArray: facets, | ||
}; | ||
} | ||
@@ -1153,3 +1197,3 @@ } | ||
}; | ||
let collections = {}; | ||
let facets = { | ||
@@ -1173,4 +1217,11 @@ byIndex: {}, | ||
let hasSk = !!index.sk; | ||
let inCollection = !!index.collection; | ||
let customFacets = { | ||
pk: false, | ||
sk: false | ||
} | ||
indexHasSortKeys[indexName] = hasSk; | ||
let { facetArray, facetLabels } = this._parseFacets(index.pk.facets); | ||
let parsedPKFacets = this._parseFacets(index.pk.facets); | ||
let { facetArray, facetLabels } = parsedPKFacets; | ||
customFacets.pk = parsedPKFacets.isCustom; | ||
facets.labels = Object.assign({}, facets.labels, facetLabels); | ||
@@ -1188,3 +1239,5 @@ let pk = { | ||
if (hasSk) { | ||
let { facetArray, facetLabels } = this._parseFacets(index.sk.facets); | ||
let parseSKFacets = this._parseFacets(index.sk.facets); | ||
let { facetArray, facetLabels } = parseSKFacets; | ||
customFacets.sk = parseSKFacets.isCustom; | ||
facets.labels = Object.assign({}, facets.labels, facetLabels); | ||
@@ -1202,6 +1255,12 @@ sk = { | ||
if (inCollection) { | ||
collections[index.collection] = index.collection; | ||
} | ||
let definition = { | ||
pk, | ||
sk, | ||
customFacets, | ||
index: indexName, | ||
collection: index.collection | ||
}; | ||
@@ -1241,5 +1300,7 @@ | ||
facets.byIndex[indexName] = { | ||
customFacets, | ||
pk: pk.facets, | ||
sk: sk.facets, | ||
all: attributes, | ||
collection: index.collection, | ||
}; | ||
@@ -1258,2 +1319,3 @@ | ||
indexAccessPattern: indexAccessPatternTransaction, | ||
collections: Object.keys(collections) | ||
}; | ||
@@ -1286,2 +1348,3 @@ } | ||
indexField, | ||
collections, | ||
indexHasSortKeys, | ||
@@ -1302,2 +1365,3 @@ indexAccessPattern, | ||
prefixes, | ||
collections, | ||
lookup: { | ||
@@ -1316,12 +1380,2 @@ indexHasSortKeys, | ||
// class AccessPattern { | ||
// constructor(name = "", definition = {}) { | ||
// this._validateIndex(definition); | ||
// this.name = name; | ||
// this.index = definition.index; | ||
// } | ||
// _validateIndex() {} | ||
// } | ||
module.exports = { | ||
@@ -1328,0 +1382,0 @@ Entity, |
@@ -9,3 +9,3 @@ const { KeyTypes } = require("./types"); | ||
this.field = definition.field || definition.name; | ||
this.label = definition.label || definition.name; | ||
this.label = definition.label || ""; | ||
this.readOnly = !!definition.readOnly; | ||
@@ -12,0 +12,0 @@ this.required = !!definition.required; |
@@ -1,230 +0,67 @@ | ||
// const { Entity, clauses } = require("../src/entity"); | ||
// const { expect } = require("chai"); | ||
// const moment = require("moment"); | ||
// const uuidV4 = require("uuid").v4; | ||
// const DynamoDB = require("aws-sdk/clients/dynamodb"); | ||
// const client = new DynamoDB.DocumentClient({ | ||
// region: "us-east-1", | ||
// }); | ||
const { Entity, clauses } = require("../src/entity"); | ||
const { expect } = require("chai"); | ||
const moment = require("moment"); | ||
const uuidV4 = require("uuid").v4; | ||
const DynamoDB = require("aws-sdk/clients/dynamodb"); | ||
const client = new DynamoDB.DocumentClient({ | ||
region: "us-east-1", | ||
}); | ||
// describe("General", async () => { | ||
// let FilterTests = new Entity({ | ||
// service: "tests", | ||
// entity: "filters", | ||
// table: "electro", | ||
// version: "1", | ||
// attributes: { | ||
// pen: { | ||
// type: "string", | ||
// default: () => uuidV4(), | ||
// field: "storeLocationId", | ||
// }, | ||
// row: { | ||
// type: "string", | ||
// required: true, | ||
// field: "mall", | ||
// }, | ||
// animal: { | ||
// type: "string", | ||
// required: true | ||
// }, | ||
// dangerous: { | ||
// type: "boolean" | ||
// } | ||
// }, | ||
// filters: {}, | ||
// indexes: { | ||
// farm: { | ||
// pk: { | ||
// field: "pk", | ||
// facets: ["pen"], | ||
// }, | ||
// sk: { | ||
// field: "sk", | ||
// facets: ["row"] | ||
// } | ||
// }, | ||
// }, | ||
// }, {client}); | ||
// let pen = uuidV4(); | ||
// let animals = [ | ||
// "Chicken", | ||
// "Chick", | ||
// "Cow", | ||
// "Dog", | ||
// "Pig", | ||
// "Rooster", | ||
// "Shark", | ||
// "Sheep", | ||
// ]; | ||
// before(async () => { | ||
// let results = await Promise.all(animals.map(animal => { | ||
// let row = uuidV4(); | ||
// if (animal === "Shark") { | ||
// return FilterTests.put({pen, row, animal, dangerous: true}).go() | ||
// } else { | ||
// return FilterTests.put({pen, row, animal}).go() | ||
// } | ||
// })); | ||
// }) | ||
// it("Should filter 'eq'", async () => { | ||
// let animals = await FilterTests.query | ||
// .farm({pen}) | ||
// .filter(({animal}) => animal.eq("Cow")) | ||
// .go() | ||
// expect(animals) | ||
// .to.be.an("array") | ||
// .and.have.length(1) | ||
// expect(animals.map(pen => pen.animal)).to.have.members(["Cow"]); | ||
// }) | ||
// it("Should filter 'gt'", async () => { | ||
// let animals = await FilterTests.query | ||
// .farm({pen}) | ||
// .filter(({animal}) => animal.gt("Dog")) | ||
// .go() | ||
// expect(animals) | ||
// .to.be.an("array") | ||
// .and.have.length(4); | ||
// expect(animals.map(pen => pen.animal)).to.have.members([ | ||
// "Pig", | ||
// "Rooster", | ||
// "Shark", | ||
// "Sheep" | ||
// ]); | ||
// }) | ||
// it("Should filter 'lt'", async () => { | ||
// let animals = await FilterTests.query | ||
// .farm({pen}) | ||
// .filter(({animal}) => animal.lt("Pig")) | ||
// .go() | ||
// expect(animals) | ||
// .to.be.an("array") | ||
// .and.have.length(4); | ||
// expect(animals.map(pen => pen.animal)).to.have.members([ | ||
// "Chicken", | ||
// "Chick", | ||
// "Cow", | ||
// "Dog", | ||
// ]); | ||
// }) | ||
// it("Should filter 'gte'", async () => { | ||
// let animals = await FilterTests.query | ||
// .farm({pen}) | ||
// .filter(({animal}) => animal.gte("Dog")) | ||
// .go() | ||
// expect(animals) | ||
// .to.be.an("array") | ||
// .and.have.length(5); | ||
// expect(animals.map(pen => pen.animal)).to.have.members([ | ||
// "Dog", | ||
// "Pig", | ||
// "Rooster", | ||
// "Shark", | ||
// "Sheep", | ||
// ]); | ||
// }) | ||
// it("Should filter 'lte'", async () => { | ||
// let animals = await FilterTests.query | ||
// .farm({pen}) | ||
// .filter(({animal}) => animal.lte("Pig")) | ||
// .go() | ||
// expect(animals) | ||
// .to.be.an("array") | ||
// .and.have.length(5); | ||
// expect(animals.map(pen => pen.animal)).to.have.members([ | ||
// "Chicken", | ||
// "Chick", | ||
// "Cow", | ||
// "Dog", | ||
// "Pig", | ||
// ]); | ||
// }) | ||
// it("Should filter 'between'", async () => { | ||
// let animals = await FilterTests.query | ||
// .farm({pen}) | ||
// .filter(({animal}) => animal.between("Dog", "Rooster")) | ||
// .go() | ||
// expect(animals) | ||
// .to.be.an("array") | ||
// .and.have.length(3); | ||
// expect(animals.map(pen => pen.animal)).to.have.members([ | ||
// "Dog", | ||
// "Pig", | ||
// "Rooster" | ||
// ]); | ||
// }) | ||
// it("Should filter 'begins'", async () => { | ||
// let animals = await FilterTests.query | ||
// .farm({pen}) | ||
// .filter(({animal}) => animal.begins("Sh")) | ||
// .go() | ||
// expect(animals) | ||
// .to.be.an("array") | ||
// .and.have.length(2); | ||
// expect(animals.map(pen => pen.animal)).to.have.members([ | ||
// "Shark", | ||
// "Sheep", | ||
// ]); | ||
// }) | ||
// it("Should filter 'exists'", async () => { | ||
// let animals = await FilterTests.query | ||
// .farm({pen}) | ||
// .filter(({dangerous}) => dangerous.exists()) | ||
// .go() | ||
// expect(animals) | ||
// .to.be.an("array") | ||
// .and.have.length(1); | ||
// expect(animals.map(pen => pen.animal)).to.have.members([ | ||
// "Shark" | ||
// ]); | ||
// }) | ||
// it("Should filter 'notExists'", async () => { | ||
// let animals = await FilterTests.query | ||
// .farm({pen}) | ||
// .filter(({dangerous}) => dangerous.notExists()) | ||
// .go() | ||
// expect(animals) | ||
// .to.be.an("array") | ||
// .and.have.length(7); | ||
// expect(animals.map(pen => pen.animal)).to.have.members([ | ||
// "Chicken", | ||
// "Chick", | ||
// "Cow", | ||
// "Dog", | ||
// "Pig", | ||
// "Rooster", | ||
// "Sheep", | ||
// ]); | ||
// }) | ||
// it("Should filter 'contains'", async () => { | ||
// let animals = await FilterTests.query | ||
// .farm({pen}) | ||
// .filter(({animal}) => animal.contains("Chick")) | ||
// .go() | ||
// expect(animals) | ||
// .to.be.an("array") | ||
// .and.have.length(2); | ||
// expect(animals.map(pen => pen.animal)).to.have.members([ | ||
// "Chicken", | ||
// "Chick" | ||
// ]); | ||
// }) | ||
// it("Should filter 'notContains'", async () => { | ||
// let animals = await FilterTests.query | ||
// .farm({pen}) | ||
// .filter(({animal}) => animal.notContains("o")) | ||
// .go() | ||
// expect(animals) | ||
// .to.be.an("array") | ||
// .and.have.length(5); | ||
// expect(animals.map(pen => pen.animal)).to.have.members([ | ||
// "Chicken", | ||
// "Chick", | ||
// "Pig", | ||
// "Shark", | ||
// "Sheep", | ||
// ]); | ||
// }) | ||
// }) | ||
describe("Custom keys", () => { | ||
it("Should default labels to facet attribute names in facet template (string)", () => { | ||
const schema = { | ||
service: "MallStoreDirectory", | ||
entity: "MallStores", | ||
table: "StoreDirectory", | ||
version: "1", | ||
attributes: { | ||
id: { | ||
type: "string", | ||
field: "storeLocationId", | ||
}, | ||
date: { | ||
type: "string", | ||
field: "dateTime", | ||
}, | ||
prop1: { | ||
type: "string", | ||
}, | ||
prop2: { | ||
type: "string", | ||
}, | ||
}, | ||
indexes: { | ||
record: { | ||
pk: { | ||
field: "pk", | ||
facets: `id_:id:prop1`, | ||
}, | ||
sk: { | ||
field: "sk", | ||
facets: `:date:prop2:id:prop2`, | ||
}, | ||
}, | ||
}, | ||
}; | ||
let mallStore = new Entity(schema); | ||
let putParams = mallStore | ||
.put({ | ||
id: "IDENTIFIER", | ||
date: "DATE", | ||
prop1: "PROPERTY1", | ||
prop2: "PROPERTY2", | ||
}) | ||
.params(); | ||
expect(putParams).to.deep.equal({ | ||
Item: { | ||
storeLocationId: "IDENTIFIER", | ||
dateTime: "DATE", | ||
prop1: "PROPERTY1", | ||
prop2: "PROPERTY2", | ||
pk: "$mallstoredirectory_1#id_identifier#prop1_property1", | ||
sk: "$mallstores#date_date#p2_property2", | ||
}, | ||
TableName: "StoreDirectory", | ||
}); | ||
}); | ||
}) |
@@ -732,4 +732,4 @@ const { Entity, clauses } = require("../src/entity"); | ||
prop2: "PROPERTY2", | ||
pk: "$mallstoredirectory_1#id_identifier#p1_property1", | ||
sk: "$mallstores#d_date#p2_property2", | ||
pk: "id_identifier#p1_property1", | ||
sk: "d_date#p2_property2", | ||
}, | ||
@@ -739,3 +739,46 @@ TableName: "StoreDirectory", | ||
}); | ||
it("Should throw on invalid characters in facet template (string)", () => { | ||
/* This test was removed because facet templates was refactored to remove all electrodb opinions. */ | ||
// | ||
// it("Should throw on invalid characters in facet template (string)", () => { | ||
// const schema = { | ||
// service: "MallStoreDirectory", | ||
// entity: "MallStores", | ||
// table: "StoreDirectory", | ||
// version: "1", | ||
// attributes: { | ||
// id: { | ||
// type: "string", | ||
// field: "storeLocationId", | ||
// }, | ||
// date: { | ||
// type: "string", | ||
// field: "dateTime", | ||
// }, | ||
// prop1: { | ||
// type: "string", | ||
// }, | ||
// prop2: { | ||
// type: "string", | ||
// }, | ||
// }, | ||
// indexes: { | ||
// record: { | ||
// pk: { | ||
// field: "pk", | ||
// facets: `id_:id#p1_:prop1`, | ||
// }, | ||
// sk: { | ||
// field: "sk", | ||
// facets: `d_:date|p2_:prop2`, | ||
// }, | ||
// }, | ||
// }, | ||
// }; | ||
// expect(() => new Entity(schema)).to.throw( | ||
// `Invalid key facet template. Allowed characters include only "A-Z", "a-z", "1-9", ":", "_", "#". Received: d_:date|p2_:prop2`, | ||
// ); | ||
// }); | ||
it("Should default labels to facet attribute names in facet template (string)", () => { | ||
const schema = { | ||
@@ -766,7 +809,7 @@ service: "MallStoreDirectory", | ||
field: "pk", | ||
facets: `id_:id#p1_:prop1`, | ||
facets: `id_:id#:prop1`, | ||
}, | ||
sk: { | ||
field: "sk", | ||
facets: `d_:date|p2_:prop2`, | ||
facets: `:date#p2_:prop2`, | ||
}, | ||
@@ -776,7 +819,24 @@ }, | ||
}; | ||
expect(() => new Entity(schema)).to.throw( | ||
`Invalid key facet template. Allowed characters include only "A-Z", "a-z", "1-9", ":", "_", "#". Received: d_:date|p2_:prop2`, | ||
); | ||
let mallStore = new Entity(schema); | ||
let putParams = mallStore | ||
.put({ | ||
id: "IDENTIFIER", | ||
date: "DATE", | ||
prop1: "PROPERTY1", | ||
prop2: "PROPERTY2", | ||
}) | ||
.params(); | ||
expect(putParams).to.deep.equal({ | ||
Item: { | ||
storeLocationId: "IDENTIFIER", | ||
dateTime: "DATE", | ||
prop1: "PROPERTY1", | ||
prop2: "PROPERTY2", | ||
pk: "id_identifier#property1", | ||
sk: "date#p2_property2", | ||
}, | ||
TableName: "StoreDirectory", | ||
}); | ||
}); | ||
it("Should default labels to facet attribute names in facet template (string)", () => { | ||
it("Should allow for mixed custom/composed facets, and adding collection prefixes when defined", () => { | ||
const schema = { | ||
@@ -802,2 +862,5 @@ service: "MallStoreDirectory", | ||
}, | ||
prop3: { | ||
type: "string", | ||
} | ||
}, | ||
@@ -808,8 +871,9 @@ indexes: { | ||
field: "pk", | ||
facets: `id_:id#:prop1`, | ||
facets: `id_:id#:prop1#wubba_:prop3`, | ||
}, | ||
sk: { | ||
field: "sk", | ||
facets: `:date#p2_:prop2`, | ||
facets: ["date", "prop2"], | ||
}, | ||
collection: "testing" | ||
}, | ||
@@ -825,2 +889,3 @@ }, | ||
prop2: "PROPERTY2", | ||
prop3: "PROPERTY3" | ||
}) | ||
@@ -834,4 +899,5 @@ .params(); | ||
prop2: "PROPERTY2", | ||
pk: "$mallstoredirectory_1#id_identifier#prop1_property1", | ||
sk: "$mallstores#date_date#p2_property2", | ||
prop3: "PROPERTY3", | ||
pk: "id_identifier#property1#wubba_property3", | ||
sk: "$testing#mallstores#date_date#prop2_property2", | ||
}, | ||
@@ -838,0 +904,0 @@ TableName: "StoreDirectory", |
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
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
1140
235612
4536