Comparing version 1.0.0-alpha.12 to 1.0.0-alpha.13
import { IPopulateMap } from './populate'; | ||
import { ISchemaSanitizeOptions, ISchemaToObjectOptions, SchemaMap, SchemaValue } from './schema'; | ||
import { ISanitizeOptions } from './sanitize'; | ||
import { SchemaMap, SchemaValue } from './schema'; | ||
import { IToObjectOptions } from './to-object'; | ||
export interface IModelConstructor<TModel extends Model<object>> { | ||
@@ -13,12 +15,10 @@ _schema: SchemaMap; | ||
*/ | ||
static PropertySchema(schema: SchemaValue): PropertyDecorator; | ||
protected _sanitized: {}; | ||
static Property(schema: SchemaValue): PropertyDecorator; | ||
constructor(data?: Partial<T>); | ||
inspect(): Partial<T>; | ||
populate(populateMap: IPopulateMap): Promise<object>; | ||
set(data: object, options?: ISchemaSanitizeOptions): void; | ||
setKey(key: string, value: any, options?: ISchemaSanitizeOptions): void; | ||
toObject(options?: ISchemaToObjectOptions): Partial<T>; | ||
set(data: object, options?: ISanitizeOptions): void; | ||
toObject(options?: IToObjectOptions): Partial<T>; | ||
toJSON(): Partial<T>; | ||
validate(): Promise<void>; | ||
} |
@@ -13,27 +13,19 @@ "use strict"; | ||
const populate_1 = require("./populate"); | ||
const schema_1 = require("./schema"); | ||
const sanitize_1 = require("./sanitize"); | ||
const to_object_1 = require("./to-object"); | ||
const validate_1 = require("./validate"); | ||
function defineModelProperty(target, key, enumerable) { | ||
Object.defineProperty(target, key, { | ||
configurable: true, | ||
enumerable, | ||
// tslint:disable-next-line:object-literal-shorthand | ||
get: function () { | ||
return this._sanitized[key]; | ||
}, | ||
// tslint:disable-next-line:object-literal-shorthand | ||
set: function (value) { | ||
const instance = this; | ||
instance.setKey(key, value); | ||
}, | ||
}); | ||
} | ||
class Model { | ||
constructor(data) { | ||
// TODO: make this work | ||
// @enumerable(false) | ||
this._sanitized = {}; | ||
// TODO: remove (see @enumerable decorator) | ||
Object.defineProperty(this, '_sanitized', { writable: true, enumerable: false }); | ||
const constructor = this.constructor; | ||
const schema = constructor._schema; | ||
// set initial data | ||
this.set(data || {}, { defaults: true, replace: true }); | ||
// create proxy that intercepts set operations | ||
// for running sanitization if the key is in the schema | ||
return new Proxy(this, { | ||
set(target, key, value, receiver) { | ||
target[key] = schema[key] ? sanitize_1.sanitize(schema[key], value) : value; | ||
return true; | ||
}, | ||
}); | ||
} | ||
@@ -44,9 +36,7 @@ /** | ||
*/ | ||
static PropertySchema(schema) { | ||
static Property(schema) { | ||
return (target, key) => { | ||
const constructor = target.constructor; | ||
constructor._schema = lodash_1.cloneDeep(constructor._schema); | ||
constructor._schema = lodash_1.cloneDeep(constructor._schema || {}); | ||
constructor._schema[key] = schema; | ||
// define model property | ||
defineModelProperty(target, key, false); | ||
}; | ||
@@ -63,34 +53,10 @@ } | ||
} | ||
// TODO: find a way to merge this with setObjectSanitized, code is pretty redundant | ||
// TODO: sanitize is called twice when this is called via the proxy | ||
set(data, options = {}) { | ||
const constructor = this.constructor; | ||
if (typeof data !== 'object') { | ||
throw new Error('data is not an object'); | ||
} | ||
const dataKeys = Object.keys(data); | ||
const schemaKeys = Object.keys(constructor._schema); | ||
const disallowedKeys = lodash_1.difference(dataKeys, schemaKeys); | ||
if (disallowedKeys.length > 0) { | ||
throw new Error(`key ${disallowedKeys[0]} not found in schema`); | ||
} | ||
lodash_1.forEach(constructor._schema, (_, key) => { | ||
if (data[key] === undefined && !options.replace) { | ||
return; | ||
} | ||
this.setKey(key, data[key], options); | ||
}); | ||
sanitize_1.setObjectSanitized(constructor._schema, this, data, options); | ||
} | ||
setKey(key, value, options = {}) { | ||
const constructor = this.constructor; | ||
const schemaValue = constructor._schema[key]; | ||
if (!schemaValue) { | ||
throw new Error(`key ${key} not found in schema`); | ||
} | ||
const sanitizedValue = schema_1.sanitize(schemaValue, value, options); | ||
this._sanitized[key] = sanitizedValue; | ||
defineModelProperty(this, key, sanitizedValue !== undefined); | ||
} | ||
toObject(options) { | ||
const constructor = this.constructor; | ||
return schema_1.toObject(constructor._schema, this, options); | ||
return to_object_1.toObject(constructor._schema, this, options); | ||
} | ||
@@ -107,4 +73,3 @@ toJSON() { | ||
} | ||
Model._schema = {}; | ||
exports.Model = Model; | ||
//# sourceMappingURL=model.js.map |
@@ -67,14 +67,1 @@ import { IModelConstructor, Model } from './model'; | ||
export declare type SchemaMap = ISchemaMap; | ||
export interface ISchemaSanitizeOptions { | ||
/** Set undefined values to defaults (if configured). Defaults to false. */ | ||
defaults?: boolean; | ||
/** Unset all properties that are not provided in the data. Defaults to false. */ | ||
replace?: boolean; | ||
} | ||
export declare function sanitize(schemaValue: SchemaValue, data: any, _options?: ISchemaSanitizeOptions): any; | ||
export declare function setObjectSanitized(schemaMap: ISchemaMap, target: object, data: object, options?: ISchemaSanitizeOptions): object; | ||
export interface ISchemaToObjectOptions { | ||
unpopulate?: boolean; | ||
} | ||
export declare function toObjectValue(schemaValue: SchemaValue, value: any, options?: ISchemaToObjectOptions): any; | ||
export declare function toObject(schemaMap: SchemaMap, source: object, options?: ISchemaToObjectOptions): {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const lodash_1 = require("lodash"); | ||
const model_array_1 = require("./model-array"); | ||
function sanitize(schemaValue, data, _options) { | ||
const options = _options || {}; | ||
switch (schemaValue.type) { | ||
case 'array': | ||
// return empty array if no data given but a value is required | ||
if (data === undefined) { | ||
return schemaValue.required ? [] : undefined; | ||
} | ||
// data incompatible? | ||
if (!lodash_1.isArray(data)) { | ||
throw new Error('data is not an array'); | ||
} | ||
if (schemaValue.definition.type === 'model') { | ||
// is the provided data already a ModelArray? | ||
if (data instanceof model_array_1.ModelArray) { | ||
// does the ModelArray's model match the definition? | ||
if (data.model !== schemaValue.definition.model) { | ||
throw new Error('ModelArray model mismatch'); | ||
} | ||
return data; | ||
} | ||
// create new ModelArray instance | ||
return new model_array_1.ModelArray(schemaValue.definition.model, data); | ||
} | ||
else { | ||
// return sanitized elements | ||
return data.map(v => sanitize(schemaValue.definition, v, options)); | ||
} | ||
case 'model': | ||
if (data instanceof schemaValue.model) { | ||
// already a model | ||
return data; | ||
} | ||
else { | ||
if (data === undefined && !schemaValue.required) { | ||
return undefined; | ||
} | ||
// create new instance | ||
return new schemaValue.model(data || {}); | ||
} | ||
case 'object': | ||
// return empty object if no data given but a value is required | ||
if (data === undefined) { | ||
return schemaValue.required ? {} : undefined; | ||
} | ||
// data incompatible? | ||
if (typeof data !== 'object') { | ||
throw new Error('data is not an object'); | ||
} | ||
// sanitize object | ||
return setObjectSanitized(schemaValue.definition, {}, data, options); | ||
case 'reference': | ||
if (data === undefined) { | ||
return undefined; | ||
} | ||
// valid data? | ||
if (!(data instanceof schemaValue.collection().model) && !lodash_1.isString(data)) { | ||
throw new Error('not an instance or an id'); | ||
} | ||
return data; | ||
case 'any': | ||
case 'boolean': | ||
case 'date': | ||
case 'number': | ||
case 'string': { | ||
let value = data; | ||
// get default value if no data given | ||
if (options.defaults && value === undefined) { | ||
value = (typeof schemaValue.default === 'function') | ||
? schemaValue.default() | ||
: schemaValue.default; | ||
} | ||
// return undefined if value is still undefined | ||
if (value === undefined) { | ||
return undefined; | ||
} | ||
if (schemaValue.type === 'boolean' && !lodash_1.isBoolean(value)) { | ||
throw new Error('not a boolean'); | ||
} | ||
if (schemaValue.type === 'date' && !lodash_1.isDate(value)) { | ||
throw new Error('not a date'); | ||
} | ||
if (schemaValue.type === 'number' && !lodash_1.isNumber(value)) { | ||
throw new Error('not a number'); | ||
} | ||
if (schemaValue.type === 'string' && !lodash_1.isString(value)) { | ||
throw new Error('not a string'); | ||
} | ||
return value; | ||
} | ||
default: | ||
throw new Error(`type ${schemaValue.type} is unknown`); | ||
} | ||
} | ||
exports.sanitize = sanitize; | ||
function setObjectSanitized(schemaMap, target, data, options = {}) { | ||
if (typeof data !== 'object') { | ||
throw new Error('data is not an object'); | ||
} | ||
const dataKeys = Object.keys(data); | ||
const schemaKeys = Object.keys(schemaMap); | ||
const disallowedKeys = lodash_1.difference(dataKeys, schemaKeys); | ||
if (disallowedKeys.length > 0) { | ||
throw new Error(`key ${disallowedKeys[0]} not found in schema`); | ||
} | ||
lodash_1.forEach(schemaMap, (schemaValue, key) => { | ||
if (options.replace) { | ||
delete target[key]; | ||
} | ||
if (dataKeys.indexOf(key) === -1) { | ||
return; | ||
} | ||
target[key] = sanitize(schemaValue, data[key], options); | ||
}); | ||
return target; | ||
} | ||
exports.setObjectSanitized = setObjectSanitized; | ||
function toObjectValue(schemaValue, value, options = {}) { | ||
if (value === undefined) { | ||
return undefined; | ||
} | ||
switch (schemaValue.type) { | ||
case 'array': | ||
return value.map(v => toObjectValue(schemaValue.definition, v, options)); | ||
case 'model': | ||
return toObject(schemaValue.model._schema, value, options); | ||
case 'object': | ||
return toObject(schemaValue.definition, value, options); | ||
case 'reference': | ||
const collection = schemaValue.collection(); | ||
// is value an instance (-> populated)? | ||
if (value instanceof collection.model) { | ||
// do we only want the id? | ||
if (options.unpopulate) { | ||
return value[collection.modelIdField]; | ||
} | ||
return value.toObject(); | ||
} | ||
// value is an id | ||
return value; | ||
case 'any': | ||
case 'boolean': | ||
case 'date': | ||
case 'number': | ||
case 'string': | ||
return value; | ||
default: | ||
throw new Error(`type ${schemaValue.type} is unknown`); | ||
} | ||
} | ||
exports.toObjectValue = toObjectValue; | ||
function toObject(schemaMap, source, options) { | ||
const result = {}; | ||
lodash_1.forEach(schemaMap, (schemaValue, key) => { | ||
const value = toObjectValue(schemaValue, source[key], options); | ||
if (value !== undefined) { | ||
result[key] = value; | ||
} | ||
}); | ||
return result; | ||
} | ||
exports.toObject = toObject; | ||
//# sourceMappingURL=schema.js.map |
@@ -13,8 +13,8 @@ "use strict"; | ||
__decorate([ | ||
main_1.Model.PropertySchema({ type: 'string', default: main_1.utils.generateId }) | ||
main_1.Model.Property({ type: 'string', default: main_1.utils.generateId }) | ||
], CatModel.prototype, "id", void 0); | ||
__decorate([ | ||
main_1.Model.PropertySchema({ type: 'string' }) | ||
main_1.Model.Property({ type: 'string' }) | ||
], CatModel.prototype, "name", void 0); | ||
exports.CatModel = CatModel; | ||
//# sourceMappingURL=cat.js.map |
@@ -14,11 +14,11 @@ "use strict"; | ||
__decorate([ | ||
main_1.Model.PropertySchema({ type: 'string', default: main_1.utils.generateId }) | ||
main_1.Model.Property({ type: 'string', default: main_1.utils.generateId }) | ||
], DiscussionModel.prototype, "id", void 0); | ||
__decorate([ | ||
main_1.Model.PropertySchema({ type: 'reference', collection: () => collections_1.collections.people }) | ||
main_1.Model.Property({ type: 'reference', collection: () => collections_1.collections.people }) | ||
], DiscussionModel.prototype, "author", void 0); | ||
__decorate([ | ||
main_1.Model.PropertySchema({ type: 'string' }) | ||
main_1.Model.Property({ type: 'string' }) | ||
], DiscussionModel.prototype, "title", void 0); | ||
exports.DiscussionModel = DiscussionModel; | ||
//# sourceMappingURL=discussion.js.map |
@@ -14,8 +14,8 @@ "use strict"; | ||
__decorate([ | ||
main_1.Model.PropertySchema({ type: 'string', default: main_1.utils.generateId }) | ||
main_1.Model.Property({ type: 'string', default: main_1.utils.generateId }) | ||
], GroupWithArrayModel.prototype, "id", void 0); | ||
__decorate([ | ||
main_1.Model.PropertySchema({ type: 'array', definition: { type: 'model', model: person_1.PersonModel } }) | ||
main_1.Model.Property({ type: 'array', definition: { type: 'model', model: person_1.PersonModel } }) | ||
], GroupWithArrayModel.prototype, "members", void 0); | ||
exports.GroupWithArrayModel = GroupWithArrayModel; | ||
//# sourceMappingURL=group-with-array.js.map |
@@ -14,8 +14,8 @@ "use strict"; | ||
__decorate([ | ||
main_1.Model.PropertySchema({ type: 'string', default: main_1.utils.generateId }) | ||
main_1.Model.Property({ type: 'string', default: main_1.utils.generateId }) | ||
], GroupWithReferencesModel.prototype, "id", void 0); | ||
__decorate([ | ||
main_1.Model.PropertySchema({ type: 'array', definition: { type: 'reference', collection: () => collections_1.collections.people } }) | ||
main_1.Model.Property({ type: 'array', definition: { type: 'reference', collection: () => collections_1.collections.people } }) | ||
], GroupWithReferencesModel.prototype, "members", void 0); | ||
exports.GroupWithReferencesModel = GroupWithReferencesModel; | ||
//# sourceMappingURL=group-with-references.js.map |
@@ -13,5 +13,5 @@ "use strict"; | ||
__decorate([ | ||
main_1.Model.PropertySchema({ type: 'string' }) | ||
main_1.Model.Property({ type: 'string' }) | ||
], PersonAccountModel.prototype, "username", void 0); | ||
exports.PersonAccountModel = PersonAccountModel; | ||
//# sourceMappingURL=person-account.js.map |
@@ -14,11 +14,11 @@ "use strict"; | ||
__decorate([ | ||
main_1.Model.PropertySchema({ type: 'string', default: main_1.utils.generateId }) | ||
main_1.Model.Property({ type: 'string', default: main_1.utils.generateId }) | ||
], PersonModel.prototype, "id", void 0); | ||
__decorate([ | ||
main_1.Model.PropertySchema({ type: 'string' }) | ||
main_1.Model.Property({ type: 'string' }) | ||
], PersonModel.prototype, "name", void 0); | ||
__decorate([ | ||
main_1.Model.PropertySchema({ type: 'model', model: person_account_1.PersonAccountModel }) | ||
main_1.Model.Property({ type: 'model', model: person_account_1.PersonAccountModel }) | ||
], PersonModel.prototype, "account", void 0); | ||
exports.PersonModel = PersonModel; | ||
//# sourceMappingURL=person.js.map |
@@ -36,2 +36,9 @@ import { collections } from '../test/data/collections'; | ||
}); | ||
it('should delete a property', () => { | ||
const cat = new CatModel(); | ||
cat.id = '42'; | ||
delete cat.id; | ||
expect(cat.id).to.equal(undefined); | ||
}); | ||
}); | ||
@@ -372,12 +379,12 @@ | ||
class TestModel extends Model<TestModel> { | ||
@Model.PropertySchema({type: 'string', required: true}) | ||
@Model.Property({type: 'string', required: true}) | ||
public required: string; | ||
@Model.PropertySchema({type: 'array', definition: {type: 'string', enum: ['foo', 'bar']}}) | ||
@Model.Property({type: 'array', definition: {type: 'string', enum: ['foo', 'bar']}}) | ||
public array: string[]; | ||
@Model.PropertySchema({type: 'model', model: TestModel}) | ||
@Model.Property({type: 'model', model: TestModel}) | ||
public model: TestModel; | ||
@Model.PropertySchema({type: 'object', definition: {foo: {type: 'string', enum: ['bar']}}}) | ||
@Model.Property({type: 'object', definition: {foo: {type: 'string', enum: ['bar']}}}) | ||
public object: {foo: string}; | ||
@@ -384,0 +391,0 @@ } |
@@ -1,6 +0,7 @@ | ||
import { cloneDeep, difference, forEach } from 'lodash'; | ||
import { cloneDeep } from 'lodash'; | ||
import { IPopulateMap, populateObject } from './populate'; | ||
import { ISchemaSanitizeOptions, ISchemaToObjectOptions, sanitize, | ||
SchemaMap, SchemaValue, toObject } from './schema'; | ||
import { ISanitizeOptions, sanitize, setObjectSanitized } from './sanitize'; | ||
import { SchemaMap, SchemaValue } from './schema'; | ||
import { IToObjectOptions, toObject } from './to-object'; | ||
import { validateObject } from './validate'; | ||
@@ -15,24 +16,6 @@ | ||
constructor: typeof Model; | ||
_sanitized: {[k: string]: object}; | ||
setKey(key: string, value: any, options?: ISchemaSanitizeOptions); | ||
} | ||
function defineModelProperty(target, key: string, enumerable: boolean) { | ||
Object.defineProperty(target, key, { | ||
configurable: true, | ||
enumerable, | ||
// tslint:disable-next-line:object-literal-shorthand | ||
get: function() { | ||
return this._sanitized[key]; | ||
}, | ||
// tslint:disable-next-line:object-literal-shorthand | ||
set: function(value) { | ||
const instance = this as IModel; | ||
instance.setKey(key, value); | ||
}, | ||
}); | ||
} | ||
export abstract class Model<T extends object> { | ||
public static _schema: SchemaMap = {}; | ||
public static _schema: SchemaMap; | ||
@@ -43,21 +26,25 @@ /** | ||
*/ | ||
public static PropertySchema(schema: SchemaValue): PropertyDecorator { | ||
public static Property(schema: SchemaValue): PropertyDecorator { | ||
return (target: IModel, key: string) => { | ||
const constructor = target.constructor; | ||
constructor._schema = cloneDeep(constructor._schema); | ||
constructor._schema = cloneDeep(constructor._schema || {}); | ||
constructor._schema[key] = schema; | ||
// define model property | ||
defineModelProperty(target, key, false); | ||
}; | ||
} | ||
// TODO: make this work | ||
// @enumerable(false) | ||
protected _sanitized = {}; | ||
constructor(data?: Partial<T>) { | ||
const constructor = this.constructor as typeof Model; | ||
const schema = constructor._schema; | ||
constructor(data?: Partial<T>) { | ||
// TODO: remove (see @enumerable decorator) | ||
Object.defineProperty(this, '_sanitized', {writable: true, enumerable: false}); | ||
// set initial data | ||
this.set(data || {}, {defaults: true, replace: true}); | ||
// create proxy that intercepts set operations | ||
// for running sanitization if the key is in the schema | ||
return new Proxy(this, { | ||
set(target, key, value, receiver) { | ||
target[key] = schema[key] ? sanitize(schema[key], value) : value; | ||
return true; | ||
}, | ||
}); | ||
} | ||
@@ -74,41 +61,10 @@ | ||
// TODO: find a way to merge this with setObjectSanitized, code is pretty redundant | ||
public set(data: object, options: ISchemaSanitizeOptions = {}) { | ||
// TODO: sanitize is called twice when this is called via the proxy | ||
public set(data: object, options: ISanitizeOptions = {}) { | ||
const constructor = this.constructor as typeof Model; | ||
if (typeof data !== 'object') { | ||
throw new Error('data is not an object'); | ||
} | ||
const dataKeys = Object.keys(data); | ||
const schemaKeys = Object.keys(constructor._schema); | ||
const disallowedKeys = difference(dataKeys, schemaKeys); | ||
if (disallowedKeys.length > 0) { | ||
throw new Error(`key ${disallowedKeys[0]} not found in schema`); | ||
} | ||
forEach(constructor._schema, (_, key) => { | ||
if (data[key] === undefined && !options.replace) { | ||
return; | ||
} | ||
this.setKey(key, data[key], options); | ||
}); | ||
setObjectSanitized(constructor._schema, this, data, options); | ||
} | ||
public setKey(key: string, value: any, options: ISchemaSanitizeOptions = {}) { | ||
public toObject(options?: IToObjectOptions): Partial<T> { | ||
const constructor = this.constructor as typeof Model; | ||
const schemaValue = constructor._schema[key]; | ||
if (!schemaValue) { | ||
throw new Error(`key ${key} not found in schema`); | ||
} | ||
const sanitizedValue = sanitize(schemaValue, value, options); | ||
this._sanitized[key] = sanitizedValue; | ||
defineModelProperty(this, key, sanitizedValue !== undefined); | ||
} | ||
public toObject(options?: ISchemaToObjectOptions): Partial<T> { | ||
const constructor = this.constructor as typeof Model; | ||
return toObject(constructor._schema, this, options) as Partial<T>; | ||
@@ -115,0 +71,0 @@ } |
@@ -1,5 +0,2 @@ | ||
import { difference, forEach, isArray, isBoolean, isDate, isNumber, isString } from 'lodash'; | ||
import { IModelConstructor, Model } from './model'; | ||
import { ModelArray } from './model-array'; | ||
@@ -85,204 +82,1 @@ export interface ISchemaValueBase { | ||
export type SchemaMap = ISchemaMap; | ||
export interface ISchemaSanitizeOptions { | ||
/** Set undefined values to defaults (if configured). Defaults to false. */ | ||
defaults?: boolean; | ||
/** Unset all properties that are not provided in the data. Defaults to false. */ | ||
replace?: boolean; | ||
} | ||
export function sanitize(schemaValue: SchemaValue, data: any, _options?: ISchemaSanitizeOptions) { | ||
const options = _options || {}; | ||
switch (schemaValue.type) { | ||
case 'array': | ||
// return empty array if no data given but a value is required | ||
if (data === undefined) { | ||
return schemaValue.required ? [] : undefined; | ||
} | ||
// data incompatible? | ||
if (!isArray(data)) { | ||
throw new Error('data is not an array'); | ||
} | ||
if (schemaValue.definition.type === 'model') { | ||
// is the provided data already a ModelArray? | ||
if (data instanceof ModelArray) { | ||
// does the ModelArray's model match the definition? | ||
if (data.model !== schemaValue.definition.model) { | ||
throw new Error('ModelArray model mismatch'); | ||
} | ||
return data; | ||
} | ||
// create new ModelArray instance | ||
return new ModelArray(schemaValue.definition.model, data); | ||
} else { | ||
// return sanitized elements | ||
return data.map(v => sanitize(schemaValue.definition, v, options)); | ||
} | ||
case 'model': | ||
if (data instanceof schemaValue.model) { | ||
// already a model | ||
return data; | ||
} else { | ||
if (data === undefined && !schemaValue.required) { | ||
return undefined; | ||
} | ||
// create new instance | ||
return new schemaValue.model(data || {}); | ||
} | ||
case 'object': | ||
// return empty object if no data given but a value is required | ||
if (data === undefined) { | ||
return schemaValue.required ? {} : undefined; | ||
} | ||
// data incompatible? | ||
if (typeof data !== 'object') { | ||
throw new Error('data is not an object'); | ||
} | ||
// sanitize object | ||
return setObjectSanitized(schemaValue.definition, {}, data, options); | ||
case 'reference': | ||
if (data === undefined) { | ||
return undefined; | ||
} | ||
// valid data? | ||
if (!(data instanceof schemaValue.collection().model) && !isString(data)) { | ||
throw new Error('not an instance or an id'); | ||
} | ||
return data; | ||
case 'any': | ||
case 'boolean': | ||
case 'date': | ||
case 'number': | ||
case 'string': { | ||
let value = data; | ||
// get default value if no data given | ||
if (options.defaults && value === undefined) { | ||
value = (typeof schemaValue.default === 'function') | ||
? schemaValue.default() | ||
: schemaValue.default; | ||
} | ||
// return undefined if value is still undefined | ||
if (value === undefined) { | ||
return undefined; | ||
} | ||
if (schemaValue.type === 'boolean' && !isBoolean(value)) { | ||
throw new Error('not a boolean'); | ||
} | ||
if (schemaValue.type === 'date' && !isDate(value)) { | ||
throw new Error('not a date'); | ||
} | ||
if (schemaValue.type === 'number' && !isNumber(value)) { | ||
throw new Error('not a number'); | ||
} | ||
if (schemaValue.type === 'string' && !isString(value)) { | ||
throw new Error('not a string'); | ||
} | ||
return value; | ||
} | ||
default: | ||
throw new Error(`type ${(schemaValue as ISchemaValueBase).type} is unknown`); | ||
} | ||
} | ||
export function setObjectSanitized(schemaMap: ISchemaMap, target: object, data: object, | ||
options: ISchemaSanitizeOptions = {}) { | ||
if (typeof data !== 'object') { | ||
throw new Error('data is not an object'); | ||
} | ||
const dataKeys = Object.keys(data); | ||
const schemaKeys = Object.keys(schemaMap); | ||
const disallowedKeys = difference(dataKeys, schemaKeys); | ||
if (disallowedKeys.length > 0) { | ||
throw new Error(`key ${disallowedKeys[0]} not found in schema`); | ||
} | ||
forEach(schemaMap, (schemaValue, key) => { | ||
if (options.replace) { | ||
delete target[key]; | ||
} | ||
if (dataKeys.indexOf(key) === -1) { | ||
return; | ||
} | ||
target[key] = sanitize(schemaValue, data[key], options); | ||
}); | ||
return target; | ||
} | ||
export interface ISchemaToObjectOptions { | ||
unpopulate?: boolean; | ||
} | ||
export function toObjectValue(schemaValue: SchemaValue, value, options: ISchemaToObjectOptions = {}) { | ||
if (value === undefined) { | ||
return undefined; | ||
} | ||
switch (schemaValue.type) { | ||
case 'array': | ||
return value.map(v => toObjectValue(schemaValue.definition, v, options)); | ||
case 'model': | ||
return toObject(schemaValue.model._schema, value, options); | ||
case 'object': | ||
return toObject(schemaValue.definition, value, options); | ||
case 'reference': | ||
const collection = schemaValue.collection(); | ||
// is value an instance (-> populated)? | ||
if (value instanceof collection.model) { | ||
// do we only want the id? | ||
if (options.unpopulate) { | ||
return value[collection.modelIdField]; | ||
} | ||
return value.toObject(); | ||
} | ||
// value is an id | ||
return value; | ||
case 'any': | ||
case 'boolean': | ||
case 'date': | ||
case 'number': | ||
case 'string': | ||
return value; | ||
default: | ||
throw new Error(`type ${(schemaValue as ISchemaValueBase).type} is unknown`); | ||
} | ||
} | ||
export function toObject(schemaMap: SchemaMap, source: object, options?: ISchemaToObjectOptions) { | ||
const result = {}; | ||
forEach(schemaMap, (schemaValue, key) => { | ||
const value = toObjectValue(schemaValue, source[key], options); | ||
if (value !== undefined) { | ||
result[key] = value; | ||
} | ||
}); | ||
return result; | ||
} |
@@ -11,3 +11,3 @@ import { collections } from '../test/data/collections'; | ||
class TestModel extends Model<TestModel> { | ||
@Model.PropertySchema(schema) | ||
@Model.Property(schema) | ||
public key: any; | ||
@@ -229,3 +229,3 @@ } | ||
class NestedModel extends Model<NestedModel> { | ||
@Model.PropertySchema({ | ||
@Model.Property({ | ||
type: 'any', | ||
@@ -232,0 +232,0 @@ validate: async (value: any, path: Array<string | number>, instance: Model<any>) => { |
{ | ||
"name": "octonom", | ||
"version": "1.0.0-alpha.12", | ||
"version": "1.0.0-alpha.13", | ||
"description": "Object Document Mapper for any database", | ||
@@ -5,0 +5,0 @@ "main": "build/lib/main.js", |
import { Model, utils } from '../../../lib/main'; | ||
export class CatModel extends Model<CatModel> { | ||
@Model.PropertySchema({type: 'string', default: utils.generateId}) | ||
@Model.Property({type: 'string', default: utils.generateId}) | ||
public id: string; | ||
@Model.PropertySchema({type: 'string'}) | ||
@Model.Property({type: 'string'}) | ||
public name: string; | ||
@@ -9,0 +9,0 @@ |
@@ -7,10 +7,10 @@ import { Model, utils } from '../../../lib/main'; | ||
export class DiscussionModel extends Model<DiscussionModel> { | ||
@Model.PropertySchema({type: 'string', default: utils.generateId}) | ||
@Model.Property({type: 'string', default: utils.generateId}) | ||
public id: string; | ||
@Model.PropertySchema({type: 'reference', collection: () => collections.people}) | ||
@Model.Property({type: 'reference', collection: () => collections.people}) | ||
public author: string | Partial<PersonModel>; | ||
@Model.PropertySchema({type: 'string'}) | ||
@Model.Property({type: 'string'}) | ||
public title: string; | ||
} |
@@ -6,7 +6,7 @@ import { Model, ModelArray, utils } from '../../../lib/main'; | ||
export class GroupWithArrayModel extends Model<GroupWithArrayModel> { | ||
@Model.PropertySchema({type: 'string', default: utils.generateId}) | ||
@Model.Property({type: 'string', default: utils.generateId}) | ||
public id: string; | ||
@Model.PropertySchema({type: 'array', definition: {type: 'model', model: PersonModel}}) | ||
@Model.Property({type: 'array', definition: {type: 'model', model: PersonModel}}) | ||
public members: ModelArray<PersonModel> | Array<Partial<PersonModel>>; | ||
} |
@@ -7,7 +7,7 @@ import { Model, utils } from '../../../lib/main'; | ||
export class GroupWithReferencesModel extends Model<GroupWithReferencesModel> { | ||
@Model.PropertySchema({type: 'string', default: utils.generateId}) | ||
@Model.Property({type: 'string', default: utils.generateId}) | ||
public id: string; | ||
@Model.PropertySchema({type: 'array', definition: {type: 'reference', collection: () => collections.people}}) | ||
@Model.Property({type: 'array', definition: {type: 'reference', collection: () => collections.people}}) | ||
public members: Array<string | PersonModel>; | ||
} |
import { Model } from '../../../lib/main'; | ||
export class PersonAccountModel extends Model<PersonAccountModel> { | ||
@Model.PropertySchema({type: 'string'}) | ||
@Model.Property({type: 'string'}) | ||
public username: string; | ||
} |
@@ -6,10 +6,10 @@ import { Model, utils } from '../../../lib/main'; | ||
export class PersonModel extends Model<PersonModel> { | ||
@Model.PropertySchema({type: 'string', default: utils.generateId}) | ||
@Model.Property({type: 'string', default: utils.generateId}) | ||
public id: string; | ||
@Model.PropertySchema({type: 'string'}) | ||
@Model.Property({type: 'string'}) | ||
public name: string; | ||
@Model.PropertySchema({type: 'model', model: PersonAccountModel}) | ||
@Model.Property({type: 'model', model: PersonAccountModel}) | ||
public account?: Partial<PersonAccountModel>; | ||
} |
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
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
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
86
173117
3383