Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@jaysalvat/smart-model

Package Overview
Dependencies
Maintainers
1
Versions
31
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@jaysalvat/smart-model - npm Package Compare versions

Comparing version 0.3.2 to 0.3.3

253

build/smart-model.esm.js

@@ -5,13 +5,6 @@ /**!

* https://github.com/jaysalvat/smart-model
* @version 0.3.2 built 2021-02-22 10:45:15
* @version 0.3.3 built 2021-02-23 12:22:54
* @license ISC
* @author Jay Salvat http://jaysalvat.com
*/
class SmartModelError extends Error {
constructor(data) {
super(data.message);
Object.assign(this, data);
}
}
function isArray(value) {

@@ -25,6 +18,2 @@ return Array.isArray(value);

function isEmpty(value) {
return value === "" || value === null || isUndef(value);
}
function isFn(value) {

@@ -39,10 +28,46 @@ return typeof value === "function";

function isClass(value) {
return value.toString().startsWith("class");
return value && value.toString().startsWith("class");
}
function isSmartModel(value) {
return value.prototype instanceof SmartModel || value instanceof SmartModel;
}
function isPlainObject(value) {
return value.toString() === "[object Object]";
return value && value.toString() === "[object Object]";
}
function isType(value, Type) {
function keys(obj, cb = function() {}) {
return Object.keys(obj).map(cb);
}
function toArray(value) {
return [].concat([], value);
}
function merge(source, target) {
target = Object.assign({}, source, target);
keys(source, (key => {
if (isPlainObject(source[key]) && isPlainObject(target[key])) {
target[key] = Object.assign({}, source[key], merge(source[key], target[key]));
}
}));
return target;
}
function eject(target) {
target = Object.assign({}, target);
keys(target, (key => {
if (isSmartModel(target[key])) {
target[key] = target[key].$eject();
}
}));
return target;
}
function pascalCase(string) {
return string.normalize("NFD").replace(/[\u0300-\u036f]/g, "").match(/[a-z1-9]+/gi).map((word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase())).join("");
}
function checkType(value, Type) {
const match = Type && Type.toString().match(/^\s*function (\w+)/);

@@ -69,15 +94,32 @@ const type = (match ? match[1] : "object").toLowerCase();

function toArray(value) {
return [].concat([], value);
class SmartModelError extends Error {
constructor(data) {
super(data.message);
Object.assign(this, data);
}
}
function pascalCase(string) {
return string.normalize("NFD").replace(/[\u0300-\u036f]/g, "").match(/[a-z]+/gi).map((word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase())).join("");
}
SmartModelError.throw = function(settings, code, message, property, source) {
const shortCode = code.split(":")[0];
if (settings.exceptions === true || isPlainObject(settings.exceptions) && settings.exceptions[shortCode]) {
throw new SmartModelError({
message: message,
property: property,
code: code,
source: source && source.constructor.name
});
}
};
function checkErrors(entry, property, value) {
function checkErrors(entry, property, value, first, settings) {
const errors = [];
if (entry.required && isEmpty(value)) {
if (settings.strict && (!entry || !keys(entry).length)) {
errors.push({
message: `Invalid value 'required' on property '${property}'`,
message: `Property "${property}" can't be set in strict mode`,
code: "strict"
});
}
if (entry.required && settings.empty(value)) {
errors.push({
message: `Property "${property}" is "required"`,
code: "required"

@@ -87,20 +129,29 @@ });

}
if (entry.readonly && !first) {
errors.push({
message: `Property '${property}' is 'readonly'`,
code: "readonly"
});
return errors;
}
if (typeof value === "undefined") {
return errors;
}
if (entry.type && (entry.required || !isEmpty(value))) {
if (!toArray(entry.type).some((type => isType(value, type)))) {
errors.push({
message: `Invalid type '${typeof value}' on property '${property}'`,
code: "type"
});
if (entry.type && (entry.required || !settings.empty(value))) {
if (!(isSmartModel(entry.type) && isPlainObject(value))) {
if (!toArray(entry.type).some((type => checkType(value, type)))) {
errors.push({
message: `Property "${property}" has an invalid type "${typeof value}"`,
code: "type"
});
}
}
}
if (entry.rule) {
Object.keys(entry.rule).forEach((key => {
keys(entry.rule, (key => {
const rule = entry.rule[key];
if (rule(value)) {
errors.push({
message: `Invalid value '${key}' on property '${property}'`,
code: key
message: `Property "${property}" breaks the "${key}" rule`,
code: "rule:" + key
});

@@ -117,6 +168,8 @@ }

}
const Child = entry.type.prototype instanceof SmartModel ? entry.type : false;
const Child = isSmartModel(entry.type) ? entry.type : false;
const schema = isPlainObject(entry.type) ? entry.type : false;
if (Child || schema) {
return Child ? Child : SmartModel.create(pascalCase(property), schema, settings);
const Model = Child ? Child : SmartModel.create(pascalCase(property), schema, settings);
entry.type = Model;
return Model;
}

@@ -130,18 +183,14 @@ return false;

set(target, property, value) {
let entry = schema[property];
const entry = schema[property] || {};
const old = target[property];
const updated = !isEqual(value, old);
const first = isUndef(old);
const updated = !first && !isEqual(value, old);
const Nested = createNested(entry, property, settings);
function trigger(method, args) {
return Reflect.apply(method, target, args ? args : [ property, value, old, schema ]);
const returned = Reflect.apply(method, target, args ? args : [ property, value, old, schema ]);
return !isUndef(returned) ? returned : value;
}
if (!entry) {
if (settings.strict) {
return true;
}
entry = {};
}
trigger(target.onBeforeSet);
value = trigger(target.$onBeforeSet);
if (updated) {
trigger(target.onBeforeUpdate);
value = trigger(target.$onBeforeUpdate);
}

@@ -151,20 +200,20 @@ if (isFn(entry.transform)) {

}
if (settings.exceptions) {
const errors = checkErrors(entry, property, value);
if (errors.length) {
throw new SmartModelError({
message: errors[0].message,
property: property,
code: errors[0].code,
source: target.constructor.name
});
const errors = checkErrors(entry, property, value, first, settings);
if (errors.length) {
if (settings.exceptions) {
SmartModelError.throw(settings, errors[0].code, errors[0].message, property, target);
} else {
return true;
}
}
if (settings.strict && !keys(entry).length) {
return true;
}
if (Nested) {
value = new Nested(value instanceof Object ? value : {});
value = new Nested(value);
}
target[property] = value;
trigger(target.onSet);
trigger(target.$onSet);
if (updated) {
trigger(target.onUpdate);
trigger(target.$onUpdate);
}

@@ -176,2 +225,7 @@ return true;

let value = target[property];
if (property === "$eject") {
return function() {
return eject(target);
};
}
if (!entry) {

@@ -181,5 +235,6 @@ return target[property];

function trigger(method, args) {
return Reflect.apply(method, target, args ? args : [ property, value, schema ]);
const returned = Reflect.apply(method, target, args ? args : [ property, value, schema ]);
return !isUndef(returned) ? returned : value;
}
trigger(target.onBeforeGet);
value = trigger(target.$onBeforeGet);
if (isFn(entry)) {

@@ -191,3 +246,3 @@ value = trigger(entry, [ target, schema ]);

}
trigger(target.onGet);
value = trigger(target.$onGet);
return value;

@@ -197,3 +252,3 @@ },

const value = target[property];
const entry = schema[property];
const entry = schema[property] || {};
function trigger(method, args) {

@@ -203,12 +258,8 @@ return Reflect.apply(method, target, args ? args : [ property, value, schema ]);

if (entry.required) {
throw new SmartModelError({
message: `Invalid delete on required propery ${property}`,
property: property,
code: "required"
});
SmartModelError.throw(settings, "required", `Property "${property}" is "required"`, property, target);
}
trigger(target.onBeforeDelete);
trigger(target.$onBeforeDelete);
Reflect.deleteProperty(target, property);
trigger(target.onDelete);
trigger(target.onUpdate);
trigger(target.$onDelete);
trigger(target.$onUpdate);
return true;

@@ -223,3 +274,3 @@ }

super(schema, settings);
Object.keys(schema).forEach((key => {
keys(schema, (key => {
if (isUndef(data[key])) {

@@ -233,26 +284,56 @@ if (!isUndef(schema[key].default)) {

}));
this.feed(data);
this.$patch(data);
}
feed(data) {
Object.keys(data).forEach((key => {
$patch(data) {
keys(data, (key => {
this[key] = data[key];
}));
}
onBeforeGet() {}
onBeforeSet() {}
onBeforeUpdate() {}
onDelete() {}
onGet() {}
onBeforeDelete() {}
onSet() {}
onUpdate() {}
$put(data) {
keys(this, (key => {
if (data[key]) {
if (isSmartModel(this[key])) {
this[key].$put(data[key]);
} else {
this[key] = data[key];
}
} else {
this.$delete(key);
}
}));
keys(data, (key => {
if (!this[key]) {
this[key] = data[key];
}
}));
}
$delete(properties) {
toArray(properties).forEach((key => {
Reflect.deleteProperty(this, key);
}));
}
$onBeforeGet() {}
$onBeforeSet() {}
$onBeforeUpdate() {}
$onDelete() {}
$onGet() {}
$onBeforeDelete() {}
$onSet() {}
$onUpdate() {}
}
SmartModel.settings = {
empty: value => value === "" || value === null || isUndef(value),
strict: false,
exceptions: true
exceptions: {
readonly: false,
required: true,
rule: true,
strict: false,
type: true
}
};
SmartModel.create = function(name, schema, settings, prototype) {
settings = Object.assign({}, SmartModel.settings, settings);
settings = merge(SmartModel.settings, settings);
const Model = {

@@ -267,3 +348,3 @@ [name]: class extends SmartModel {

const invalidations = {};
Object.keys(schema).forEach((property => {
keys(schema, (property => {
let subErrors;

@@ -276,3 +357,3 @@ const value = payload[property];

}
let errors = checkErrors(entry, property, value);
let errors = checkErrors(entry, property, value, false, settings);
if (subErrors) {

@@ -289,3 +370,3 @@ invalidations[property] = subErrors;

}));
return Object.keys(invalidations).length ? invalidations : false;
return keys(invalidations).length ? invalidations : false;
};

@@ -292,0 +373,0 @@ Model.hydrate = function(payload) {

@@ -1,2 +0,2 @@

/*! SmartModel v0.3.2 */
class e extends Error{constructor(e){super(e.message),Object.assign(this,e)}}function t(e){return Array.isArray(e)}function r(e){return void 0===e}function n(e){return""===e||null===e||r(e)}function o(e){return"function"==typeof e}function s(e){return e.toString().startsWith("class")}function c(e){return[].concat([],e)}function u(e,r,o){const u=[];return e.required&&n(o)?(u.push({message:`Invalid value 'required' on property '${r}'`,code:"required"}),u):(void 0===o||(!e.type||!e.required&&n(o)||c(e.type).some((e=>function(e,r){const n=r&&r.toString().match(/^\s*function (\w+)/),o=(n?n[1]:"object").toLowerCase();if("date"===o&&e instanceof r)return!0;if("array"===o&&t(e))return!0;if("object"===o){if(s(r)&&e instanceof r)return!0;if(!s(r)&&typeof e===o)return!0}else if(typeof e===o)return!0;return!1}(o,e)))||u.push({message:`Invalid type '${typeof o}' on property '${r}'`,code:"type"}),e.rule&&Object.keys(e.rule).forEach((t=>{(0,e.rule[t])(o)&&u.push({message:`Invalid value '${t}' on property '${r}'`,code:t})}))),u)}function i(e={},t,r){if(!e.type)return!1;const n=e.type.prototype instanceof a&&e.type,o="[object Object]"===e.type.toString()&&e.type;return!(!n&&!o)&&(n||a.create(t.normalize("NFD").replace(/[\u0300-\u036f]/g,"").match(/[a-z]+/gi).map((e=>e.charAt(0).toUpperCase()+e.substr(1).toLowerCase())).join(""),o,r))}class a extends class{constructor(t,r){return new Proxy(this,{set(n,s,c){let a=t[s];const f=n[s],p=(l=f,!(JSON.stringify(c)===JSON.stringify(l)));var l;const d=i(a,s,r);function y(e,r){return Reflect.apply(e,n,r||[s,c,f,t])}if(!a){if(r.strict)return!0;a={}}if(y(n.onBeforeSet),p&&y(n.onBeforeUpdate),o(a.transform)&&(c=y(a.transform,[c,t])),r.exceptions){const t=u(a,s,c);if(t.length)throw new e({message:t[0].message,property:s,code:t[0].code,source:n.constructor.name})}return d&&(c=new d(c instanceof Object?c:{})),n[s]=c,y(n.onSet),p&&y(n.onUpdate),!0},get(e,r){const n=t[r];let s=e[r];if(!n)return e[r];function c(n,o){return Reflect.apply(n,e,o||[r,s,t])}return c(e.onBeforeGet),o(n)&&(s=c(n,[e,t])),o(n.format)&&(s=c(n.format,[s,t])),c(e.onGet),s},deleteProperty(r,n){const o=r[n];function s(e,s){return Reflect.apply(e,r,s||[n,o,t])}if(t[n].required)throw new e({message:`Invalid delete on required propery ${n}`,property:n,code:"required"});return s(r.onBeforeDelete),Reflect.deleteProperty(r,n),s(r.onDelete),s(r.onUpdate),!0}})}}{constructor(e={},t={},n){super(e,n),Object.keys(e).forEach((n=>{r(t[n])&&(this[n]=r(e[n].default)?t[n]:e[n].default)})),this.feed(t)}feed(e){Object.keys(e).forEach((t=>{this[t]=e[t]}))}onBeforeGet(){}onBeforeSet(){}onBeforeUpdate(){}onDelete(){}onGet(){}onBeforeDelete(){}onSet(){}onUpdate(){}}a.settings={strict:!1,exceptions:!0},a.create=function(e,r,n,o){n=Object.assign({},a.settings,n);const s={[e]:class extends a{constructor(e){super(r,e,n)}}}[e];return s.checkErrors=function(e,t){const o={};return Object.keys(r).forEach((s=>{let a;const f=e[s],p=r[s],l=i(p,s,n);l&&(a=l.checkErrors(f,t));let d=u(p,s,f);a?o[s]=a:d.length&&(t&&(d=d.filter((e=>!c(t).includes(e.code)))),d.length&&(o[s]=d))})),!!Object.keys(o).length&&o},s.hydrate=function(e){return t(e)?e.map((e=>new s(e))):new s(e)},Object.assign(s.prototype,o),s.schema=r,s};export default a;
/*! SmartModel v0.3.3 */
function e(e){return Array.isArray(e)}function t(e){return void 0===e}function r(e){return"function"==typeof e}function n(e){return e&&e.toString().startsWith("class")}function o(e){return e.prototype instanceof l||e instanceof l}function s(e){return e&&"[object Object]"===e.toString()}function c(e,t=function(){}){return Object.keys(e).map(t)}function i(e){return[].concat([],e)}function u(e,t){return t=Object.assign({},e,t),c(e,(r=>{s(e[r])&&s(t[r])&&(t[r]=Object.assign({},e[r],u(e[r],t[r])))})),t}class a extends Error{constructor(e){super(e.message),Object.assign(this,e)}}function f(t,r,u,a,f){const p=[];return!f.strict||t&&c(t).length||p.push({message:`Property "${r}" can't be set in strict mode`,code:"strict"}),t.required&&f.empty(u)?(p.push({message:`Property "${r}" is "required"`,code:"required"}),p):t.readonly&&!a?(p.push({message:`Property '${r}' is 'readonly'`,code:"readonly"}),p):(void 0===u||(!t.type||!t.required&&f.empty(u)||o(t.type)&&s(u)||i(t.type).some((t=>function(t,r){const o=r&&r.toString().match(/^\s*function (\w+)/),s=(o?o[1]:"object").toLowerCase();if("date"===s&&t instanceof r)return!0;if("array"===s&&e(t))return!0;if("object"===s){if(n(r)&&t instanceof r)return!0;if(!n(r)&&typeof t===s)return!0}else if(typeof t===s)return!0;return!1}(u,t)))||p.push({message:`Property "${r}" has an invalid type "${typeof u}"`,code:"type"}),t.rule&&c(t.rule,(e=>{(0,t.rule[e])(u)&&p.push({message:`Property "${r}" breaks the "${e}" rule`,code:"rule:"+e})}))),p)}function p(e={},t,r){if(!e.type)return!1;const n=!!o(e.type)&&e.type,c=!!s(e.type)&&e.type;if(n||c){const o=n||l.create(t.normalize("NFD").replace(/[\u0300-\u036f]/g,"").match(/[a-z1-9]+/gi).map((e=>e.charAt(0).toUpperCase()+e.substr(1).toLowerCase())).join(""),c,r);return e.type=o,o}return!1}a.throw=function(e,t,r,n,o){const c=t.split(":")[0];if(!0===e.exceptions||s(e.exceptions)&&e.exceptions[c])throw new a({message:r,property:n,code:t,source:o&&o.constructor.name})};class l extends class{constructor(e,n){return new Proxy(this,{set(o,s,i){const u=e[s]||{},l=o[s],y=t(l),d=!(y||(h=i,$=l,JSON.stringify(h)===JSON.stringify($)));var h,$;const g=p(u,s,n);function m(r,n){const c=Reflect.apply(r,o,n||[s,i,l,e]);return t(c)?i:c}i=m(o.$onBeforeSet),d&&(i=m(o.$onBeforeUpdate)),r(u.transform)&&(i=m(u.transform,[i,e]));const b=f(u,s,i,y,n);if(b.length){if(!n.exceptions)return!0;a.throw(n,b[0].code,b[0].message,s,o)}return n.strict&&!c(u).length||(g&&(i=new g(i)),o[s]=i,m(o.$onSet),d&&m(o.$onUpdate)),!0},get(n,s){const i=e[s];let u=n[s];if("$eject"===s)return function(){return function(e){return c(e=Object.assign({},e),(t=>{o(e[t])&&(e[t]=e[t].$eject())})),e}(n)};if(!i)return n[s];function a(r,o){const c=Reflect.apply(r,n,o||[s,u,e]);return t(c)?u:c}return u=a(n.$onBeforeGet),r(i)&&(u=a(i,[n,e])),r(i.format)&&(u=a(i.format,[u,e])),u=a(n.$onGet),u},deleteProperty(t,r){const o=t[r];function s(n,s){return Reflect.apply(n,t,s||[r,o,e])}return(e[r]||{}).required&&a.throw(n,"required",`Property "${r}" is "required"`,r,t),s(t.$onBeforeDelete),Reflect.deleteProperty(t,r),s(t.$onDelete),s(t.$onUpdate),!0}})}}{constructor(e={},r={},n){super(e,n),c(e,(n=>{t(r[n])&&(this[n]=t(e[n].default)?r[n]:e[n].default)})),this.$patch(r)}$patch(e){c(e,(t=>{this[t]=e[t]}))}$put(e){c(this,(t=>{e[t]?o(this[t])?this[t].$put(e[t]):this[t]=e[t]:this.$delete(t)})),c(e,(t=>{this[t]||(this[t]=e[t])}))}$delete(e){i(e).forEach((e=>{Reflect.deleteProperty(this,e)}))}$onBeforeGet(){}$onBeforeSet(){}$onBeforeUpdate(){}$onDelete(){}$onGet(){}$onBeforeDelete(){}$onSet(){}$onUpdate(){}}l.settings={empty:e=>""===e||null===e||t(e),strict:!1,exceptions:{readonly:!1,required:!0,rule:!0,strict:!1,type:!0}},l.create=function(t,r,n,o){n=u(l.settings,n);const s={[t]:class extends l{constructor(e){super(r,e,n)}}}[t];return s.checkErrors=function(e,t){const o={};return c(r,(s=>{let c;const u=e[s],a=r[s],l=p(a,s,n);l&&(c=l.checkErrors(u,t));let y=f(a,s,u,!1,n);c?o[s]=c:y.length&&(t&&(y=y.filter((e=>!i(t).includes(e.code)))),y.length&&(o[s]=y))})),!!c(o).length&&o},s.hydrate=function(t){return e(t)?t.map((e=>new s(e))):new s(t)},Object.assign(s.prototype,o),s.schema=r,s};export default l;

@@ -5,3 +5,3 @@ /**!

* https://github.com/jaysalvat/smart-model
* @version 0.3.2 built 2021-02-22 10:45:15
* @version 0.3.3 built 2021-02-23 12:22:54
* @license ISC

@@ -12,8 +12,2 @@ * @author Jay Salvat http://jaysalvat.com

"use strict";
class SmartModelError extends Error {
constructor(data) {
super(data.message);
Object.assign(this, data);
}
}
function isArray(value) {

@@ -25,5 +19,2 @@ return Array.isArray(value);

}
function isEmpty(value) {
return value === "" || value === null || isUndef(value);
}
function isFn(value) {

@@ -36,8 +27,38 @@ return typeof value === "function";

function isClass(value) {
return value.toString().startsWith("class");
return value && value.toString().startsWith("class");
}
function isSmartModel(value) {
return value.prototype instanceof SmartModel || value instanceof SmartModel;
}
function isPlainObject(value) {
return value.toString() === "[object Object]";
return value && value.toString() === "[object Object]";
}
function isType(value, Type) {
function keys(obj, cb = function() {}) {
return Object.keys(obj).map(cb);
}
function toArray(value) {
return [].concat([], value);
}
function merge(source, target) {
target = Object.assign({}, source, target);
keys(source, (key => {
if (isPlainObject(source[key]) && isPlainObject(target[key])) {
target[key] = Object.assign({}, source[key], merge(source[key], target[key]));
}
}));
return target;
}
function eject(target) {
target = Object.assign({}, target);
keys(target, (key => {
if (isSmartModel(target[key])) {
target[key] = target[key].$eject();
}
}));
return target;
}
function pascalCase(string) {
return string.normalize("NFD").replace(/[\u0300-\u036f]/g, "").match(/[a-z1-9]+/gi).map((word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase())).join("");
}
function checkType(value, Type) {
const match = Type && Type.toString().match(/^\s*function (\w+)/);

@@ -63,13 +84,30 @@ const type = (match ? match[1] : "object").toLowerCase();

}
function toArray(value) {
return [].concat([], value);
class SmartModelError extends Error {
constructor(data) {
super(data.message);
Object.assign(this, data);
}
}
function pascalCase(string) {
return string.normalize("NFD").replace(/[\u0300-\u036f]/g, "").match(/[a-z]+/gi).map((word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase())).join("");
}
function checkErrors(entry, property, value) {
SmartModelError.throw = function(settings, code, message, property, source) {
const shortCode = code.split(":")[0];
if (settings.exceptions === true || isPlainObject(settings.exceptions) && settings.exceptions[shortCode]) {
throw new SmartModelError({
message: message,
property: property,
code: code,
source: source && source.constructor.name
});
}
};
function checkErrors(entry, property, value, first, settings) {
const errors = [];
if (entry.required && isEmpty(value)) {
if (settings.strict && (!entry || !keys(entry).length)) {
errors.push({
message: `Invalid value 'required' on property '${property}'`,
message: `Property "${property}" can't be set in strict mode`,
code: "strict"
});
}
if (entry.required && settings.empty(value)) {
errors.push({
message: `Property "${property}" is "required"`,
code: "required"

@@ -79,20 +117,29 @@ });

}
if (entry.readonly && !first) {
errors.push({
message: `Property '${property}' is 'readonly'`,
code: "readonly"
});
return errors;
}
if (typeof value === "undefined") {
return errors;
}
if (entry.type && (entry.required || !isEmpty(value))) {
if (!toArray(entry.type).some((type => isType(value, type)))) {
errors.push({
message: `Invalid type '${typeof value}' on property '${property}'`,
code: "type"
});
if (entry.type && (entry.required || !settings.empty(value))) {
if (!(isSmartModel(entry.type) && isPlainObject(value))) {
if (!toArray(entry.type).some((type => checkType(value, type)))) {
errors.push({
message: `Property "${property}" has an invalid type "${typeof value}"`,
code: "type"
});
}
}
}
if (entry.rule) {
Object.keys(entry.rule).forEach((key => {
keys(entry.rule, (key => {
const rule = entry.rule[key];
if (rule(value)) {
errors.push({
message: `Invalid value '${key}' on property '${property}'`,
code: key
message: `Property "${property}" breaks the "${key}" rule`,
code: "rule:" + key
});

@@ -108,6 +155,8 @@ }

}
const Child = entry.type.prototype instanceof SmartModel ? entry.type : false;
const Child = isSmartModel(entry.type) ? entry.type : false;
const schema = isPlainObject(entry.type) ? entry.type : false;
if (Child || schema) {
return Child ? Child : SmartModel.create(pascalCase(property), schema, settings);
const Model = Child ? Child : SmartModel.create(pascalCase(property), schema, settings);
entry.type = Model;
return Model;
}

@@ -120,18 +169,14 @@ return false;

set(target, property, value) {
let entry = schema[property];
const entry = schema[property] || {};
const old = target[property];
const updated = !isEqual(value, old);
const first = isUndef(old);
const updated = !first && !isEqual(value, old);
const Nested = createNested(entry, property, settings);
function trigger(method, args) {
return Reflect.apply(method, target, args ? args : [ property, value, old, schema ]);
const returned = Reflect.apply(method, target, args ? args : [ property, value, old, schema ]);
return !isUndef(returned) ? returned : value;
}
if (!entry) {
if (settings.strict) {
return true;
}
entry = {};
}
trigger(target.onBeforeSet);
value = trigger(target.$onBeforeSet);
if (updated) {
trigger(target.onBeforeUpdate);
value = trigger(target.$onBeforeUpdate);
}

@@ -141,20 +186,20 @@ if (isFn(entry.transform)) {

}
if (settings.exceptions) {
const errors = checkErrors(entry, property, value);
if (errors.length) {
throw new SmartModelError({
message: errors[0].message,
property: property,
code: errors[0].code,
source: target.constructor.name
});
const errors = checkErrors(entry, property, value, first, settings);
if (errors.length) {
if (settings.exceptions) {
SmartModelError.throw(settings, errors[0].code, errors[0].message, property, target);
} else {
return true;
}
}
if (settings.strict && !keys(entry).length) {
return true;
}
if (Nested) {
value = new Nested(value instanceof Object ? value : {});
value = new Nested(value);
}
target[property] = value;
trigger(target.onSet);
trigger(target.$onSet);
if (updated) {
trigger(target.onUpdate);
trigger(target.$onUpdate);
}

@@ -166,2 +211,7 @@ return true;

let value = target[property];
if (property === "$eject") {
return function() {
return eject(target);
};
}
if (!entry) {

@@ -171,5 +221,6 @@ return target[property];

function trigger(method, args) {
return Reflect.apply(method, target, args ? args : [ property, value, schema ]);
const returned = Reflect.apply(method, target, args ? args : [ property, value, schema ]);
return !isUndef(returned) ? returned : value;
}
trigger(target.onBeforeGet);
value = trigger(target.$onBeforeGet);
if (isFn(entry)) {

@@ -181,3 +232,3 @@ value = trigger(entry, [ target, schema ]);

}
trigger(target.onGet);
value = trigger(target.$onGet);
return value;

@@ -187,3 +238,3 @@ },

const value = target[property];
const entry = schema[property];
const entry = schema[property] || {};
function trigger(method, args) {

@@ -193,12 +244,8 @@ return Reflect.apply(method, target, args ? args : [ property, value, schema ]);

if (entry.required) {
throw new SmartModelError({
message: `Invalid delete on required propery ${property}`,
property: property,
code: "required"
});
SmartModelError.throw(settings, "required", `Property "${property}" is "required"`, property, target);
}
trigger(target.onBeforeDelete);
trigger(target.$onBeforeDelete);
Reflect.deleteProperty(target, property);
trigger(target.onDelete);
trigger(target.onUpdate);
trigger(target.$onDelete);
trigger(target.$onUpdate);
return true;

@@ -212,3 +259,3 @@ }

super(schema, settings);
Object.keys(schema).forEach((key => {
keys(schema, (key => {
if (isUndef(data[key])) {

@@ -222,24 +269,54 @@ if (!isUndef(schema[key].default)) {

}));
this.feed(data);
this.$patch(data);
}
feed(data) {
Object.keys(data).forEach((key => {
$patch(data) {
keys(data, (key => {
this[key] = data[key];
}));
}
onBeforeGet() {}
onBeforeSet() {}
onBeforeUpdate() {}
onDelete() {}
onGet() {}
onBeforeDelete() {}
onSet() {}
onUpdate() {}
$put(data) {
keys(this, (key => {
if (data[key]) {
if (isSmartModel(this[key])) {
this[key].$put(data[key]);
} else {
this[key] = data[key];
}
} else {
this.$delete(key);
}
}));
keys(data, (key => {
if (!this[key]) {
this[key] = data[key];
}
}));
}
$delete(properties) {
toArray(properties).forEach((key => {
Reflect.deleteProperty(this, key);
}));
}
$onBeforeGet() {}
$onBeforeSet() {}
$onBeforeUpdate() {}
$onDelete() {}
$onGet() {}
$onBeforeDelete() {}
$onSet() {}
$onUpdate() {}
}
SmartModel.settings = {
empty: value => value === "" || value === null || isUndef(value),
strict: false,
exceptions: true
exceptions: {
readonly: false,
required: true,
rule: true,
strict: false,
type: true
}
};
SmartModel.create = function(name, schema, settings, prototype) {
settings = Object.assign({}, SmartModel.settings, settings);
settings = merge(SmartModel.settings, settings);
const Model = {

@@ -254,3 +331,3 @@ [name]: class extends SmartModel {

const invalidations = {};
Object.keys(schema).forEach((property => {
keys(schema, (property => {
let subErrors;

@@ -263,3 +340,3 @@ const value = payload[property];

}
let errors = checkErrors(entry, property, value);
let errors = checkErrors(entry, property, value, false, settings);
if (subErrors) {

@@ -276,3 +353,3 @@ invalidations[property] = subErrors;

}));
return Object.keys(invalidations).length ? invalidations : false;
return keys(invalidations).length ? invalidations : false;
};

@@ -279,0 +356,0 @@ Model.hydrate = function(payload) {

@@ -1,2 +0,2 @@

/*! SmartModel v0.3.2 */
var SmartModel=function(){"use strict";class e extends Error{constructor(e){super(e.message),Object.assign(this,e)}}function t(e){return Array.isArray(e)}function r(e){return void 0===e}function n(e){return""===e||null===e||r(e)}function o(e){return"function"==typeof e}function s(e){return e.toString().startsWith("class")}function c(e){return[].concat([],e)}function u(e,r,o){const u=[];return e.required&&n(o)?(u.push({message:`Invalid value 'required' on property '${r}'`,code:"required"}),u):(void 0===o||(!e.type||!e.required&&n(o)||c(e.type).some((e=>function(e,r){const n=r&&r.toString().match(/^\s*function (\w+)/),o=(n?n[1]:"object").toLowerCase();if("date"===o&&e instanceof r)return!0;if("array"===o&&t(e))return!0;if("object"===o){if(s(r)&&e instanceof r)return!0;if(!s(r)&&typeof e===o)return!0}else if(typeof e===o)return!0;return!1}(o,e)))||u.push({message:`Invalid type '${typeof o}' on property '${r}'`,code:"type"}),e.rule&&Object.keys(e.rule).forEach((t=>{(0,e.rule[t])(o)&&u.push({message:`Invalid value '${t}' on property '${r}'`,code:t})}))),u)}function i(e={},t,r){if(!e.type)return!1;const n=e.type.prototype instanceof a&&e.type,o="[object Object]"===e.type.toString()&&e.type;return!(!n&&!o)&&(n||a.create(t.normalize("NFD").replace(/[\u0300-\u036f]/g,"").match(/[a-z]+/gi).map((e=>e.charAt(0).toUpperCase()+e.substr(1).toLowerCase())).join(""),o,r))}class a extends class{constructor(t,r){return new Proxy(this,{set(n,s,c){let a=t[s];const f=n[s],p=(l=f,!(JSON.stringify(c)===JSON.stringify(l)));var l;const d=i(a,s,r);function y(e,r){return Reflect.apply(e,n,r||[s,c,f,t])}if(!a){if(r.strict)return!0;a={}}if(y(n.onBeforeSet),p&&y(n.onBeforeUpdate),o(a.transform)&&(c=y(a.transform,[c,t])),r.exceptions){const t=u(a,s,c);if(t.length)throw new e({message:t[0].message,property:s,code:t[0].code,source:n.constructor.name})}return d&&(c=new d(c instanceof Object?c:{})),n[s]=c,y(n.onSet),p&&y(n.onUpdate),!0},get(e,r){const n=t[r];let s=e[r];if(!n)return e[r];function c(n,o){return Reflect.apply(n,e,o||[r,s,t])}return c(e.onBeforeGet),o(n)&&(s=c(n,[e,t])),o(n.format)&&(s=c(n.format,[s,t])),c(e.onGet),s},deleteProperty(r,n){const o=r[n];function s(e,s){return Reflect.apply(e,r,s||[n,o,t])}if(t[n].required)throw new e({message:`Invalid delete on required propery ${n}`,property:n,code:"required"});return s(r.onBeforeDelete),Reflect.deleteProperty(r,n),s(r.onDelete),s(r.onUpdate),!0}})}}{constructor(e={},t={},n){super(e,n),Object.keys(e).forEach((n=>{r(t[n])&&(this[n]=r(e[n].default)?t[n]:e[n].default)})),this.feed(t)}feed(e){Object.keys(e).forEach((t=>{this[t]=e[t]}))}onBeforeGet(){}onBeforeSet(){}onBeforeUpdate(){}onDelete(){}onGet(){}onBeforeDelete(){}onSet(){}onUpdate(){}}return a.settings={strict:!1,exceptions:!0},a.create=function(e,r,n,o){n=Object.assign({},a.settings,n);const s={[e]:class extends a{constructor(e){super(r,e,n)}}}[e];return s.checkErrors=function(e,t){const o={};return Object.keys(r).forEach((s=>{let a;const f=e[s],p=r[s],l=i(p,s,n);l&&(a=l.checkErrors(f,t));let d=u(p,s,f);a?o[s]=a:d.length&&(t&&(d=d.filter((e=>!c(t).includes(e.code)))),d.length&&(o[s]=d))})),!!Object.keys(o).length&&o},s.hydrate=function(e){return t(e)?e.map((e=>new s(e))):new s(e)},Object.assign(s.prototype,o),s.schema=r,s},a}();
/*! SmartModel v0.3.3 */
var SmartModel=function(){"use strict";function e(e){return Array.isArray(e)}function t(e){return void 0===e}function r(e){return"function"==typeof e}function n(e){return e&&e.toString().startsWith("class")}function o(e){return e.prototype instanceof l||e instanceof l}function s(e){return e&&"[object Object]"===e.toString()}function c(e,t=function(){}){return Object.keys(e).map(t)}function i(e){return[].concat([],e)}function u(e,t){return t=Object.assign({},e,t),c(e,(r=>{s(e[r])&&s(t[r])&&(t[r]=Object.assign({},e[r],u(e[r],t[r])))})),t}class a extends Error{constructor(e){super(e.message),Object.assign(this,e)}}function f(t,r,u,a,f){const p=[];return!f.strict||t&&c(t).length||p.push({message:`Property "${r}" can't be set in strict mode`,code:"strict"}),t.required&&f.empty(u)?(p.push({message:`Property "${r}" is "required"`,code:"required"}),p):t.readonly&&!a?(p.push({message:`Property '${r}' is 'readonly'`,code:"readonly"}),p):(void 0===u||(!t.type||!t.required&&f.empty(u)||o(t.type)&&s(u)||i(t.type).some((t=>function(t,r){const o=r&&r.toString().match(/^\s*function (\w+)/),s=(o?o[1]:"object").toLowerCase();if("date"===s&&t instanceof r)return!0;if("array"===s&&e(t))return!0;if("object"===s){if(n(r)&&t instanceof r)return!0;if(!n(r)&&typeof t===s)return!0}else if(typeof t===s)return!0;return!1}(u,t)))||p.push({message:`Property "${r}" has an invalid type "${typeof u}"`,code:"type"}),t.rule&&c(t.rule,(e=>{(0,t.rule[e])(u)&&p.push({message:`Property "${r}" breaks the "${e}" rule`,code:"rule:"+e})}))),p)}function p(e={},t,r){if(!e.type)return!1;const n=!!o(e.type)&&e.type,c=!!s(e.type)&&e.type;if(n||c){const o=n||l.create(t.normalize("NFD").replace(/[\u0300-\u036f]/g,"").match(/[a-z1-9]+/gi).map((e=>e.charAt(0).toUpperCase()+e.substr(1).toLowerCase())).join(""),c,r);return e.type=o,o}return!1}a.throw=function(e,t,r,n,o){const c=t.split(":")[0];if(!0===e.exceptions||s(e.exceptions)&&e.exceptions[c])throw new a({message:r,property:n,code:t,source:o&&o.constructor.name})};class l extends class{constructor(e,n){return new Proxy(this,{set(o,s,i){const u=e[s]||{},l=o[s],y=t(l),d=!(y||(h=i,$=l,JSON.stringify(h)===JSON.stringify($)));var h,$;const g=p(u,s,n);function m(r,n){const c=Reflect.apply(r,o,n||[s,i,l,e]);return t(c)?i:c}i=m(o.$onBeforeSet),d&&(i=m(o.$onBeforeUpdate)),r(u.transform)&&(i=m(u.transform,[i,e]));const b=f(u,s,i,y,n);if(b.length){if(!n.exceptions)return!0;a.throw(n,b[0].code,b[0].message,s,o)}return n.strict&&!c(u).length||(g&&(i=new g(i)),o[s]=i,m(o.$onSet),d&&m(o.$onUpdate)),!0},get(n,s){const i=e[s];let u=n[s];if("$eject"===s)return function(){return function(e){return c(e=Object.assign({},e),(t=>{o(e[t])&&(e[t]=e[t].$eject())})),e}(n)};if(!i)return n[s];function a(r,o){const c=Reflect.apply(r,n,o||[s,u,e]);return t(c)?u:c}return u=a(n.$onBeforeGet),r(i)&&(u=a(i,[n,e])),r(i.format)&&(u=a(i.format,[u,e])),u=a(n.$onGet),u},deleteProperty(t,r){const o=t[r];function s(n,s){return Reflect.apply(n,t,s||[r,o,e])}return(e[r]||{}).required&&a.throw(n,"required",`Property "${r}" is "required"`,r,t),s(t.$onBeforeDelete),Reflect.deleteProperty(t,r),s(t.$onDelete),s(t.$onUpdate),!0}})}}{constructor(e={},r={},n){super(e,n),c(e,(n=>{t(r[n])&&(this[n]=t(e[n].default)?r[n]:e[n].default)})),this.$patch(r)}$patch(e){c(e,(t=>{this[t]=e[t]}))}$put(e){c(this,(t=>{e[t]?o(this[t])?this[t].$put(e[t]):this[t]=e[t]:this.$delete(t)})),c(e,(t=>{this[t]||(this[t]=e[t])}))}$delete(e){i(e).forEach((e=>{Reflect.deleteProperty(this,e)}))}$onBeforeGet(){}$onBeforeSet(){}$onBeforeUpdate(){}$onDelete(){}$onGet(){}$onBeforeDelete(){}$onSet(){}$onUpdate(){}}return l.settings={empty:e=>""===e||null===e||t(e),strict:!1,exceptions:{readonly:!1,required:!0,rule:!0,strict:!1,type:!0}},l.create=function(t,r,n,o){n=u(l.settings,n);const s={[t]:class extends l{constructor(e){super(r,e,n)}}}[t];return s.checkErrors=function(e,t){const o={};return c(r,(s=>{let c;const u=e[s],a=r[s],l=p(a,s,n);l&&(c=l.checkErrors(u,t));let y=f(a,s,u,!1,n);c?o[s]=c:y.length&&(t&&(y=y.filter((e=>!i(t).includes(e.code)))),y.length&&(o[s]=y))})),!!c(o).length&&o},s.hydrate=function(t){return e(t)?t.map((e=>new s(e))):new s(t)},Object.assign(s.prototype,o),s.schema=r,s},l}();

@@ -5,3 +5,3 @@ /**!

* https://github.com/jaysalvat/smart-model
* @version 0.3.2 built 2021-02-22 10:45:15
* @version 0.3.3 built 2021-02-23 12:22:54
* @license ISC

@@ -15,8 +15,2 @@ * @author Jay Salvat http://jaysalvat.com

"use strict";
class SmartModelError extends Error {
constructor(data) {
super(data.message);
Object.assign(this, data);
}
}
function isArray(value) {

@@ -28,5 +22,2 @@ return Array.isArray(value);

}
function isEmpty(value) {
return value === "" || value === null || isUndef(value);
}
function isFn(value) {

@@ -39,8 +30,38 @@ return typeof value === "function";

function isClass(value) {
return value.toString().startsWith("class");
return value && value.toString().startsWith("class");
}
function isSmartModel(value) {
return value.prototype instanceof SmartModel || value instanceof SmartModel;
}
function isPlainObject(value) {
return value.toString() === "[object Object]";
return value && value.toString() === "[object Object]";
}
function isType(value, Type) {
function keys(obj, cb = function() {}) {
return Object.keys(obj).map(cb);
}
function toArray(value) {
return [].concat([], value);
}
function merge(source, target) {
target = Object.assign({}, source, target);
keys(source, (key => {
if (isPlainObject(source[key]) && isPlainObject(target[key])) {
target[key] = Object.assign({}, source[key], merge(source[key], target[key]));
}
}));
return target;
}
function eject(target) {
target = Object.assign({}, target);
keys(target, (key => {
if (isSmartModel(target[key])) {
target[key] = target[key].$eject();
}
}));
return target;
}
function pascalCase(string) {
return string.normalize("NFD").replace(/[\u0300-\u036f]/g, "").match(/[a-z1-9]+/gi).map((word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase())).join("");
}
function checkType(value, Type) {
const match = Type && Type.toString().match(/^\s*function (\w+)/);

@@ -66,13 +87,30 @@ const type = (match ? match[1] : "object").toLowerCase();

}
function toArray(value) {
return [].concat([], value);
class SmartModelError extends Error {
constructor(data) {
super(data.message);
Object.assign(this, data);
}
}
function pascalCase(string) {
return string.normalize("NFD").replace(/[\u0300-\u036f]/g, "").match(/[a-z]+/gi).map((word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase())).join("");
}
function checkErrors(entry, property, value) {
SmartModelError.throw = function(settings, code, message, property, source) {
const shortCode = code.split(":")[0];
if (settings.exceptions === true || isPlainObject(settings.exceptions) && settings.exceptions[shortCode]) {
throw new SmartModelError({
message: message,
property: property,
code: code,
source: source && source.constructor.name
});
}
};
function checkErrors(entry, property, value, first, settings) {
const errors = [];
if (entry.required && isEmpty(value)) {
if (settings.strict && (!entry || !keys(entry).length)) {
errors.push({
message: `Invalid value 'required' on property '${property}'`,
message: `Property "${property}" can't be set in strict mode`,
code: "strict"
});
}
if (entry.required && settings.empty(value)) {
errors.push({
message: `Property "${property}" is "required"`,
code: "required"

@@ -82,20 +120,29 @@ });

}
if (entry.readonly && !first) {
errors.push({
message: `Property '${property}' is 'readonly'`,
code: "readonly"
});
return errors;
}
if (typeof value === "undefined") {
return errors;
}
if (entry.type && (entry.required || !isEmpty(value))) {
if (!toArray(entry.type).some((type => isType(value, type)))) {
errors.push({
message: `Invalid type '${typeof value}' on property '${property}'`,
code: "type"
});
if (entry.type && (entry.required || !settings.empty(value))) {
if (!(isSmartModel(entry.type) && isPlainObject(value))) {
if (!toArray(entry.type).some((type => checkType(value, type)))) {
errors.push({
message: `Property "${property}" has an invalid type "${typeof value}"`,
code: "type"
});
}
}
}
if (entry.rule) {
Object.keys(entry.rule).forEach((key => {
keys(entry.rule, (key => {
const rule = entry.rule[key];
if (rule(value)) {
errors.push({
message: `Invalid value '${key}' on property '${property}'`,
code: key
message: `Property "${property}" breaks the "${key}" rule`,
code: "rule:" + key
});

@@ -111,6 +158,8 @@ }

}
const Child = entry.type.prototype instanceof SmartModel ? entry.type : false;
const Child = isSmartModel(entry.type) ? entry.type : false;
const schema = isPlainObject(entry.type) ? entry.type : false;
if (Child || schema) {
return Child ? Child : SmartModel.create(pascalCase(property), schema, settings);
const Model = Child ? Child : SmartModel.create(pascalCase(property), schema, settings);
entry.type = Model;
return Model;
}

@@ -123,18 +172,14 @@ return false;

set(target, property, value) {
let entry = schema[property];
const entry = schema[property] || {};
const old = target[property];
const updated = !isEqual(value, old);
const first = isUndef(old);
const updated = !first && !isEqual(value, old);
const Nested = createNested(entry, property, settings);
function trigger(method, args) {
return Reflect.apply(method, target, args ? args : [ property, value, old, schema ]);
const returned = Reflect.apply(method, target, args ? args : [ property, value, old, schema ]);
return !isUndef(returned) ? returned : value;
}
if (!entry) {
if (settings.strict) {
return true;
}
entry = {};
}
trigger(target.onBeforeSet);
value = trigger(target.$onBeforeSet);
if (updated) {
trigger(target.onBeforeUpdate);
value = trigger(target.$onBeforeUpdate);
}

@@ -144,20 +189,20 @@ if (isFn(entry.transform)) {

}
if (settings.exceptions) {
const errors = checkErrors(entry, property, value);
if (errors.length) {
throw new SmartModelError({
message: errors[0].message,
property: property,
code: errors[0].code,
source: target.constructor.name
});
const errors = checkErrors(entry, property, value, first, settings);
if (errors.length) {
if (settings.exceptions) {
SmartModelError.throw(settings, errors[0].code, errors[0].message, property, target);
} else {
return true;
}
}
if (settings.strict && !keys(entry).length) {
return true;
}
if (Nested) {
value = new Nested(value instanceof Object ? value : {});
value = new Nested(value);
}
target[property] = value;
trigger(target.onSet);
trigger(target.$onSet);
if (updated) {
trigger(target.onUpdate);
trigger(target.$onUpdate);
}

@@ -169,2 +214,7 @@ return true;

let value = target[property];
if (property === "$eject") {
return function() {
return eject(target);
};
}
if (!entry) {

@@ -174,5 +224,6 @@ return target[property];

function trigger(method, args) {
return Reflect.apply(method, target, args ? args : [ property, value, schema ]);
const returned = Reflect.apply(method, target, args ? args : [ property, value, schema ]);
return !isUndef(returned) ? returned : value;
}
trigger(target.onBeforeGet);
value = trigger(target.$onBeforeGet);
if (isFn(entry)) {

@@ -184,3 +235,3 @@ value = trigger(entry, [ target, schema ]);

}
trigger(target.onGet);
value = trigger(target.$onGet);
return value;

@@ -190,3 +241,3 @@ },

const value = target[property];
const entry = schema[property];
const entry = schema[property] || {};
function trigger(method, args) {

@@ -196,12 +247,8 @@ return Reflect.apply(method, target, args ? args : [ property, value, schema ]);

if (entry.required) {
throw new SmartModelError({
message: `Invalid delete on required propery ${property}`,
property: property,
code: "required"
});
SmartModelError.throw(settings, "required", `Property "${property}" is "required"`, property, target);
}
trigger(target.onBeforeDelete);
trigger(target.$onBeforeDelete);
Reflect.deleteProperty(target, property);
trigger(target.onDelete);
trigger(target.onUpdate);
trigger(target.$onDelete);
trigger(target.$onUpdate);
return true;

@@ -215,3 +262,3 @@ }

super(schema, settings);
Object.keys(schema).forEach((key => {
keys(schema, (key => {
if (isUndef(data[key])) {

@@ -225,24 +272,54 @@ if (!isUndef(schema[key].default)) {

}));
this.feed(data);
this.$patch(data);
}
feed(data) {
Object.keys(data).forEach((key => {
$patch(data) {
keys(data, (key => {
this[key] = data[key];
}));
}
onBeforeGet() {}
onBeforeSet() {}
onBeforeUpdate() {}
onDelete() {}
onGet() {}
onBeforeDelete() {}
onSet() {}
onUpdate() {}
$put(data) {
keys(this, (key => {
if (data[key]) {
if (isSmartModel(this[key])) {
this[key].$put(data[key]);
} else {
this[key] = data[key];
}
} else {
this.$delete(key);
}
}));
keys(data, (key => {
if (!this[key]) {
this[key] = data[key];
}
}));
}
$delete(properties) {
toArray(properties).forEach((key => {
Reflect.deleteProperty(this, key);
}));
}
$onBeforeGet() {}
$onBeforeSet() {}
$onBeforeUpdate() {}
$onDelete() {}
$onGet() {}
$onBeforeDelete() {}
$onSet() {}
$onUpdate() {}
}
SmartModel.settings = {
empty: value => value === "" || value === null || isUndef(value),
strict: false,
exceptions: true
exceptions: {
readonly: false,
required: true,
rule: true,
strict: false,
type: true
}
};
SmartModel.create = function(name, schema, settings, prototype) {
settings = Object.assign({}, SmartModel.settings, settings);
settings = merge(SmartModel.settings, settings);
const Model = {

@@ -257,3 +334,3 @@ [name]: class extends SmartModel {

const invalidations = {};
Object.keys(schema).forEach((property => {
keys(schema, (property => {
let subErrors;

@@ -266,3 +343,3 @@ const value = payload[property];

}
let errors = checkErrors(entry, property, value);
let errors = checkErrors(entry, property, value, false, settings);
if (subErrors) {

@@ -279,3 +356,3 @@ invalidations[property] = subErrors;

}));
return Object.keys(invalidations).length ? invalidations : false;
return keys(invalidations).length ? invalidations : false;
};

@@ -282,0 +359,0 @@ Model.hydrate = function(payload) {

@@ -1,2 +0,2 @@

/*! SmartModel v0.3.2 */
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).SmartModel=t()}(this,(function(){"use strict";class e extends Error{constructor(e){super(e.message),Object.assign(this,e)}}function t(e){return Array.isArray(e)}function r(e){return void 0===e}function n(e){return""===e||null===e||r(e)}function o(e){return"function"==typeof e}function s(e){return e.toString().startsWith("class")}function c(e){return[].concat([],e)}function i(e,r,o){const i=[];return e.required&&n(o)?(i.push({message:`Invalid value 'required' on property '${r}'`,code:"required"}),i):(void 0===o||(!e.type||!e.required&&n(o)||c(e.type).some((e=>function(e,r){const n=r&&r.toString().match(/^\s*function (\w+)/),o=(n?n[1]:"object").toLowerCase();if("date"===o&&e instanceof r)return!0;if("array"===o&&t(e))return!0;if("object"===o){if(s(r)&&e instanceof r)return!0;if(!s(r)&&typeof e===o)return!0}else if(typeof e===o)return!0;return!1}(o,e)))||i.push({message:`Invalid type '${typeof o}' on property '${r}'`,code:"type"}),e.rule&&Object.keys(e.rule).forEach((t=>{(0,e.rule[t])(o)&&i.push({message:`Invalid value '${t}' on property '${r}'`,code:t})}))),i)}function u(e={},t,r){if(!e.type)return!1;const n=e.type.prototype instanceof f&&e.type,o="[object Object]"===e.type.toString()&&e.type;return!(!n&&!o)&&(n||f.create(t.normalize("NFD").replace(/[\u0300-\u036f]/g,"").match(/[a-z]+/gi).map((e=>e.charAt(0).toUpperCase()+e.substr(1).toLowerCase())).join(""),o,r))}class f extends class{constructor(t,r){return new Proxy(this,{set(n,s,c){let f=t[s];const a=n[s],p=(l=a,!(JSON.stringify(c)===JSON.stringify(l)));var l;const d=u(f,s,r);function y(e,r){return Reflect.apply(e,n,r||[s,c,a,t])}if(!f){if(r.strict)return!0;f={}}if(y(n.onBeforeSet),p&&y(n.onBeforeUpdate),o(f.transform)&&(c=y(f.transform,[c,t])),r.exceptions){const t=i(f,s,c);if(t.length)throw new e({message:t[0].message,property:s,code:t[0].code,source:n.constructor.name})}return d&&(c=new d(c instanceof Object?c:{})),n[s]=c,y(n.onSet),p&&y(n.onUpdate),!0},get(e,r){const n=t[r];let s=e[r];if(!n)return e[r];function c(n,o){return Reflect.apply(n,e,o||[r,s,t])}return c(e.onBeforeGet),o(n)&&(s=c(n,[e,t])),o(n.format)&&(s=c(n.format,[s,t])),c(e.onGet),s},deleteProperty(r,n){const o=r[n];function s(e,s){return Reflect.apply(e,r,s||[n,o,t])}if(t[n].required)throw new e({message:`Invalid delete on required propery ${n}`,property:n,code:"required"});return s(r.onBeforeDelete),Reflect.deleteProperty(r,n),s(r.onDelete),s(r.onUpdate),!0}})}}{constructor(e={},t={},n){super(e,n),Object.keys(e).forEach((n=>{r(t[n])&&(this[n]=r(e[n].default)?t[n]:e[n].default)})),this.feed(t)}feed(e){Object.keys(e).forEach((t=>{this[t]=e[t]}))}onBeforeGet(){}onBeforeSet(){}onBeforeUpdate(){}onDelete(){}onGet(){}onBeforeDelete(){}onSet(){}onUpdate(){}}return f.settings={strict:!1,exceptions:!0},f.create=function(e,r,n,o){n=Object.assign({},f.settings,n);const s={[e]:class extends f{constructor(e){super(r,e,n)}}}[e];return s.checkErrors=function(e,t){const o={};return Object.keys(r).forEach((s=>{let f;const a=e[s],p=r[s],l=u(p,s,n);l&&(f=l.checkErrors(a,t));let d=i(p,s,a);f?o[s]=f:d.length&&(t&&(d=d.filter((e=>!c(t).includes(e.code)))),d.length&&(o[s]=d))})),!!Object.keys(o).length&&o},s.hydrate=function(e){return t(e)?e.map((e=>new s(e))):new s(e)},Object.assign(s.prototype,o),s.schema=r,s},f}));
/*! SmartModel v0.3.3 */
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).SmartModel=t()}(this,(function(){"use strict";function e(e){return Array.isArray(e)}function t(e){return void 0===e}function r(e){return"function"==typeof e}function n(e){return e&&e.toString().startsWith("class")}function o(e){return e.prototype instanceof l||e instanceof l}function s(e){return e&&"[object Object]"===e.toString()}function c(e,t=function(){}){return Object.keys(e).map(t)}function i(e){return[].concat([],e)}function u(e,t){return t=Object.assign({},e,t),c(e,(r=>{s(e[r])&&s(t[r])&&(t[r]=Object.assign({},e[r],u(e[r],t[r])))})),t}class f extends Error{constructor(e){super(e.message),Object.assign(this,e)}}function a(t,r,u,f,a){const p=[];return!a.strict||t&&c(t).length||p.push({message:`Property "${r}" can't be set in strict mode`,code:"strict"}),t.required&&a.empty(u)?(p.push({message:`Property "${r}" is "required"`,code:"required"}),p):t.readonly&&!f?(p.push({message:`Property '${r}' is 'readonly'`,code:"readonly"}),p):(void 0===u||(!t.type||!t.required&&a.empty(u)||o(t.type)&&s(u)||i(t.type).some((t=>function(t,r){const o=r&&r.toString().match(/^\s*function (\w+)/),s=(o?o[1]:"object").toLowerCase();if("date"===s&&t instanceof r)return!0;if("array"===s&&e(t))return!0;if("object"===s){if(n(r)&&t instanceof r)return!0;if(!n(r)&&typeof t===s)return!0}else if(typeof t===s)return!0;return!1}(u,t)))||p.push({message:`Property "${r}" has an invalid type "${typeof u}"`,code:"type"}),t.rule&&c(t.rule,(e=>{(0,t.rule[e])(u)&&p.push({message:`Property "${r}" breaks the "${e}" rule`,code:"rule:"+e})}))),p)}function p(e={},t,r){if(!e.type)return!1;const n=!!o(e.type)&&e.type,c=!!s(e.type)&&e.type;if(n||c){const o=n||l.create(t.normalize("NFD").replace(/[\u0300-\u036f]/g,"").match(/[a-z1-9]+/gi).map((e=>e.charAt(0).toUpperCase()+e.substr(1).toLowerCase())).join(""),c,r);return e.type=o,o}return!1}f.throw=function(e,t,r,n,o){const c=t.split(":")[0];if(!0===e.exceptions||s(e.exceptions)&&e.exceptions[c])throw new f({message:r,property:n,code:t,source:o&&o.constructor.name})};class l extends class{constructor(e,n){return new Proxy(this,{set(o,s,i){const u=e[s]||{},l=o[s],d=t(l),y=!(d||(h=i,$=l,JSON.stringify(h)===JSON.stringify($)));var h,$;const g=p(u,s,n);function m(r,n){const c=Reflect.apply(r,o,n||[s,i,l,e]);return t(c)?i:c}i=m(o.$onBeforeSet),y&&(i=m(o.$onBeforeUpdate)),r(u.transform)&&(i=m(u.transform,[i,e]));const b=a(u,s,i,d,n);if(b.length){if(!n.exceptions)return!0;f.throw(n,b[0].code,b[0].message,s,o)}return n.strict&&!c(u).length||(g&&(i=new g(i)),o[s]=i,m(o.$onSet),y&&m(o.$onUpdate)),!0},get(n,s){const i=e[s];let u=n[s];if("$eject"===s)return function(){return function(e){return c(e=Object.assign({},e),(t=>{o(e[t])&&(e[t]=e[t].$eject())})),e}(n)};if(!i)return n[s];function f(r,o){const c=Reflect.apply(r,n,o||[s,u,e]);return t(c)?u:c}return u=f(n.$onBeforeGet),r(i)&&(u=f(i,[n,e])),r(i.format)&&(u=f(i.format,[u,e])),u=f(n.$onGet),u},deleteProperty(t,r){const o=t[r];function s(n,s){return Reflect.apply(n,t,s||[r,o,e])}return(e[r]||{}).required&&f.throw(n,"required",`Property "${r}" is "required"`,r,t),s(t.$onBeforeDelete),Reflect.deleteProperty(t,r),s(t.$onDelete),s(t.$onUpdate),!0}})}}{constructor(e={},r={},n){super(e,n),c(e,(n=>{t(r[n])&&(this[n]=t(e[n].default)?r[n]:e[n].default)})),this.$patch(r)}$patch(e){c(e,(t=>{this[t]=e[t]}))}$put(e){c(this,(t=>{e[t]?o(this[t])?this[t].$put(e[t]):this[t]=e[t]:this.$delete(t)})),c(e,(t=>{this[t]||(this[t]=e[t])}))}$delete(e){i(e).forEach((e=>{Reflect.deleteProperty(this,e)}))}$onBeforeGet(){}$onBeforeSet(){}$onBeforeUpdate(){}$onDelete(){}$onGet(){}$onBeforeDelete(){}$onSet(){}$onUpdate(){}}return l.settings={empty:e=>""===e||null===e||t(e),strict:!1,exceptions:{readonly:!1,required:!0,rule:!0,strict:!1,type:!0}},l.create=function(t,r,n,o){n=u(l.settings,n);const s={[t]:class extends l{constructor(e){super(r,e,n)}}}[t];return s.checkErrors=function(e,t){const o={};return c(r,(s=>{let c;const u=e[s],f=r[s],l=p(f,s,n);l&&(c=l.checkErrors(u,t));let d=a(f,s,u,!1,n);c?o[s]=c:d.length&&(t&&(d=d.filter((e=>!i(t).includes(e.code)))),d.length&&(o[s]=d))})),!!c(o).length&&o},s.hydrate=function(t){return e(t)?t.map((e=>new s(e))):new s(t)},Object.assign(s.prototype,o),s.schema=r,s},l}));
{
"name": "@jaysalvat/smart-model",
"version": "0.3.2",
"version": "0.3.3",
"description": "Javascript object model",

@@ -5,0 +5,0 @@ "main": "./build/smart-model.cjs.min.cjs",

@@ -0,9 +1,20 @@

```
________ _____ ______ ________ ________ _________ _____ ______ ________ ________ _______ ___
|\ ____\|\ _ \ _ \|\ __ \|\ __ \|\___ ___\\ _ \ _ \|\ __ \|\ ___ \|\ ___ \ |\ \
\ \ \___|\ \ \\\__\ \ \ \ \|\ \ \ \|\ \|___ \ \_\ \ \\\__\ \ \ \ \|\ \ \ \_|\ \ \ __/|\ \ \
\ \_____ \ \ \\|__| \ \ \ __ \ \ _ _\ \ \ \ \ \ \\|__| \ \ \ \\\ \ \ \ \\ \ \ \_|/_\ \ \
\|____|\ \ \ \ \ \ \ \ \ \ \ \ \\ \| \ \ \ \ \ \ \ \ \ \ \\\ \ \ \_\\ \ \ \_|\ \ \ \____
____\_\ \ \__\ \ \__\ \__\ \__\ \__\\ _\ \ \__\ \ \__\ \ \__\ \_______\ \_______\ \_______\ \_______\
|\_________\|__| \|__|\|__|\|__|\|__|\|__| \|__| \|__| \|__|\|_______|\|_______|\|_______|\|_______|
\|_________|
```
[![npm version](https://badge.fury.io/js/%40jaysalvat%2Fsmart-model.svg)](https://badge.fury.io/js/%40jaysalvat%2Fsmart-model)
SmartModel
==========
Model
=============
Javascript object model.
- [x] ~1Kb gzipped
- [x] 1Kb+ gzipped
- [x] Value transformation

@@ -14,2 +25,3 @@ - [x] Value format

- [x] Default value
- [x] Readonly properties
- [x] Virtual property

@@ -26,2 +38,4 @@ - [x] Throw exception (or not) if invalid

[Provided formats](https://github.com/jaysalvat/smart-model/tree/master/build) are IFFE, ESM, CJS and UMD, all minified.
### NPM

@@ -114,7 +128,191 @@

const post = new Post({
title: 'my new post',
body: 'lorem ipsum...'
title: 'My new post',
body: 'Lorem ipsum...',
author: {
firstname: 'Brad',
lastname: 'Pitt'
}
})
```
## Documentation
### Schema
Options for property:
| Option | Type | Description
| ----------- | ------- | ---
| type | any | The required type (*) of a value. You can set a schema or another model (*) in order to nest models
| required | bool | The value is required. See `settings.empty` for the empty check function
| readonly | bool | The value can't be overwritten
| default | any | The default value if the property is undefined
| transform | fn | A function to transform the value to set
| format | fn | A function to format the value to get
| rule | object | An object which contains the validation rules (**)
#### [*] Type
Type can be `String`, `Boolean`, `Number`, `Date`, `Function` or a class.
If a schema is set as a type, a nested model will be created.
```javascript
const Post = SmartModel.create('Post', {
title: {
type: String
},
body: {
type: String
},
Date: {
type: Date
},
author: {
type {
firstname: {
type: String
},
lastnaame: {
type: String
}
}
}
})
```
An existing Model can be set as a type in order to nest this Model.
```javascript
const Author = SmartModel.create('Author', {
firstname: {
type: String
},
lastnaame: {
type: String
}
})
const Post = SmartModel.create('Post', {
title: {
type: String
},
body: {
type: String
},
Date: {
type: Date
},
body: {
type: string
},
author: {
type: Author
}
})
```
#### [**]s Rule
Multiple rules of validation can be set on a property.
```javascript
const Discount = SmartModel.create('Discount', {
percent: {
type: Number,
rule: {
'min': {value) => value < 0,
'max': {value) => value > 100
}
}
})
```
### Settings
| Option | Type | Default | Description
| ----------- | ----------- | ------------- | ---
| strict | bool | false | Allow to set property not present in the schema
| empty | fn | fn (***) | Function to check if a value is empty if required
| exceptions | bool/object | object (****) | Throw exceptions on errors. can be `boolean` or òbject` for advanced settings
#### [***] Empty check function
The default function to check if a value is empty is:
```javascript
(value) => value === '' || value === null || value === undefined
```
#### [****] Exceptions object
| Option | Type | Default
| --------- | ---- | -------
| readonly | bool | false
| required | bool | true
| rule | bool | true
| strict | bool | false
| type | bool | true
### Methods
#### $put
```javascript
const article = new Article()
article.put({
title: 'My article',
})
```
#### $patch
Same as $put, but only passed property are updated.
#### $eject
```javascript
const article = new Article()
const json = article.eject()
```
### Custom methods
Methods can be added to models.
```javascript
const Article = SmartModel.create('Article', {
body: {
type: String
}
}, {}, {
excerpt(limit = 10) {
return this.body.substr(0, limit) + '…'
}
})
```
## Callbacks
Models have some callbacks methods that are called when properties are set, get, updated or deleted.
```javascript
const User = SmartModel.create('User', {
username: {
type: String
}
}, {}, {
$onBeforeGet() {}
$onBeforeSet() {}
$onBeforeUpdate() {}
$onDelete() {}
$onGet() {}
$onBeforeDelete() {}
$onSet() {}
$onUpdate() {}
})
```
## Dev

@@ -121,0 +319,0 @@

@@ -1,9 +0,16 @@

import { isEmpty, toArray, isType } from './utils.js'
import { keys, isSmartModel, toArray, checkType, isPlainObject } from './utils.js'
export default function checkErrors(entry, property, value) {
export default function checkErrors(entry, property, value, first, settings) {
const errors = []
if (entry.required && isEmpty(value)) {
if (settings.strict && (!entry || !keys(entry).length)) {
errors.push({
message: `Invalid value 'required' on property '${property}'`,
message: `Property "${property}" can't be set in strict mode`,
code: 'strict'
})
}
if (entry.required && settings.empty(value)) {
errors.push({
message: `Property "${property}" is "required"`,
code: 'required'

@@ -15,2 +22,11 @@ })

if (entry.readonly && !first) {
errors.push({
message: `Property '${property}' is 'readonly'`,
code: 'readonly'
})
return errors
}
if (typeof value === 'undefined') {

@@ -20,8 +36,10 @@ return errors

if (entry.type && (entry.required || !isEmpty(value))) {
if (!toArray(entry.type).some((type) => isType(value, type))) {
errors.push({
message: `Invalid type '${typeof value}' on property '${property}'`,
code: 'type'
})
if (entry.type && (entry.required || !settings.empty(value))) {
if (!(isSmartModel(entry.type) && isPlainObject(value))) {
if (!toArray(entry.type).some((type) => checkType(value, type))) {
errors.push({
message: `Property "${property}" has an invalid type "${typeof value}"`,
code: 'type'
})
}
}

@@ -31,3 +49,3 @@ }

if (entry.rule) {
Object.keys(entry.rule).forEach((key) => {
keys(entry.rule, (key) => {
const rule = entry.rule[key]

@@ -37,4 +55,4 @@

errors.push({
message: `Invalid value '${key}' on property '${property}'`,
code: key
message: `Property "${property}" breaks the "${key}" rule`,
code: 'rule:' + key
})

@@ -41,0 +59,0 @@ }

@@ -1,2 +0,2 @@

import { pascalCase, isPlainObject } from './utils.js'
import { pascalCase, isSmartModel, isPlainObject } from './utils.js'
import SmartModel from './SmartModel.js'

@@ -9,7 +9,11 @@

const Child = entry.type.prototype instanceof SmartModel ? entry.type : false
const Child = isSmartModel(entry.type) ? entry.type : false
const schema = isPlainObject(entry.type) ? entry.type : false
if (Child || schema) {
return Child ? Child : SmartModel.create(pascalCase(property), schema, settings)
const Model = Child ? Child : SmartModel.create(pascalCase(property), schema, settings)
entry.type = Model
return Model
}

@@ -16,0 +20,0 @@

@@ -5,3 +5,3 @@

import checkErrors from './checkErrors.js'
import { toArray, isArray, isUndef } from './utils.js'
import { toArray, isSmartModel, keys, merge, isArray, isUndef } from './utils.js'

@@ -12,3 +12,3 @@ class SmartModel extends SmartModelProxy {

Object.keys(schema).forEach((key) => {
keys(schema, (key) => {
if (isUndef(data[key])) {

@@ -23,7 +23,7 @@ if (!isUndef(schema[key].default)) {

this.feed(data)
this.$patch(data)
}
feed(data) {
Object.keys(data).forEach((key) => {
$patch(data) {
keys(data, (key) => {
this[key] = data[key]

@@ -33,19 +33,52 @@ })

onBeforeGet() {}
onBeforeSet() {}
onBeforeUpdate() {}
onDelete() {}
onGet() {}
onBeforeDelete() {}
onSet() {}
onUpdate() {}
$put(data) {
keys(this, (key) => {
if (data[key]) {
if (isSmartModel(this[key])) {
this[key].$put(data[key])
} else {
this[key] = data[key]
}
} else {
this.$delete(key)
}
})
keys(data, (key) => {
if (!this[key]) {
this[key] = data[key]
}
})
}
$delete(properties) {
toArray(properties).forEach((key) => {
Reflect.deleteProperty(this, key)
})
}
$onBeforeGet() {}
$onBeforeSet() {}
$onBeforeUpdate() {}
$onDelete() {}
$onGet() {}
$onBeforeDelete() {}
$onSet() {}
$onUpdate() {}
}
SmartModel.settings = {
empty: (value) => value === '' || value === null || isUndef(value),
strict: false,
exceptions: true
exceptions: {
readonly: false,
required: true,
rule: true,
strict: false,
type: true
}
}
SmartModel.create = function (name, schema, settings, prototype) {
settings = Object.assign({}, SmartModel.settings, settings)
settings = merge(SmartModel.settings, settings)

@@ -61,3 +94,3 @@ const Model = { [name]: class extends SmartModel {

Object.keys(schema).forEach((property) => {
keys(schema, (property) => {
let subErrors

@@ -72,3 +105,3 @@ const value = payload[property]

let errors = checkErrors(entry, property, value)
let errors = checkErrors(entry, property, value, false, settings)

@@ -88,3 +121,3 @@ if (subErrors) {

return Object.keys(invalidations).length ? invalidations : false
return keys(invalidations).length ? invalidations : false
}

@@ -91,0 +124,0 @@

@@ -0,1 +1,3 @@

import { isPlainObject } from './utils.js'
class SmartModelError extends Error {

@@ -8,2 +10,15 @@ constructor(data) {

SmartModelError.throw = function (settings, code, message, property, source) {
const shortCode = code.split(':')[0]
if (settings.exceptions === true || (isPlainObject(settings.exceptions) && settings.exceptions[shortCode])) {
throw new SmartModelError({
message,
property,
code,
source: source && source.constructor.name
})
}
}
export default SmartModelError
import SmartModelError from './SmartModelError.js'
import checkErrors from './checkErrors.js'
import createNested from './createNested.js'
import { isFn, isEqual } from './utils.js'
import { keys, eject, isFn, isEqual, isUndef } from './utils.js'

@@ -9,24 +9,19 @@ class SmartModelProxy {

return new Proxy(this, {
set(target, property, value) {
let entry = schema[property]
const entry = schema[property] || {}
const old = target[property]
const updated = !isEqual(value, old)
const first = isUndef(old)
const updated = !first && !isEqual(value, old)
const Nested = createNested(entry, property, settings)
function trigger(method, args) {
return Reflect.apply(method, target, args ? args : [ property, value, old, schema ])
}
const returned = Reflect.apply(method, target, args ? args : [ property, value, old, schema ])
if (!entry) {
if (settings.strict) {
return true
}
entry = {}
return !isUndef(returned) ? returned : value
}
trigger(target.onBeforeSet)
value = trigger(target.$onBeforeSet)
if (updated) {
trigger(target.onBeforeUpdate)
value = trigger(target.$onBeforeUpdate)
}

@@ -38,17 +33,18 @@

if (settings.exceptions) {
const errors = checkErrors(entry, property, value)
const errors = checkErrors(entry, property, value, first, settings)
if (errors.length) {
throw new SmartModelError({
message: errors[0].message,
property: property,
code: errors[0].code,
source: target.constructor.name
})
if (errors.length) {
if (settings.exceptions) {
SmartModelError.throw(settings, errors[0].code, errors[0].message, property, target)
} else {
return true
}
}
if (settings.strict && !keys(entry).length) {
return true
}
if (Nested) {
value = new Nested(value instanceof Object ? value : {})
value = new Nested(value)
}

@@ -58,6 +54,6 @@

trigger(target.onSet)
trigger(target.$onSet)
if (updated) {
trigger(target.onUpdate)
trigger(target.$onUpdate)
}

@@ -72,2 +68,8 @@

if (property === '$eject') {
return function () {
return eject(target)
}
}
if (!entry) {

@@ -78,6 +80,8 @@ return target[property]

function trigger(method, args) {
return Reflect.apply(method, target, args ? args : [ property, value, schema ])
const returned = Reflect.apply(method, target, args ? args : [ property, value, schema ])
return !isUndef(returned) ? returned : value
}
trigger(target.onBeforeGet)
value = trigger(target.$onBeforeGet)

@@ -92,3 +96,3 @@ if (isFn(entry)) {

trigger(target.onGet)
value = trigger(target.$onGet)

@@ -100,3 +104,3 @@ return value

const value = target[property]
const entry = schema[property]
const entry = schema[property] || {}

@@ -108,15 +112,11 @@ function trigger(method, args) {

if (entry.required) {
throw new SmartModelError({
message: `Invalid delete on required propery ${property}`,
property: property,
code: 'required'
})
SmartModelError.throw(settings, 'required', `Property "${property}" is "required"`, property, target)
}
trigger(target.onBeforeDelete)
trigger(target.$onBeforeDelete)
Reflect.deleteProperty(target, property)
trigger(target.onDelete)
trigger(target.onUpdate)
trigger(target.$onDelete)
trigger(target.$onUpdate)

@@ -123,0 +123,0 @@ return true

/* eslint-disable new-cap */
import SmartModel from './SmartModel.js'
export function isArray(value) {

@@ -11,6 +13,2 @@ return Array.isArray(value)

export function isEmpty(value) {
return value === '' || value === null || isUndef(value)
}
export function isFn(value) {

@@ -25,10 +23,55 @@ return typeof value === 'function'

export function isClass(value) {
return value.toString().startsWith('class')
return value && value.toString().startsWith('class')
}
export function isSmartModel(value) {
return value.prototype instanceof SmartModel || value instanceof SmartModel
}
export function isPlainObject(value) {
return value.toString() === '[object Object]'
return value && value.toString() === '[object Object]'
}
export function isType(value, Type) {
export function keys(obj, cb = function () { }) {
return Object.keys(obj).map(cb)
}
export function toArray(value) {
return [].concat([], value)
}
export function merge(source, target) {
target = Object.assign({}, source, target)
keys(source, (key) => {
if (isPlainObject(source[key]) && isPlainObject(target[key])) {
target[key] = Object.assign({}, source[key], merge(source[key], target[key]))
}
})
return target
}
export function eject(target) {
target = Object.assign({}, target)
keys(target, (key) => {
if (isSmartModel(target[key])) {
target[key] = target[key].$eject()
}
})
return target
}
export function pascalCase(string) {
return string
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.match(/[a-z1-9]+/gi)
.map((word) => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase())
.join('')
}
export function checkType(value, Type) {
const match = Type && Type.toString().match(/^\s*function (\w+)/)

@@ -58,14 +101,1 @@ const type = (match ? match[1] : 'object').toLowerCase()

}
export function toArray(value) {
return [].concat([], value)
}
export function pascalCase(string) {
return string
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.match(/[a-z]+/gi)
.map((word) => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase())
.join('')
}

@@ -34,3 +34,3 @@ /* eslint-disable prefer-reflect */

const Model = SmartModel.create('Model', {
prop1: { default: 'Default prop1' },
prop1: { default: 'Default value1' },
prop2: { }

@@ -40,3 +40,3 @@ })

expect(model.prop1).to.be.equal('Default prop1')
expect(model.prop1).to.be.equal('Default value1')
expect(model.prop2).to.be.equal(undef)

@@ -46,216 +46,224 @@ })

// Required
// Validations
describe('Required', function () {
it('should throw an error if a required prop is not set on init', function () {
const Model = SmartModel.create('Model', {
prop: { required: true }
})
describe('Validation', function () {
const err = checkExceptions(() => {
new Model()
})
// Required
expect(err.source).to.be.equal('Model')
expect(err.property).to.be.equal('prop')
expect(err.code).to.be.equal('required')
})
describe('Required', function () {
it('should throw an error if a required prop is not set on init', function () {
const Model = SmartModel.create('Model', {
prop: { required: true }
})
it('should not throw an error if a required prop is set on init', function () {
const Model = SmartModel.create('Model', {
prop: { required: true }
})
const err = checkExceptions(() => {
new Model()
})
const err = checkExceptions(() => {
new Model({ prop: 'string' })
expect(err.source).to.be.equal('Model')
expect(err.property).to.be.equal('prop')
expect(err.code).to.be.equal('required')
})
expect(err.property).to.be.equal(undef)
expect(err.code).to.be.equal(undef)
})
it('should not throw an error if a required prop is set on init', function () {
const Model = SmartModel.create('Model', {
prop: { required: true }
})
it('should not throw an error if a required prop is not set but have default value on init', function () {
const Model = SmartModel.create('Model', {
prop: { required: true, default: 'string' }
})
const err = checkExceptions(() => {
new Model({ prop: 'string' })
})
const err = checkExceptions(() => {
new Model()
expect(err.property).to.be.equal(undef)
expect(err.code).to.be.equal(undef)
})
expect(err.property).to.be.equal(undef)
expect(err.code).to.be.equal(undef)
})
it('should not throw an error if a required prop is not set but have default value on init', function () {
const Model = SmartModel.create('Model', {
prop: { required: true, default: 'string' }
})
it('should throw an error if a required prop is set to empty', function () {
const Model = SmartModel.create('Model', {
prop: { required: true, default: 'string' }
})
const model = new Model()
const err = checkExceptions(() => {
new Model()
})
const err = checkExceptions(() => {
model.prop = null
expect(err.property).to.be.equal(undef)
expect(err.code).to.be.equal(undef)
})
expect(err.property).to.be.equal('prop')
expect(err.code).to.be.equal('required')
})
it('should throw an error if a required prop is set to empty', function () {
const Model = SmartModel.create('Model', {
prop: { required: true, default: 'string' }
})
const model = new Model()
it('should throw an error if a required prop is deleted', function () {
const Model = SmartModel.create('Model', {
prop: { required: true, default: 'string' }
})
const model = new Model()
const err = checkExceptions(() => {
model.prop = null
})
const err = checkExceptions(() => {
delete model.prop
expect(err.property).to.be.equal('prop')
expect(err.code).to.be.equal('required')
})
expect(err.property).to.be.equal('prop')
expect(err.code).to.be.equal('required')
})
})
it('should throw an error if a required prop is deleted', function () {
const Model = SmartModel.create('Model', {
prop: { required: true, default: 'string' }
})
const model = new Model()
// Type
const err = checkExceptions(() => {
delete model.prop
})
describe('Type', function () {
it('should throw an error if a typed prop is not set properly', function () {
const Model = SmartModel.create('Model', {
prop: { required: true, type: String }
expect(err.property).to.be.equal('prop')
expect(err.code).to.be.equal('required')
})
})
const err = checkExceptions(() => {
new Model({ prop: 0 })
})
// Type
expect(err.property).to.be.equal('prop')
expect(err.code).to.be.equal('type')
})
describe('Type', function () {
it('should throw an error if a typed prop is not set properly', function () {
const Model = SmartModel.create('Model', {
prop: { required: true, type: String }
})
it('should not throw an error if a typed prop is set properly', function () {
const Model = SmartModel.create('Model', {
prop: { required: true, type: String }
})
const err = checkExceptions(() => {
new Model({ prop: 0 })
})
const err = checkExceptions(() => {
new Model({ prop: 'string' })
expect(err.property).to.be.equal('prop')
expect(err.code).to.be.equal('type')
})
expect(err.property).to.be.equal(undef)
expect(err.code).to.be.equal(undef)
})
it('should not throw an error if a typed prop is set properly', function () {
const Model = SmartModel.create('Model', {
prop: { required: true, type: String }
})
it('should not throw an error if multiple typed props is set properly', function () {
const Model = SmartModel.create('Model', {
prop: { type: [ String, Array ] }
})
const model = new Model()
const err = checkExceptions(() => {
new Model({ prop: 'string' })
})
const err = checkExceptions(() => {
model.prop = 'string'
model.prop = [ 'string' ]
expect(err.property).to.be.equal(undef)
expect(err.code).to.be.equal(undef)
})
expect(err.property).to.be.equal(undef)
expect(err.code).to.be.equal(undef)
})
it('should not throw an error if multiple typed props is set properly', function () {
const Model = SmartModel.create('Model', {
prop: { type: [ String, Array ] }
})
const model = new Model()
it('should not throw an error if a typed prop is set on a not required property', function () {
const Model = SmartModel.create('Model', {
prop: { type: String }
const err = checkExceptions(() => {
model.prop = 'string'
model.prop = [ 'string' ]
})
expect(err.property).to.be.equal(undef)
expect(err.code).to.be.equal(undef)
})
const err = checkExceptions(() => {
new Model()
it('should not throw an error if a typed prop is set on a not required property', function () {
const Model = SmartModel.create('Model', {
prop: { type: String }
})
const err = checkExceptions(() => {
new Model()
})
expect(err.property).to.be.equal(undef)
expect(err.code).to.be.equal(undef)
})
expect(err.property).to.be.equal(undef)
expect(err.code).to.be.equal(undef)
})
it('should be alright with all those type checks', function () {
const TestModel = SmartModel.create('Test', {})
const testModel = new TestModel()
it('should be alright with all those type checks', function () {
const TestModel = SmartModel.create('Test', {})
const testModel = new TestModel()
const types = [
{ type: Date, fail: 100, success: new Date() },
{ type: String, fail: 0, success: 'string' },
{ type: Object, fail: 'string', success: {} },
{ type: Number, fail: 'string', success: 100 },
{ type: Boolean, fail: 'string', success: true },
{ type: Function, fail: 'string', success: function () { } },
{ type: TestModel, fail: 'string', success: testModel },
{ type: SmartModel, fail: {}, success: testModel },
{ type: Array, fail: {}, success: [] }
]
const types = [
{ type: Date, fail: 100, success: new Date() },
{ type: Array, fail: {}, success: [] },
{ type: String, fail: 0, success: 'string' },
{ type: Object, fail: 'string', success: {} },
{ type: Number, fail: 'string', success: 100 },
{ type: Boolean, fail: 'string', success: true },
{ type: Function, fail: 'string', success: function () { } },
{ type: SmartModel, fail: {}, success: testModel },
{ type: TestModel, fail: 'string', success: testModel }
]
types.forEach((type) => {
expect(() => {
const Model = SmartModel.create('Model', {
prop: { type: type.type, default: type.fail }
})
types.forEach((type) => {
expect(() => {
const Model = SmartModel.create('Model', {
prop: { type: type.type, default: type.fail }
})
new Model()
}).to.throw('type')
new Model()
}).to.throw('type')
expect(() => {
const Model = SmartModel.create('Model', {
prop: { type: type.type, default: type.success }
})
expect(() => {
const Model = SmartModel.create('Model', {
prop: { type: type.type, default: type.success }
})
new Model()
}).to.not.throw('type')
new Model()
}).to.not.throw('type')
})
})
})
})
// // Rule
// // Rule
describe('Rule', function () {
it('should throw an error if a ruled prop is not set properly', function () {
const Model = SmartModel.create('Model', {
prop: { required: true, rule: {
min: (value) => value < 5
} }
})
describe('Rule', function () {
it('should throw an error if a ruled prop is not set properly', function () {
const Model = SmartModel.create('Model', {
prop: {
required: true, rule: {
min: (value) => value < 5
}
}
})
const err = checkExceptions(() => {
new Model({ prop: 0 })
const err = checkExceptions(() => {
new Model({ prop: 0 })
})
expect(err.property).to.be.equal('prop')
expect(err.code).to.be.equal('rule:min')
})
expect(err.property).to.be.equal('prop')
expect(err.code).to.be.equal('min')
})
it('should not throw an error if a ruled prop is set properly', function () {
const Model = SmartModel.create('Model', {
prop: {
required: true, rule: {
min: (value) => value < 5
}
}
})
it('should not throw an error if a ruled prop is set properly', function () {
const Model = SmartModel.create('Model', {
prop: { required: true, rule: {
min: (value) => value < 5
} }
})
const err = checkExceptions(() => {
new Model({ prop: 10 })
})
const err = checkExceptions(() => {
new Model({ prop: 10 })
expect(err.property).to.be.equal(undef)
expect(err.code).to.be.equal(undef)
})
expect(err.property).to.be.equal(undef)
expect(err.code).to.be.equal(undef)
})
it('should not throw an error if a ruled prop is set on an empty not required property', function () {
const Model = SmartModel.create('Model', {
prop: {
rule: {
min: (value) => value < 5
}
}
})
it('should not throw an error if a ruled prop is set on an empty not required property', function () {
let err = {}
const Model = SmartModel.create('Model', {
prop: { rule: {
min: (value) => value < 5
} }
const err = checkExceptions(() => {
new Model()
})
expect(err.property).to.be.equal(undef)
expect(err.code).to.be.equal(undef)
})
try {
new Model()
} catch (e) {
err = e
}
expect(err.property).to.be.equal(undef)
expect(err.code).to.be.equal(undef)
})

@@ -266,3 +274,3 @@ })

describe('Format / Transform', function () {
describe('Format and transform', function () {
it('should transform a value', function () {

@@ -302,3 +310,3 @@ const Model = SmartModel.create('Model', {

// Virtual property
// Virtual / Readonly property

@@ -327,2 +335,19 @@ describe('Virtual property', function () {

})
it('should throw an exception if property is readonly', function () {
SmartModel.settings.exceptions.readonly = true
const Model = SmartModel.create('Model', {
prop: { default: 'string', readonly: true }
})
const model = new Model()
const err = checkExceptions(() => {
model.prop = 'another string'
})
expect(err.code).to.be.equal('readonly')
expect(err.property).to.be.equal('prop')
})
})

@@ -333,10 +358,72 @@

describe('Events', function () {
it('should trigger onBeforeDelete', testTrigger('onBeforeDelete'))
it('should trigger onBeforeGet', testTrigger('onBeforeGet'))
it('should trigger onBeforeSet', testTrigger('onBeforeSet'))
it('should trigger onBeforeUpdate', testTrigger('onBeforeUpdate'))
it('should trigger onDelete', testTrigger('onDelete'))
it('should trigger onGet', testTrigger('onGet'))
it('should trigger onSet', testTrigger('onSet'))
it('should trigger onUpdate', testTrigger('onUpdate'))
it('should trigger $onBeforeDelete', testTrigger('$onBeforeDelete'))
it('should trigger $onBeforeGet', testTrigger('$onBeforeGet'))
it('should trigger $onBeforeSet', testTrigger('$onBeforeSet'))
it('should trigger $onBeforeUpdate', testTrigger('$onBeforeUpdate'))
it('should trigger $onDelete', testTrigger('$onDelete'))
it('should trigger $onGet', testTrigger('$onGet'))
it('should trigger $onSet', testTrigger('$onSet'))
it('should trigger $onUpdate', testTrigger('$onUpdate'))
it('should intercept set', () => {
let isIntercepted
const Model = SmartModel.create('Model', {
prop: { default: 'default string' }
}, {}, {
$onBeforeSet() {
return 'INTERCEPTED'
},
$onSet(_, val) {
isIntercepted = val === 'INTERCEPTED'
}
})
const model = new Model()
expect(model.prop).to.be.equal('INTERCEPTED')
expect(isIntercepted).to.be.equal(true)
})
it('should intercept update', () => {
let isIntercepted
const Model = SmartModel.create('Model', {
prop: { default: 'default string' }
}, {}, {
$onBeforeUpdate() {
return 'INTERCEPTED'
},
$onUpdate(_, val) {
isIntercepted = val === 'INTERCEPTED'
}
})
const model = new Model()
model.prop = 'updated'
expect(model.prop).to.be.equal('INTERCEPTED')
expect(isIntercepted).to.be.equal(true)
})
it('should intercept get', () => {
let isIntercepted
const Model = SmartModel.create('Model', {
prop: { default: 'default string' }
}, {}, {
$onBeforeGet() {
return 'INTERCEPTED'
},
$onGet(_, val) {
isIntercepted = val === 'INTERCEPTED'
}
})
const model = new Model()
expect(model.prop).to.be.equal('INTERCEPTED')
expect(isIntercepted).to.be.equal(true)
})
})

@@ -346,2 +433,124 @@

describe('Methods', function () {
describe('$put', function () {
it('should replace model data', function () {
const Model = SmartModel.create('Model', {
prop1: { default: 'string1' },
prop2: { default: 'string2' },
prop3: {
type: {
nestedProp1: { default: 'string3' },
nestedProp2: { default: 'string4' }
}
}
})
const model = new Model()
model.$put({
prop1: 'newString1',
prop3: {
nestedProp1: 'newString2',
new: 'string'
},
new: 'string'
})
expect(model.prop1).to.be.equal('newString1')
expect(model.prop2).to.be.equal(undef)
expect(model.new).to.be.equal('string')
expect(model.prop3.nestedProp1).to.be.equal('newString2')
expect(model.prop3.nestedProp2).to.be.equal(undef)
expect(model.prop3.new).to.be.equal('string')
})
})
describe('$patch', function () {
it('should replace model data', function () {
const Model = SmartModel.create('Model', {
prop1: { default: 'string1' },
prop2: { default: 'string2' },
prop3: {
type: {
nestedProp1: { default: 'string3' },
nestedProp2: { default: 'string4' }
}
}
})
const model = new Model()
model.$patch({
prop1: 'newString1',
prop3: {
nestedProp1: 'newString2',
new: 'string'
},
new: 'string'
})
expect(model.prop1).to.be.equal('newString1')
expect(model.prop2).to.be.equal('string2')
expect(model.new).to.be.equal('string')
expect(model.prop3.nestedProp1).to.be.equal('newString2')
expect(model.prop3.nestedProp2).to.be.equal('string4')
expect(model.prop3.new).to.be.equal('string')
})
})
describe('$delete', function () {
it('should delete one property', function () {
const Model = SmartModel.create('Model', {
prop1: { default: 'string1' },
prop2: { default: 'string2' }
})
const model = new Model()
model.$delete('prop2')
expect(model.prop1).to.be.equal('string1')
expect(model.prop2).to.be.equal(undef)
})
it('should delete an array of properties', function () {
const Model = SmartModel.create('Model', {
prop1: { default: 'string1' },
prop2: { default: 'string2' },
prop3: { default: 'string3' }
})
const model = new Model()
model.$delete([ 'prop2', 'prop3' ])
expect(model).have.own.property('prop1')
expect(model).not.have.own.property('prop2')
expect(model).not.have.own.property('prop3')
})
})
describe('$eject', function () {
it('should works', function () {
const Model = SmartModel.create('Model', {
prop1: { default: 'string1' },
prop2: {
type: {
nestedProp: { default: 'string2' }
}
}
})
const model = new Model()
const obj = model.$eject()
expect(obj).to.be.deep.equal(model)
expect(model).to.be.instanceOf(SmartModel)
expect(model.prop2).to.be.instanceOf(SmartModel)
expect(obj).to.not.be.instanceOf(SmartModel)
expect(obj.prop2).to.not.be.instanceOf(SmartModel)
})
})
})
describe('Statics methods', function () {

@@ -373,4 +582,4 @@ describe('CheckErrors', function () {

expect(errrors.prop2[0].code).to.be.equal('type')
expect(errrors.prop4[0].code).to.be.equal('min')
expect(errrors.prop4[1].code).to.be.equal('pos')
expect(errrors.prop4[0].code).to.be.equal('rule:min')
expect(errrors.prop4[1].code).to.be.equal('rule:pos')
})

@@ -413,2 +622,14 @@

})
it('should return an array of model errors with readonly error', function () {
const Model = SmartModel.create('Model', {
prop: { type: String, readonly: true }
})
const errrors = Model.checkErrors({
prop: 'string'
})
expect(errrors.prop[0].code).to.be.equal('readonly')
})
})

@@ -459,2 +680,3 @@

SmartModel.settings.strict = true
SmartModel.settings.exceptions.strict = false

@@ -535,3 +757,3 @@ const Model1 = SmartModel.create('Model1', {

const Model = SmartModel.create('Model', {
prop1: { required: true }
prop: { required: true }
}, {

@@ -548,3 +770,3 @@ exceptions: false

const Model = SmartModel.create('Model', {
prop1: { required: true }
prop: { required: true }
}, {

@@ -558,2 +780,58 @@ exceptions: true

})
it('should not throw errors if exceptions.required:false', function () {
const Model = SmartModel.create('Model', {
prop: { required: true }
}, {
exceptions: {
required: false
}
})
expect(() => {
new Model()
}).to.not.throw(Error)
})
it('should not throw errors if exceptions.type:false', function () {
const Model = SmartModel.create('Model', {
prop: { type: String }
}, {
exceptions: {
type: false
}
})
expect(() => {
new Model({ prop: 0 })
}).to.not.throw(Error)
})
it('should not throw errors if exceptions.strict:false', function () {
const Model = SmartModel.create('Model', {
prop1: { type: String }
}, {
exceptions: {
strict: false
}
})
expect(() => {
new Model({ prop2: 'ok' })
}).to.not.throw(Error)
})
it('should not throw errors if exceptions.rule:false', function () {
const Model = SmartModel.create('Model', {
prop: { rule: { min: (value) => value < 2 } }
}, {
exceptions: {
rule: false
}
})
expect(() => {
new Model({ prop: 1 })
}).to.not.throw(Error)
})
})

@@ -629,2 +907,20 @@ })

it('should nest a child model and init it properly', function () {
const Model = SmartModel.create('Model', {
prop: {
type: {
nestedProp: {
default: 'string'
}
}
}
})
const model = new Model({
prop: {}
})
expect(model.prop.nestedProp).to.be.equal('string')
})
it('should throw an error if a required parent prop is not set on init', function () {

@@ -744,3 +1040,3 @@ const Model = SmartModel.create('Model', {

if ([ 'onBeforeDelete', 'onDelete' ].includes(name)) {
if ([ '$onBeforeDelete', '$onDelete' ].includes(name)) {
delete model.prop

@@ -750,3 +1046,3 @@

} else if ([ 'onBeforeGet', 'onGet' ].includes(name)) {
} else if ([ '$onBeforeGet', '$onGet' ].includes(name)) {
model.prop = model.prop

@@ -753,0 +1049,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc