electrodb
Advanced tools
Comparing version 1.2.0 to 1.3.0
@@ -8,6 +8,2 @@ # Changelog | ||
## Adding | ||
- Additional return options for `update`, `patch`, `create` and `put` methods. | ||
- Complex Attribute support for "list", "set", and "map". | ||
## [1.0.0] - 2021-06-27 | ||
@@ -51,2 +47,19 @@ ### Added | ||
### Changed | ||
- The property names of `ExpressionAttributeValues` underwent some change in this release due to the addition of new update operations. This is not a breaking change but if you have tests to match on the exact params returned from ElectroDB these will likely break. [[read more]](./RELEASE.md#expressionattributevalues-properties) | ||
- The property names of `ExpressionAttributeValues` underwent some change in this release due to the addition of new update operations. This is not a breaking change but if you have tests to match on the exact params returned from ElectroDB these will likely break. [[read more]](./RELEASE.md#expressionattributevalues-properties) | ||
## [1.3.0] - 2021-08-09 | ||
### Added | ||
- New Attribute types `map`, `list`, `set` [[read more]](./README.md#expanded-syntax) | ||
- New Query Options, and support for, `ReturnValues` as requested in Issue#71 [[read more]](./README.md#query-options) | ||
- New type definitions for recently released update methods `append`, `add`, `subtract`, `data`, `remove`, and `delete`. [[read more]](./README.md#exported-types) | ||
### Changed | ||
- Attributes that have been flagged as `required` are now not possible to be removed (using the update method `remove()`) from a stored Item. This was an oversight from the last release. | ||
- Attributes that have been flagged as `hidden` now skips invoking that attribute's getter method. | ||
### Fixed | ||
- Issues that prevented the nesting of update `value()` operation. | ||
- TypeScript type definitions for `get()` method now incorporate potential for `null` response. | ||
- Type definitions for `value()` and `name()` where clause operations. | ||
{ | ||
"name": "electrodb", | ||
"version": "1.2.0", | ||
"version": "1.3.0", | ||
"description": "A library to more easily create and interact with multiple entities and heretical relationships in dynamodb", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -278,2 +278,3 @@ const { QueryTypes, MethodTypes, ItemOperations, ExpressionTypes } = require("./types"); | ||
try { | ||
entity.model.schema.checkUpdate(data); | ||
state.query.updateProxy.fromObject(ItemOperations.set, data); | ||
@@ -295,2 +296,3 @@ return state; | ||
try { | ||
entity.model.schema.checkUpdate(data); | ||
state.query.updateProxy.fromObject(ItemOperations.append, data); | ||
@@ -315,4 +317,3 @@ return state; | ||
} | ||
// let record = entity.model.schema.checkRemove(data); | ||
entity.model.schema.checkRemove(data); | ||
state.query.updateProxy.fromArray(ItemOperations.remove, data); | ||
@@ -334,2 +335,3 @@ return state; | ||
try { | ||
entity.model.schema.checkUpdate(data); | ||
state.query.updateProxy.fromObject(ItemOperations.delete, data); | ||
@@ -351,2 +353,3 @@ return state; | ||
try { | ||
entity.model.schema.checkUpdate(data); | ||
state.query.updateProxy.fromObject(ItemOperations.add, data); | ||
@@ -368,2 +371,3 @@ return state; | ||
try { | ||
entity.model.schema.checkUpdate(data); | ||
state.query.updateProxy.fromObject(ItemOperations.subtract, data); | ||
@@ -370,0 +374,0 @@ return state; |
const {AttributeTypes, ItemOperations, AttributeProxySymbol, BuilderTypes} = require("./types"); | ||
const e = require("./errors"); | ||
const v = require("./util"); | ||
@@ -219,3 +220,4 @@ const deleteOperations = { | ||
}, | ||
strict: false | ||
strict: false, | ||
canNest: true, | ||
}, | ||
@@ -226,3 +228,4 @@ name: { | ||
}, | ||
strict: false | ||
strict: false, | ||
canNest: true, | ||
} | ||
@@ -322,3 +325,3 @@ }; | ||
const value = record[path]; | ||
const parts = this._parseJSONPath(path); | ||
const parts = v.parseJSONPath(path); | ||
let attribute = this.attributes; | ||
@@ -332,3 +335,3 @@ for (let part of parts) { | ||
if (target.readOnly) { | ||
throw new Error(`Attribute "${target.name}" is Read-Only and cannot be updated`); | ||
throw new Error(`Attribute "${target.path}" is Read-Only and cannot be updated`); | ||
} | ||
@@ -341,3 +344,3 @@ } | ||
for (let path of paths) { | ||
const parts = this._parseJSONPath(path); | ||
const parts = v.parseJSONPath(path); | ||
let attribute = this.attributes; | ||
@@ -351,3 +354,5 @@ for (let part of parts) { | ||
if (target.readOnly) { | ||
throw new Error(`Attribute "${target.name}" is Read-Only and cannot be updated`); | ||
throw new Error(`Attribute "${target.path}" is Read-Only and cannot be updated`); | ||
} else if (operation === ItemOperations.remove && target.required) { | ||
throw new Error(`Attribute "${target.path}" is Required and cannot be removed`); | ||
} | ||
@@ -358,11 +363,2 @@ } | ||
_parseJSONPath(path = "") { | ||
if (typeof path !== "string") { | ||
throw new Error("Path must be a string"); | ||
} | ||
path = path.replace(/\[/g, "."); | ||
path = path.replace(/\]/g, ""); | ||
return path.split("."); | ||
} | ||
static buildOperations(builder, operations) { | ||
@@ -397,5 +393,5 @@ let ops = {}; | ||
builder.setImpacted(operation, paths.json); | ||
if (canNest) { | ||
seen.add(paths.expression); | ||
seen.add(formatted); | ||
} | ||
@@ -428,3 +424,3 @@ | ||
if (attribute === undefined) { | ||
throw new Error(`Invalid attribute "${prop}" on path "${paths.json}".`); | ||
throw new Error(`Invalid attribute "${prop}" at path "${paths.json}".`); | ||
} else if (attribute === root && attribute.type === AttributeTypes.any) { | ||
@@ -451,3 +447,3 @@ // This function is only called if a nested property is called. If this attribute is ultimately the root, don't use the root's field name | ||
} | ||
}) | ||
}); | ||
} | ||
@@ -454,0 +450,0 @@ return attr; |
@@ -1,11 +0,41 @@ | ||
const { CastTypes, AttributeTypes, AttributeMutationMethods, AttributeWildCard, PathTypes } = require("./types"); | ||
const { CastTypes, ValueTypes, AttributeTypes, AttributeMutationMethods, AttributeWildCard, PathTypes, TraverserIndexes } = require("./types"); | ||
const AttributeTypeNames = Object.keys(AttributeTypes); | ||
const ValidFacetTypes = [AttributeTypes.string, AttributeTypes.number, AttributeTypes.boolean, AttributeTypes.enum]; | ||
const e = require("./errors"); | ||
const u = require("./util"); | ||
const {DynamoDBSet} = require("./set"); | ||
function getValueType(value) { | ||
if (value === undefined) { | ||
return ValueTypes.undefined; | ||
} else if (value === null) { | ||
return ValueTypes.null; | ||
} else if (typeof value === "string") { | ||
return ValueTypes.string; | ||
} else if (typeof value === "number") { | ||
return ValueTypes.number; | ||
} else if (typeof value === "boolean") { | ||
return ValueTypes.boolean; | ||
} else if (Array.isArray(value)) { | ||
return ValueTypes.array; | ||
} else if (value.wrapperName === "Set") { | ||
return ValueTypes.aws_set; | ||
} else if (value.constructor.name === "Set") { | ||
return ValueTypes.set; | ||
} else if (value.constructor.name === "Map") { | ||
return ValueTypes.map; | ||
} else if (value.constructor.name === "Object") { | ||
return ValueTypes.object; | ||
} else { | ||
return ValueTypes.unknown; | ||
} | ||
} | ||
class AttributeTraverser { | ||
constructor(parentTraverser) { | ||
if (parentTraverser instanceof AttributeTraverser) { | ||
this.paths = parentTraverser.paths; | ||
this.parent = parentTraverser; | ||
this.paths = this.parent.paths; | ||
} else { | ||
this.parent = null; | ||
this.paths = new Map(); | ||
@@ -16,16 +46,39 @@ } | ||
setChild(name, attribute) { | ||
this.children.set(name, attribute); | ||
} | ||
asChild(name, attribute) { | ||
if (this.parent) { | ||
this.parent.setChild(name, attribute); | ||
} | ||
} | ||
setPath(path, attribute) { | ||
if (this.parent) { | ||
this.parent.setPath(path, attribute); | ||
} | ||
this.paths.set(path, attribute); | ||
this.children.set(path, attribute); | ||
} | ||
getPath(path) { | ||
path = u.genericizeJSONPath(path); | ||
if (this.parent) { | ||
return this.parent.getPath(path); | ||
} | ||
return this.paths.get(path); | ||
} | ||
getChild(path) { | ||
return this.children.get(path); | ||
getChild(name) { | ||
return this.children.get(name); | ||
} | ||
getAllChildren() { | ||
return this.children.entries(); | ||
} | ||
getAll() { | ||
if (this.parent) { | ||
return this.parent.getAll(); | ||
} | ||
return this.paths.entries(); | ||
@@ -35,4 +88,5 @@ } | ||
class Attribute { | ||
constructor(definition = {}, parent = null) { | ||
constructor(definition = {}) { | ||
this.name = definition.name; | ||
@@ -47,4 +101,2 @@ this.field = definition.field || definition.name; | ||
this.validate = this._makeValidate(definition.validate); | ||
this.get = this._makeGet(definition.name, definition.get); | ||
this.set = this._makeSet(definition.name, definition.set); | ||
this.indexes = [...(definition.indexes || [])]; | ||
@@ -61,4 +113,5 @@ let {isWatched, isWatcher, watchedBy, watching, watchAll} = Attribute._destructureWatcher(definition); | ||
this.parentType = definition.parentType; | ||
this.parentPath = definition.parentPath; | ||
const pathType = this.getPathType(this.type, this.parentType); | ||
const path = Attribute.buildPath(this.name, pathType, this.parentType); | ||
const path = Attribute.buildPath(this.name, pathType, this.parentPath); | ||
const fieldPath = Attribute.buildPath(this.field, pathType, this.parentType); | ||
@@ -68,6 +121,55 @@ this.path = path; | ||
this.traverser = new AttributeTraverser(definition.traverser); | ||
this.traverser.setPath(path, this); | ||
this.traverser.setPath(fieldPath, this); | ||
this.traverser.setPath(this.path, this); | ||
this.traverser.setPath(this.fieldPath, this); | ||
this.traverser.asChild(this.name, this); | ||
this.parent = { parentType: this.type, parentPath: this.path }; | ||
this.get = this._makeGet(definition.get); | ||
this.set = this._makeSet(definition.set); | ||
this.client = definition.client; | ||
} | ||
static buildChildAttributes(type, definition, parent) { | ||
let items; | ||
let properties; | ||
if (type === AttributeTypes.list) { | ||
items = Attribute.buildChildListItems(definition, parent); | ||
} else if (type === AttributeTypes.set) { | ||
items = Attribute.buildChildSetItems(definition, parent); | ||
} else if (type === AttributeTypes.map) { | ||
properties = Attribute.buildChildMapProperties(definition, parent); | ||
} | ||
return {items, properties}; | ||
} | ||
static buildChildListItems(definition, parent) { | ||
const {items, client} = definition; | ||
const prop = {...items, ...parent}; | ||
// The use of "*" is to ensure the child's name is "*" when added to the traverser and searching for the children of a list | ||
return Schema.normalizeAttributes({ '*': prop }, {}, {client, traverser: parent.traverser}).attributes["*"]; | ||
} | ||
static buildChildSetItems(definition, parent) { | ||
const {items, client} = definition; | ||
const allowedTypes = [AttributeTypes.string, AttributeTypes.boolean, AttributeTypes.number, AttributeTypes.enum]; | ||
if (!Array.isArray(items) && !allowedTypes.includes(items)) { | ||
throw new e.ElectroError(e.ErrorCodes.InvalidAttributeDefinition, `Invalid "items" definition for Set attribute: "${definition.path}". Acceptable item types include ${u.commaSeparatedString(allowedTypes)}`); | ||
} | ||
const prop = {type: items, ...parent}; | ||
return Schema.normalizeAttributes({ prop }, {}, {client, traverser: parent.traverser}).attributes.prop; | ||
} | ||
static buildChildMapProperties(definition, parent) { | ||
const {properties, client} = definition; | ||
if (!properties || typeof properties !== "object") { | ||
throw new e.ElectroError(e.ErrorCodes.InvalidAttributeDefinition, `Invalid "properties" definition for Map attribute: "${definition.path}". The "properties" definition must describe the attributes that the Map will accept`); | ||
} | ||
const attributes = {}; | ||
for (let name of Object.keys(properties)) { | ||
attributes[name] = {...properties[name], ...parent}; | ||
} | ||
return Schema.normalizeAttributes(attributes, {}, {client, traverser: parent.traverser}); | ||
} | ||
static buildPath(name, type, parentPath) { | ||
@@ -118,2 +220,18 @@ if (!parentPath) return name; | ||
_makeGet(get) { | ||
this._checkGetSet(get, "get"); | ||
const getter = get || ((attr) => attr); | ||
return (values, siblings) => { | ||
if (this.hidden) { | ||
return; | ||
} | ||
return getter(values, siblings); | ||
} | ||
} | ||
_makeSet(set) { | ||
this._checkGetSet(set, "set"); | ||
return set || ((attr) => attr); | ||
} | ||
getPathType(type, parentType) { | ||
@@ -133,23 +251,13 @@ if (parentType === AttributeTypes.list || parentType === AttributeTypes.set) { | ||
return this; | ||
} | ||
return this.traverser.getChild(path); | ||
} | ||
_makeGet(name, get) { | ||
if (typeof get === "function") { | ||
return get; | ||
} else if (get === undefined) { | ||
return (attr) => attr; | ||
} else if (!isNaN(path) && (this.type === AttributeTypes.list || this.type === AttributeTypes.set)) { | ||
// if they're asking for a number, and this is a list, children will be under "*" | ||
return this.traverser.getChild("*"); | ||
} else { | ||
throw new e.ElectroError(e.ErrorCodes.InvalidAttributeDefinition, `Invalid "get" property for attribute ${name}. Please ensure value is a function or undefined.`); | ||
return this.traverser.getChild(path); | ||
} | ||
} | ||
_makeSet(name, set) { | ||
if (typeof set === "function") { | ||
return set; | ||
} else if (set === undefined) { | ||
return (attr) => attr; | ||
} else { | ||
throw new e.ElectroError(e.ErrorCodes.InvalidAttributeDefinition, `Invalid "set" property for attribute ${name}. Please ensure value is a function or undefined.`); | ||
_checkGetSet(val, type) { | ||
if (typeof val !== "function" && val !== undefined) { | ||
throw new e.ElectroError(e.ErrorCodes.InvalidAttributeDefinition, `Invalid "${type}" property for attribute ${this.path}. Please ensure value is a function or undefined.`); | ||
} | ||
@@ -161,3 +269,3 @@ } | ||
throw new e.ElectroError(e.ErrorCodes.InvalidAttributeDefinition, `Invalid "cast" property for attribute: "${name}". Acceptable types include ${CastTypes.join(", ",)}`, | ||
); | ||
); | ||
} else if (cast === AttributeTypes.string) { | ||
@@ -205,3 +313,3 @@ return (val) => { | ||
let isValid = definition.test(val); | ||
let reason = isValid ? "" : "Failed user defined regex"; | ||
let reason = isValid ? "" : `Invalid value for attribute "${this.path}": Failed user defined regex`; | ||
return [isValid, reason]; | ||
@@ -255,3 +363,3 @@ }; | ||
if (value === undefined) { | ||
return [!this.required, this.required ? "Value is required" : ""]; | ||
return [!this.required, this.required ? `Invalid value type at entity path: "${this.path}". Value is required.` : ""]; | ||
} | ||
@@ -264,3 +372,3 @@ let isTyped = false; | ||
if (!isTyped) { | ||
reason = `Value not found in set of acceptable values: ${this.enumArray.join(", ")}`; | ||
reason = `Invalid value type at entity path: "${this.path}". Value not found in set of acceptable values: ${u.commaSeparatedString(this.enumArray)}`; | ||
} | ||
@@ -271,20 +379,2 @@ break; | ||
break; | ||
case AttributeTypes.map: | ||
isTyped = value.constructor.name === "Object" || value.constructor.name === "Map"; | ||
if (!isTyped) { | ||
reason = `Expected value to be an Object to fulfill attribute type "${this.type}"` | ||
} | ||
break; | ||
case AttributeTypes.set: | ||
isTyped = Array.isArray(value) || value.constructor.name === "Set"; | ||
if (!isTyped) { | ||
reason = `Expected value to be an Array or javascript Set to fulfill attribute type "${this.type}"` | ||
} | ||
break; | ||
case AttributeTypes.list: | ||
isTyped = Array.isArray(value); | ||
if (!isTyped) { | ||
reason = `Expected value to be an Array to fulfill attribute type "${this.type}"` | ||
} | ||
break; | ||
case AttributeTypes.string: | ||
@@ -296,3 +386,3 @@ case AttributeTypes.number: | ||
if (!isTyped) { | ||
reason = `Received value of type "${typeof value}", expected value of type "${this.type}"`; | ||
reason = `Invalid value type at entity path: "${this.path}". Received value of type "${typeof value}", expected value of type "${this.type}"`; | ||
} | ||
@@ -328,3 +418,3 @@ break; | ||
// todo: #electroerror | ||
throw new Error(`Invalid value for attribute "${this.name}": ${validationError}.`); | ||
throw new Error(validationError); | ||
} | ||
@@ -335,6 +425,415 @@ return value; | ||
class MapAttribute extends Attribute { | ||
constructor(definition) { | ||
super(definition); | ||
const properties = Attribute.buildChildMapProperties(definition, { | ||
parentType: this.type, | ||
parentPath: this.path, | ||
traverser: this.traverser | ||
}); | ||
this.properties = properties; | ||
this.get = this._makeGet(definition.get, properties); | ||
this.set = this._makeSet(definition.set, properties); | ||
} | ||
_makeGet(get, properties) { | ||
this._checkGetSet(get, "get"); | ||
const getter = get || ((attr) => attr); | ||
return (values, siblings) => { | ||
const data = {}; | ||
if (this.hidden) { | ||
return; | ||
} | ||
if (values === undefined) { | ||
return getter(data, siblings); | ||
} | ||
for (const name of Object.keys(properties.attributes)) { | ||
const attribute = properties.attributes[name]; | ||
if (values[attribute.field] !== undefined) { | ||
let results = attribute.get(values[attribute.field], {...values}); | ||
if (results !== undefined) { | ||
data[name] = results; | ||
} | ||
} | ||
} | ||
return getter(data, siblings); | ||
} | ||
} | ||
_makeSet(set, properties) { | ||
this._checkGetSet(set, "set"); | ||
const setter = set || ((attr) => attr); | ||
return (values, siblings) => { | ||
const data = {}; | ||
if (values === undefined) { | ||
return setter(data, siblings); | ||
} | ||
for (const name of Object.keys(properties.attributes)) { | ||
const attribute = properties.attributes[name]; | ||
if (values[name] !== undefined) { | ||
const results = attribute.set(values[name], {...values}); | ||
if (results !== undefined) { | ||
data[attribute.field] = results; | ||
} | ||
} | ||
} | ||
return setter(data, siblings); | ||
} | ||
} | ||
_isType(value) { | ||
if (value === undefined) { | ||
return [!this.required, this.required ? `Invalid value type at entity path: "${this.path}". Value is required.` : ""]; | ||
} | ||
const valueType = getValueType(value); | ||
if (valueType !== ValueTypes.object) { | ||
return [false, `Invalid value type at entity path "${this.path}. Received value of type "${valueType}", expected value of type "object"`]; | ||
} | ||
let reason = ""; | ||
const [childrenAreValid, childErrors] = this._validateChildren(value); | ||
if (!childrenAreValid) { | ||
reason = childErrors; | ||
} | ||
return [childrenAreValid, reason] | ||
} | ||
_validateChildren(value) { | ||
const valueType = getValueType(value); | ||
const attributes = this.properties.attributes; | ||
const errors = []; | ||
if (valueType === ValueTypes.object) { | ||
for (const child of Object.keys(attributes)) { | ||
const [isValid, errorMessages] = attributes[child].isValid(value === undefined ? value : value[child]) | ||
if (!isValid) { | ||
errors.push(errorMessages); | ||
} | ||
} | ||
} else if (valueType !== ValueTypes.object) { | ||
errors.push( | ||
`Invalid value type at entity path: "${this.path}". Expected value to be an object to fulfill attribute type "${this.type}"` | ||
); | ||
} else if (this.properties.hasRequiredAttributes) { | ||
errors.push( | ||
`Invalid value type at entity path: "${this.path}". Map attribute requires at least the properties ${u.commaSeparatedString(Object.keys(attributes))}` | ||
); | ||
} | ||
return [errors.length === 0, errors.filter(Boolean).join(", ")]; | ||
} | ||
val(value) { | ||
const getValue = (v) => { | ||
v = this.cast(v); | ||
if (v === undefined) { | ||
v = this.default(); | ||
} | ||
return v; | ||
} | ||
const valueType = getValueType(value); | ||
if (value === undefined) { | ||
return getValue(value); | ||
} else if (value && valueType !== "object" && Object.keys(value).length === 0) { | ||
return getValue(value); | ||
} else if (valueType !== "object") { | ||
throw new Error(`Invalid value type at entity path: "${this.path}". Expected value to be an object to fulfill attribute type "${this.type}"`); | ||
} | ||
const data = {}; | ||
for (const name of Object.keys(this.properties.attributes)) { | ||
const attribute = this.properties.attributes[name]; | ||
const results = attribute.val(value[attribute.name]); | ||
if (results !== undefined) { | ||
data[name] = results; | ||
} | ||
} | ||
if (Object.keys(data).length > 0) { | ||
return getValue(data); | ||
} else { | ||
return getValue(); | ||
} | ||
} | ||
} | ||
class ListAttribute extends Attribute { | ||
constructor(definition) { | ||
super(definition); | ||
const items = Attribute.buildChildListItems(definition, { | ||
parentType: this.type, | ||
parentPath: this.path, | ||
traverser: this.traverser | ||
}); | ||
this.items = items; | ||
this.get = this._makeGet(definition.get, items); | ||
this.set = this._makeSet(definition.set, items); | ||
} | ||
_makeGet(get, items) { | ||
this._checkGetSet(get, "get"); | ||
const getter = get || ((attr) => attr); | ||
return (values, siblings) => { | ||
const data = []; | ||
if (this.hidden) { | ||
return; | ||
} | ||
if (values === undefined) { | ||
return getter(data, siblings); | ||
} | ||
for (let value of values) { | ||
const results = items.get(value, [...values]); | ||
if (results !== undefined) { | ||
data.push(results); | ||
} | ||
} | ||
return getter(data, siblings); | ||
} | ||
} | ||
_makeSet(set, items) { | ||
this._checkGetSet(set, "set"); | ||
const setter = set || ((attr) => attr); | ||
return (values, siblings) => { | ||
const data = []; | ||
if (values === undefined) { | ||
return setter(values, siblings); | ||
} | ||
for (const value of values) { | ||
const results = items.set(value, [...values]); | ||
if (results !== undefined) { | ||
data.push(results); | ||
} | ||
} | ||
return setter(data, siblings); | ||
} | ||
} | ||
_validateArrayValue(value) { | ||
const valueType = getValueType(value); | ||
if (value !== undefined && valueType !== ValueTypes.array) { | ||
return [false, `Invalid value type at entity path "${this.path}. Received value of type "${valueType}", expected value of type "array"`]; | ||
} else { | ||
return [true, ""]; | ||
} | ||
} | ||
_isType(value) { | ||
if (value === undefined) { | ||
return [!this.required, this.required ? `Invalid value type at entity path: "${this.path}". Value is required.` : ""]; | ||
} | ||
const [isValidArray, errors] = this._validateArrayValue(value); | ||
if (!isValidArray) { | ||
return [isValidArray, errors]; | ||
} | ||
let reason = ""; | ||
const [childrenAreValid, childErrors] = this._validateChildren(value); | ||
if (!childrenAreValid) { | ||
reason = childErrors; | ||
} | ||
return [childrenAreValid, reason] | ||
} | ||
_validateChildren(value) { | ||
const valueType = getValueType(value); | ||
const errors = []; | ||
if (valueType === ValueTypes.array) { | ||
for (const i in value) { | ||
const [isValid, errorMessages] = this.items.isValid(value[i]); | ||
if (!isValid) { | ||
errors.push(errorMessages + ` at index "${i}"`); | ||
} | ||
} | ||
} else { | ||
errors.push( | ||
`Invalid value type at entity path: "${this.path}". Expected value to be an Array to fulfill attribute type "${this.type}"` | ||
); | ||
} | ||
return [errors.length === 0, errors.filter(Boolean).join(", ")]; | ||
} | ||
val(value) { | ||
const getValue = (v) => { | ||
v = this.cast(v); | ||
if (v === undefined) { | ||
v = this.default(); | ||
} | ||
return v; | ||
} | ||
if (value === undefined) { | ||
return this.default(); | ||
} else if (Array.isArray(value) && value.length === 0) { | ||
return value; | ||
} else if (!Array.isArray(value)) { | ||
throw new Error(`Invalid value type at entity path "${this.path}. Received value of type "${getValueType(value)}", expected value of type "array"`); | ||
} | ||
const data = []; | ||
for (const v of value) { | ||
const results = this.items.val(v); | ||
if (results !== undefined) { | ||
data.push(results); | ||
} | ||
} | ||
if (data.filter(value => value !== undefined).length > 0) { | ||
return getValue(data); | ||
} else { | ||
return getValue(); | ||
} | ||
} | ||
} | ||
class SetAttribute extends Attribute { | ||
constructor(definition) { | ||
super(definition); | ||
const items = Attribute.buildChildSetItems(definition, { | ||
parentType: this.type, | ||
parentPath: this.path, | ||
traverser: this.traverser | ||
}); | ||
this.items = items; | ||
this.get = this._makeGet(definition.get, items); | ||
this.set = this._makeSet(definition.set, items); | ||
} | ||
fromDDBSet(value) { | ||
if (getValueType(value) === ValueTypes.aws_set) { | ||
return value.values; | ||
} | ||
return value; | ||
} | ||
_createDDBSet(value) { | ||
if (this.client && typeof this.client.createSet === "function") { | ||
value = Array.isArray(value) | ||
? Array.from(new Set(value)) | ||
: value; | ||
return this.client.createSet(value, {validate: true}); | ||
} else { | ||
return new DynamoDBSet(value, this.items.type); | ||
} | ||
} | ||
toDDBSet(value) { | ||
const valueType = getValueType(value); | ||
let array; | ||
switch(valueType) { | ||
case ValueTypes.set: | ||
array = Array.from(value); | ||
return this._createDDBSet(array); | ||
case ValueTypes.aws_set: | ||
return value; | ||
case ValueTypes.array: | ||
return this._createDDBSet(value); | ||
case ValueTypes.string: | ||
case ValueTypes.number: { | ||
this.items.getValidate(value); | ||
return this._createDDBSet(value); | ||
} | ||
default: | ||
throw new Error(`Invalid attribute value supplied to "set" attribute "${this.path}". Received value of type "${valueType}". Set values must be supplied as either Arrays, native JavaScript Set objects, DocumentClient Set objects, strings, or numbers.`) | ||
} | ||
} | ||
_makeGet(get, items) { | ||
this._checkGetSet(get, "get"); | ||
const getter = get || ((attr) => attr); | ||
return (values, siblings) => { | ||
if (values !== undefined) { | ||
const data = this.fromDDBSet(values); | ||
return getter(data, siblings); | ||
} | ||
let results = getter(data, siblings); | ||
if (results !== undefined) { | ||
// if not undefined, try to convert, else no need to return | ||
return this.fromDDBSet(results); | ||
} | ||
} | ||
} | ||
_makeSet(set, items) { | ||
this._checkGetSet(set, "set"); | ||
const setter = set || ((attr) => attr); | ||
return (values, siblings) => { | ||
const results = setter(values, siblings); | ||
if (results !== undefined) { | ||
return this.toDDBSet(results); | ||
} | ||
} | ||
} | ||
_isType(value) { | ||
if (value === undefined) { | ||
return [!this.required, this.required ? `Invalid value type at entity path: "${this.path}". Value is required.` : ""]; | ||
} | ||
let reason = ""; | ||
const [childrenAreValid, childErrors] = this._validateChildren(value); | ||
if (!childrenAreValid) { | ||
reason = childErrors; | ||
} | ||
return [childrenAreValid, reason] | ||
} | ||
_validateChildren(value) { | ||
const valueType = getValueType(value); | ||
const errors = []; | ||
let arr = []; | ||
if (valueType === ValueTypes.array) { | ||
arr = value; | ||
} else if (valueType === ValueTypes.set) { | ||
arr = Array.from(value); | ||
} else if (valueType === ValueTypes.aws_set) { | ||
arr = value.values; | ||
} else { | ||
errors.push( | ||
`Invalid value type at attribute path: "${this.path}". Expected value to be an Expected value to be an Array, native JavaScript Set objects, or DocumentClient Set objects to fulfill attribute type "${this.type}"` | ||
) | ||
} | ||
for (const item of arr) { | ||
const [isValid, errorMessage] = this.items.isValid(item); | ||
if (!isValid) { | ||
errors.push(errorMessage); | ||
} | ||
} | ||
return [errors.length === 0, errors.filter(Boolean).join(", ")]; | ||
} | ||
val(value) { | ||
if (value === undefined) { | ||
value = this.default(); | ||
} | ||
if (value !== undefined) { | ||
return this.toDDBSet(value); | ||
} | ||
} | ||
} | ||
class Schema { | ||
constructor(properties = {}, facets = {}, traverser = new AttributeTraverser()) { | ||
constructor(properties = {}, facets = {}, {traverser = new AttributeTraverser(), client} = {}) { | ||
this._validateProperties(properties); | ||
let schema = Schema.normalizeAttributes(properties, facets, traverser); | ||
let schema = Schema.normalizeAttributes(properties, facets, {traverser, client}); | ||
this.client = client; | ||
this.attributes = schema.attributes; | ||
@@ -351,3 +850,3 @@ this.enums = schema.enums; | ||
static normalizeAttributes(attributes = {}, facets = {}, traverser) { | ||
static normalizeAttributes(attributes = {}, facets = {}, {traverser, client} = {}) { | ||
let invalidProperties = []; | ||
@@ -360,3 +859,3 @@ let normalized = {}; | ||
let watchedAttributes = {}; | ||
let requiredAttributes = {}; | ||
let requiredAttributes = new Set(); | ||
let hiddenAttributes = new Set(); | ||
@@ -375,3 +874,3 @@ let readOnlyAttributes = new Set(); | ||
} | ||
if (attribute.field && facets.fields.includes(attribute.field)) { | ||
if (attribute.field && facets.fields && facets.fields.includes(attribute.field)) { | ||
continue; | ||
@@ -383,2 +882,3 @@ } | ||
traverser, | ||
client, | ||
label: attribute.label, | ||
@@ -399,3 +899,3 @@ required: !!attribute.required, | ||
parentPath: attribute.parentPath, | ||
parentType: attribute.parentType | ||
parentType: attribute.parentType, | ||
}; | ||
@@ -425,3 +925,3 @@ | ||
if (definition.required) { | ||
requiredAttributes[name] = name; | ||
requiredAttributes.add(name); | ||
} | ||
@@ -459,7 +959,21 @@ | ||
for (let name of Object.keys(definitions)) { | ||
let definition = definitions[name]; | ||
const definition = definitions[name]; | ||
definition.watchedBy = Array.isArray(watchedAttributes[name]) | ||
? watchedAttributes[name] | ||
: [] | ||
normalized[name] = new Attribute(definition); | ||
: []; | ||
switch(definition.type) { | ||
case AttributeTypes.map: | ||
normalized[name] = new MapAttribute(definition); | ||
break; | ||
case AttributeTypes.list: | ||
normalized[name] = new ListAttribute(definition); | ||
break; | ||
case AttributeTypes.set: | ||
normalized[name] = new SetAttribute(definition); | ||
break; | ||
default: | ||
normalized[name] = new Attribute(definition); | ||
} | ||
} | ||
@@ -612,6 +1126,2 @@ | ||
// applyAttributeSetters(payload = {}) { | ||
// return this._fulfillAttributeMutationMethod(AttributeMutationMethods.set, payload); | ||
// } | ||
translateFromFields(item = {}, options = {}) { | ||
@@ -658,3 +1168,5 @@ let { includeKeys } = options; | ||
} else if (attribute.readOnly) { | ||
throw new Error(`Attribute "${attribute.name}" is Read-Only and cannot be updated`); | ||
throw new Error(`Attribute "${attribute.path}" is Read-Only and cannot be removed`); | ||
} else if (attribute.required) { | ||
throw new Error(`Attribute "${attribute.path}" is Required and cannot be removed`); | ||
} | ||
@@ -674,3 +1186,3 @@ } | ||
// todo: #electroerror | ||
throw new Error(`Attribute "${attribute.name}" is Read-Only and cannot be updated`); | ||
throw new Error(`Attribute "${attribute.path}" is Read-Only and cannot be updated`); | ||
} else { | ||
@@ -687,2 +1199,6 @@ record[path] = attribute.getValidate(value); | ||
getRequired() { | ||
return Array.from(this.requiredAttributes); | ||
} | ||
formatItemForRetrieval(item, config) { | ||
@@ -689,0 +1205,0 @@ let remapped = this.translateFromFields(item, config); |
@@ -134,4 +134,43 @@ const KeyTypes = { | ||
filter: "filter" | ||
}; | ||
const ValueTypes = { | ||
string: "string", | ||
boolean: "boolean", | ||
number: "number", | ||
array: "array", | ||
set: "set", | ||
aws_set: "aws_set", | ||
object: "object", | ||
map: "map", | ||
null: "null", | ||
undefined: "undefined", | ||
unknown: "unknown", | ||
}; | ||
const TraverserIndexes = { | ||
readonly: "readonly", | ||
required: "required", | ||
getters: "getters", | ||
setters: "setters" | ||
} | ||
const ReturnValues = { | ||
'default': 'default', | ||
'none': 'none', | ||
'all_old': 'all_old', | ||
'updated_old': 'updated_old', | ||
'all_new': 'all_new', | ||
'updated_new': 'updated_new', | ||
}; | ||
const FormatToReturnValues = { | ||
'none': 'NONE', | ||
'default': 'NONE', | ||
'all_old': 'ALL_OLD', | ||
'updated_old': 'UPDATED_OLD', | ||
'all_new': 'ALL_NEW', | ||
'updated_new': 'UPDATED_NEW' | ||
}; | ||
module.exports = { | ||
@@ -143,5 +182,7 @@ Pager, | ||
QueryTypes, | ||
ValueTypes, | ||
MethodTypes, | ||
Comparisons, | ||
BuilderTypes, | ||
ReturnValues, | ||
MaxBatchItems, | ||
@@ -155,7 +196,9 @@ ModelVersions, | ||
ElectroInstance, | ||
TraverserIndexes, | ||
UnprocessedTypes, | ||
AttributeWildCard, | ||
FormatToReturnValues, | ||
AttributeProxySymbol, | ||
ElectroInstanceTypes, | ||
AttributeMutationMethods, | ||
AttributeMutationMethods | ||
}; |
const v = require("./validations"); | ||
const t = require("./types"); | ||
function parseJSONPath(path = "") { | ||
if (typeof path !== "string") { | ||
throw new Error("Path must be a string"); | ||
} | ||
path = path.replace(/\[/g, "."); | ||
path = path.replace(/\]/g, ""); | ||
return path.split("."); | ||
} | ||
function genericizeJSONPath(path = "") { | ||
return path.replace(/\[\d+\]/g, "[*]"); | ||
} | ||
function getInstanceType(instance = {}) { | ||
@@ -70,6 +83,8 @@ let [isModel, errors] = v.testModel(instance); | ||
batchItems, | ||
parseJSONPath, | ||
getInstanceType, | ||
getModelVersion, | ||
genericizeJSONPath, | ||
commaSeparatedString, | ||
applyBetaModelOverrides | ||
}; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
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
612150
32
10770
4982