Skip to content

Commit

Permalink
Feature/complextypes (tywalch#23)
Browse files Browse the repository at this point in the history
* initial commit

* Adding type `any` and laying the groundwork for types `map`, `set`, and `list`.

* Updating readme to include info on new type `"any"`.

* Fixing failed tests
  • Loading branch information
tywalch authored Aug 21, 2020
1 parent be2110b commit 825b9d8
Show file tree
Hide file tree
Showing 8 changed files with 464 additions and 70 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,10 +419,10 @@ Optional second parameter
> Using the `field` property, you can map an `AttributeName` 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.
#### Simple Syntax
Assign just the `type` of the attribute directly to the attribute name. Currently supported options are "string", "number", "boolean", and an array of strings representing a fixed set of possible values.
Assign just the `type` of the attribute directly to the attribute name. Currently supported options are "string", "number", "boolean", an array of strings representing a fixed set of possible values, or "any" which disables value type checking on that attribute.
```typescript
attributes: {
<AttributeName>: "string"|"number"|"boolean"|string[]
<AttributeName>: "string"|"number"|"boolean"|"any"|string[]
}
```

Expand All @@ -447,7 +447,7 @@ attributes: {

| Property | Type | Required | Description |
| -------- | :--: | :--: | ----------- |
| `type` | `string`, `string[]` | yes | Accepts the values: `"string"`, `"number"` `"boolean"`, or an array of strings representing a finite list of acceptable values: `["option1", "option2", "option3"]`. |
| `type` | `string`, `string[]` | yes | Accepts the values: `"string"`, `"number"` `"boolean"`, an array of strings representing a finite list of acceptable values: `["option1", "option2", "option3"]`, or `"any"`which disables value type checking on that attribute. |
`required` | `boolean` | no | Whether or not the value is required when creating a new record. |
`default` | `value`, `() => value` | no | Either the default value itself or a synchronous function that returns the desired value. |
`validate` | `RegExp`, `(value: any) => void|string` | no | Either regex or a synchronous callback to return an error string (will result in exception using the string as the error's message), or thrown exception in the event of an error. |
Expand Down Expand Up @@ -492,7 +492,9 @@ indexes: {
`collection` | `string` | no | Used when models are joined to a `Service`. When two entities share a `collection` on the same `index`, they can be queried with one request to DynamoDB. The name of the collection should represent what the query would return as a pseudo `Entity`. (see [Collections](#collections) below for more on this functionality).

## Facets
A **Facet** is a segment of a key based on one of the attributes. **Facets** are concatenated together from either a **Partition Key** or an **Sort Key** key, which define an `index`.
A **Facet** is a segment of a key based on one of the attributes. **Facets** are concatenated together from either a **Partition Key** or an **Sort Key** key, which define an `index`.

> Note: Only attributes with a type of `"string"`, `"number"`, or `"boolean"` can be used as a facet
There are two ways to provide facets:
1. As a [Facet Array](#facet-arrays)
Expand Down
41 changes: 37 additions & 4 deletions src/clauses.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ let clauses = {
// // return state;
// },
children: ["get", "delete", "update", "query", "put", "scan", "collection", "create", "patch"],

},
collection: {
name: "collection",
/* istanbul ignore next */
/* istanbul ignore next */
action(entity, state, collection = "", facets /* istanbul ignore next */ = {}) {
state.query.keys.pk = entity._expectFacets(facets, state.query.facets.pk);
entity._expectFacets(facets, Object.keys(facets), `"query" facets`);
Expand Down Expand Up @@ -138,7 +138,7 @@ let clauses = {
}
return state;
},
children: ["set"],
children: ["set", "append", "remove", "add", "subtract", ],
},
update: {
name: "update",
Expand All @@ -158,7 +158,7 @@ let clauses = {
}
return state;
},
children: ["set"],
children: ["set", "append", "remove", "add", "subtract"],
},
set: {
name: "set",
Expand All @@ -173,6 +173,39 @@ let clauses = {
},
children: ["set", "go", "params"],
},
// append: {
// name: "append",
// action(entity, state, data = {}) {
// let attributes = {}
// let payload = {};
// for (let path of Object.keys(data)) {
// let parsed = entity.model.schema.parseAttributePath(path);
//
// }
// },
// children: ["set", "append", "remove", "add", "subtract", "go", "params"]
// },
// remove: {
// name: "remove",
// action(entity, state, data) {
//
// },
// children: ["set", "append", "remove", "add", "subtract", "go", "params"]
// },
// add: {
// name: "add",
// action(entity, state, data) {
//
// },
// children: ["set", "append", "remove", "add", "subtract", "go", "params"]
// },
// subtract: {
// name: "subtract",
// action(entity, state, data) {
//
// },
// children: ["set", "append", "remove", "add", "subtract", "go", "params"]
// },
query: {
name: "query",
action(entity, state, facets, options = {}) {
Expand Down
44 changes: 21 additions & 23 deletions src/entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class Entity {
let match = this._findBestIndexKeyMatch(facets);
if (match.shouldScan) {
return this._makeChain("", this._clausesWithFilters, clauses.index).scan().filter(attr => {
let eqFilters = [];
let eqFilters = [];
for (let facet of Object.keys(facets)) {
if (attr[facet]) {
eqFilters.push(attr[facet].eq(facets[facet]));
Expand All @@ -68,7 +68,7 @@ class Entity {
return this._makeChain(match.index, this._clausesWithFilters, clauses.index).query(
facets,
).filter(attr => {
let eqFilters = [];
let eqFilters = [];
for (let facet of Object.keys(facets)) {
if (attr[facet]) {
eqFilters.push(attr[facet].eq(facets[facet]));
Expand All @@ -78,7 +78,7 @@ class Entity {
});
}
}

collection(collection = "", clauses = {}, facets = {}) {
let index = this.model.translations.collections.fromCollectionToIndex[
collection
Expand All @@ -95,7 +95,7 @@ class Entity {
_validateModel(model) {
return validations.model(model);
}

get(facets = {}) {
let index = "";
return this._makeChain(index, clauses, clauses.index).get(facets);
Expand Down Expand Up @@ -163,6 +163,10 @@ class Entity {
facets: { ...facets },
update: {
set: {},
append: {},
remove: {},
add: {},
subtract: {}
},
put: {
data: {},
Expand Down Expand Up @@ -338,7 +342,7 @@ class Entity {
break;
case MethodTypes.update:
case MethodTypes.patch:
case MethodTypes.patch:
case MethodTypes.patch:
params = this._makeUpdateParams(
update,
keys.pk,
Expand Down Expand Up @@ -367,7 +371,7 @@ class Entity {
}
return key;
}

/* istanbul ignore next */
_makeScanParam(filter = {}) {
let indexBase = "";
Expand Down Expand Up @@ -544,7 +548,7 @@ class Entity {
)}`;
return expressions;
}

/* istanbul ignore next */
_queryParams(chainState = {}, options = {}) {
let conlidatedQueryFacets = this._consolidateQueryFacets(
Expand Down Expand Up @@ -657,7 +661,7 @@ class Entity {
}
return merged;
}

/* istanbul ignore next */
_makeComparisonQueryParams(index = "", comparison = "", filter = {}, pk = {}, sk = {}) {
let operator = Comparisons[comparison];
Expand Down Expand Up @@ -758,7 +762,7 @@ class Entity {
}
return { indexKey, updatedKeys };
}

/* istanbul ignore next */
_getIndexImpact(attributes = {}, included = {}) {
let includedFacets = Object.keys(included);
Expand Down Expand Up @@ -848,7 +852,7 @@ class Entity {
);
return { ...queryFacets };
}

/* istanbul ignore next */
_expectFacets(obj = {}, properties = [], type = "key facets") {
let [incompletePk, missing, matching] = this._expectProperties(
Expand Down Expand Up @@ -903,15 +907,15 @@ class Entity {
return "";
}
}

/* istanbul ignore next */
_getPrefixes({ collection = "", customFacets = {}, sk } = {}) {
/*
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.
the custom key.
*/

let keys = {
Expand All @@ -934,7 +938,7 @@ class Entity {
}

if (sk === undefined) {
keys.pk.prefix += keys.sk.prefix;
keys.pk.prefix += keys.sk.prefix;
}

if (customFacets.pk) {
Expand All @@ -949,7 +953,7 @@ class Entity {

return keys;
}

/* istanbul ignore next */
_makeIndexKeys(index = "", pkFacets = {}, ...skFacets) {
this._validateIndex(index);
Expand Down Expand Up @@ -1046,7 +1050,7 @@ class Entity {
shouldScan: match === undefined
};
}

/* istanbul ignore next */
_parseComposedKey(key = "") {
let facets = {};
Expand Down Expand Up @@ -1165,17 +1169,12 @@ class Entity {
facets.fields.push(sk.field);
}

// if (inCollection) {
// collections[index.collection] = index.collection;
// }

let definition = {
pk,
sk,
collection,
customFacets,
index: indexName,
collection: index.collection,
};

if (inCollection) {
Expand Down Expand Up @@ -1237,7 +1236,7 @@ class Entity {
utilities.structureFacets(facets, facet, j, attributes, i),
);
}

if (facets.byIndex[""] === undefined) {
throw new Error("Schema is missing an index definition for the table's main index. Please update the schema to include an index without a specified name to define the table's natural index");
}
Expand All @@ -1252,7 +1251,7 @@ class Entity {
collections: Object.keys(collections),
};
}

_normalizeFilters(filters = {}) {
let normalized = {};
let invalidFilterNames = ["go", "params", "filter"];
Expand Down Expand Up @@ -1303,7 +1302,6 @@ class Entity {
indexes: indexAccessPattern,
collections: indexCollection,
},

original: model,
};
}
Expand Down
45 changes: 45 additions & 0 deletions src/parse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
function getPartDetail(part = "") {
let detail = {
expression: "",
name: "",
value: "",
};
if (part.includes("[")) {
if (!part.match(/\[\d\]/gi)) {
throw new Error(`Invalid path part "${part}" has bracket containing non-numeric characters.`);
}
let [name] = part.match(/.*(?=\[)/gi);
detail.name = `#${name}`;
detail.value = name;
} else {
detail.name = `#${part}`;
detail.value = part;
}
detail.expression = `#${part}`;
return detail;
}

function parse(path = "") {
if (typeof path !== "string" || !path.length) {
throw new Error("Path must be a string with a non-zero length");
}
let parts = path.split(/\./gi);
let attr = getPartDetail(parts[0]).value;
let target = getPartDetail(parts[parts.length-1]);
if (target.expression.includes("[")) {

}
let names = {};
let expressions = []
for (let part of parts) {
let detail = getPartDetail(part);
names[detail.name] = detail.value;
expressions.push(detail.expression);
}
return {attr, path, names, target: target.value, expression: expressions.join(".")};
}

module.exports = {
parse,
getPartDetail
};
Loading

0 comments on commit 825b9d8

Please sign in to comment.