@loopback/repository
Version:
Define and implement a common set of interfaces for interacting with databases
381 lines • 11.9 kB
JavaScript
"use strict";
// Copyright IBM Corp. and LoopBack contributors 2017,2020. All Rights Reserved.
// Node module: @loopback/repository
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
Object.defineProperty(exports, "__esModule", { value: true });
exports.rejectNavigationalPropertiesInData = exports.Event = exports.Entity = exports.ValueObject = exports.Model = exports.ModelDefinition = void 0;
const index_1 = require("./index");
/**
* Definition for a model
*/
class ModelDefinition {
constructor(nameOrDef) {
if (typeof nameOrDef === 'string') {
nameOrDef = { name: nameOrDef };
}
const { name, properties, settings, relations } = nameOrDef;
this.name = name;
this.properties = {};
if (properties) {
for (const p in properties) {
this.addProperty(p, properties[p]);
}
}
this.settings = settings !== null && settings !== void 0 ? settings : new Map();
this.relations = relations !== null && relations !== void 0 ? relations : {};
}
/**
* Add a property
* @param name - Property definition or name (string)
* @param definitionOrType - Definition or property type
*/
addProperty(name, definitionOrType) {
const definition = definitionOrType.type
? definitionOrType
: { type: definitionOrType };
if (definition.id === true &&
definition.generated === true &&
definition.type !== undefined &&
definition.useDefaultIdType === undefined) {
definition.useDefaultIdType = false;
}
this.properties[name] = definition;
return this;
}
/**
* Add a setting
* @param name - Setting name
* @param value - Setting value
*/
addSetting(name, value) {
this.settings[name] = value;
return this;
}
/**
* Define a new relation.
* @param definition - The definition of the new relation.
*/
addRelation(definition) {
this.relations[definition.name] = definition;
return this;
}
/**
* Define a new belongsTo relation.
* @param name - The name of the belongsTo relation.
* @param definition - The definition of the belongsTo relation.
*/
belongsTo(name, definition) {
const meta = {
...definition,
name,
type: index_1.RelationType.belongsTo,
targetsMany: false,
};
return this.addRelation(meta);
}
/**
* Define a new hasOne relation.
* @param name - The name of the hasOne relation.
* @param definition - The definition of the hasOne relation.
*/
hasOne(name, definition) {
const meta = {
...definition,
name,
type: index_1.RelationType.hasOne,
targetsMany: false,
};
return this.addRelation(meta);
}
/**
* Define a new hasMany relation.
* @param name - The name of the hasMany relation.
* @param definition - The definition of the hasMany relation.
*/
hasMany(name, definition) {
const meta = {
...definition,
name,
type: index_1.RelationType.hasMany,
targetsMany: true,
};
return this.addRelation(meta);
}
/**
* Define a new referencesMany relation.
* @param name - The name of the referencesMany relation.
* @param definition - The definition of the referencesMany relation.
*/
referencesMany(name, definition) {
const meta = {
...definition,
name,
type: index_1.RelationType.referencesMany,
targetsMany: true,
};
return this.addRelation(meta);
}
/**
* Get an array of names of ID properties, which are specified in
* the model settings or properties with `id` attribute.
*
* @example
* ```ts
* {
* settings: {
* id: ['id']
* }
* properties: {
* id: {
* type: 'string',
* id: true
* }
* }
* }
* ```
*/
idProperties() {
if (typeof this.settings.id === 'string') {
return [this.settings.id];
}
else if (Array.isArray(this.settings.id)) {
return this.settings.id;
}
const idProps = Object.keys(this.properties).filter(prop => this.properties[prop].id);
return idProps;
}
}
exports.ModelDefinition = ModelDefinition;
function asJSON(value) {
if (value == null)
return value;
if (typeof value.toJSON === 'function') {
return value.toJSON();
}
// Handle arrays
if (Array.isArray(value)) {
return value.map(item => asJSON(item));
}
return value;
}
/**
* Convert a value to a plain object as DTO.
*
* - The prototype of the value in primitive types are preserved,
* like `Date`, `ObjectId`.
* - If the value is an instance of custom model, call `toObject` to convert.
* - If the value is an array, convert each element recursively.
*
* @param value the value to convert
* @param options the options
*/
function asObject(value, options) {
if (value == null)
return value;
if (typeof value.toObject === 'function') {
return value.toObject(options);
}
if (Array.isArray(value)) {
return value.map(item => asObject(item, options));
}
return value;
}
/**
* Base class for models
*/
class Model {
static get modelName() {
var _a;
return ((_a = this.definition) === null || _a === void 0 ? void 0 : _a.name) || this.name;
}
/**
* Serialize into a plain JSON object
*/
toJSON() {
const def = this.constructor.definition;
if (def == null || def.settings.strict === false) {
return this.toObject({ ignoreUnknownProperties: false });
}
const copyPropertyAsJson = (key) => {
const val = asJSON(this[key]);
if (val !== undefined) {
json[key] = val;
}
};
const json = {};
const hiddenProperties = def.settings.hiddenProperties || [];
for (const p in def.properties) {
if (p in this && !hiddenProperties.includes(p)) {
copyPropertyAsJson(p);
}
}
for (const r in def.relations) {
const relName = def.relations[r].name;
if (relName in this) {
copyPropertyAsJson(relName);
}
}
return json;
}
/**
* Convert to a plain object as DTO
*
* If `ignoreUnknownProperty` is set to false, convert all properties in the
* model instance, otherwise only convert the ones defined in the model
* definitions.
*
* See function `asObject` for each property's conversion rules.
*/
toObject(options) {
const def = this.constructor.definition;
const obj = {};
if ((options === null || options === void 0 ? void 0 : options.ignoreUnknownProperties) === false) {
const hiddenProperties = (def === null || def === void 0 ? void 0 : def.settings.hiddenProperties) || [];
for (const p in this) {
if (!hiddenProperties.includes(p)) {
const val = this[p];
obj[p] = asObject(val, options);
}
}
return obj;
}
if (def === null || def === void 0 ? void 0 : def.relations) {
for (const r in def.relations) {
const relName = def.relations[r].name;
if (relName in this) {
obj[relName] = asObject(this[relName], {
...options,
ignoreUnknownProperties: false,
});
}
}
}
const props = def.properties;
const keys = Object.keys(props);
for (const i in keys) {
const propertyName = keys[i];
const val = this[propertyName];
if (val === undefined)
continue;
obj[propertyName] = asObject(val, options);
}
return obj;
}
constructor(data) {
Object.assign(this, data);
}
}
exports.Model = Model;
/**
* Base class for value objects - An object that contains attributes but has no
* conceptual identity. They should be treated as immutable.
*/
class ValueObject extends Model {
}
exports.ValueObject = ValueObject;
/**
* Base class for entities which have unique ids
*/
class Entity extends Model {
/**
* Get the names of identity properties (primary keys).
*/
static getIdProperties() {
return this.definition.idProperties();
}
/**
* Get the identity value for a given entity instance or entity data object.
*
* @param entityOrData - The data object for which to determine the identity
* value.
*/
static getIdOf(entityOrData) {
if (typeof entityOrData.getId === 'function') {
return entityOrData.getId();
}
const idName = this.getIdProperties()[0];
return entityOrData[idName];
}
/**
* Get the identity value. If the identity is a composite key, returns
* an object.
*/
getId() {
const definition = this.constructor.definition;
const idProps = definition.idProperties();
if (idProps.length === 1) {
return this[idProps[0]];
}
if (!idProps.length) {
throw new Error(`Invalid Entity ${this.constructor.name}:` +
'missing primary key (id) property');
}
return this.getIdObject();
}
/**
* Get the identity as an object, such as `{id: 1}` or
* `{schoolId: 1, studentId: 2}`
*/
getIdObject() {
const definition = this.constructor.definition;
const idProps = definition.idProperties();
const idObj = {};
for (const idProp of idProps) {
idObj[idProp] = this[idProp];
}
return idObj;
}
/**
* Build the where object for the given id
* @param id - The id value
*/
static buildWhereForId(id) {
const where = {};
const idProps = this.definition.idProperties();
if (idProps.length === 1) {
where[idProps[0]] = id;
}
else {
for (const idProp of idProps) {
where[idProp] = id[idProp];
}
}
return where;
}
}
exports.Entity = Entity;
/**
* Domain events
*/
class Event {
}
exports.Event = Event;
/**
* Check model data for navigational properties linking to related models.
* Throw a descriptive error if any such property is found.
*
* @param modelClass Model constructor, e.g. `Product`.
* @param entityData Model instance or a plain-data object,
* e.g. `{name: 'pen'}`.
*/
function rejectNavigationalPropertiesInData(modelClass, data) {
const def = modelClass.definition;
const props = def.properties;
for (const r in def.relations) {
const relName = def.relations[r].name;
if (!(relName in data))
continue;
let msg = 'Navigational properties are not allowed in model data ' +
`(model "${modelClass.modelName}" property "${relName}"), ` +
'please remove it.';
if (relName in props) {
msg +=
' The error might be invoked by belongsTo relations, please make' +
' sure the relation name is not the same as the property name.';
}
throw new Error(msg);
}
}
exports.rejectNavigationalPropertiesInData = rejectNavigationalPropertiesInData;
//# sourceMappingURL=model.js.map