UNPKG

11.9 kBJavaScriptView Raw
1"use strict";
2// Copyright IBM Corp. and LoopBack contributors 2017,2020. All Rights Reserved.
3// Node module: @loopback/repository
4// This file is licensed under the MIT License.
5// License text available at https://opensource.org/licenses/MIT
6Object.defineProperty(exports, "__esModule", { value: true });
7exports.rejectNavigationalPropertiesInData = exports.Event = exports.Entity = exports.ValueObject = exports.Model = exports.ModelDefinition = void 0;
8const index_1 = require("./index");
9/**
10 * Definition for a model
11 */
12class ModelDefinition {
13 constructor(nameOrDef) {
14 if (typeof nameOrDef === 'string') {
15 nameOrDef = { name: nameOrDef };
16 }
17 const { name, properties, settings, relations } = nameOrDef;
18 this.name = name;
19 this.properties = {};
20 if (properties) {
21 for (const p in properties) {
22 this.addProperty(p, properties[p]);
23 }
24 }
25 this.settings = settings !== null && settings !== void 0 ? settings : new Map();
26 this.relations = relations !== null && relations !== void 0 ? relations : {};
27 }
28 /**
29 * Add a property
30 * @param name - Property definition or name (string)
31 * @param definitionOrType - Definition or property type
32 */
33 addProperty(name, definitionOrType) {
34 const definition = definitionOrType.type
35 ? definitionOrType
36 : { type: definitionOrType };
37 if (definition.id === true &&
38 definition.generated === true &&
39 definition.type !== undefined &&
40 definition.useDefaultIdType === undefined) {
41 definition.useDefaultIdType = false;
42 }
43 this.properties[name] = definition;
44 return this;
45 }
46 /**
47 * Add a setting
48 * @param name - Setting name
49 * @param value - Setting value
50 */
51 addSetting(name, value) {
52 this.settings[name] = value;
53 return this;
54 }
55 /**
56 * Define a new relation.
57 * @param definition - The definition of the new relation.
58 */
59 addRelation(definition) {
60 this.relations[definition.name] = definition;
61 return this;
62 }
63 /**
64 * Define a new belongsTo relation.
65 * @param name - The name of the belongsTo relation.
66 * @param definition - The definition of the belongsTo relation.
67 */
68 belongsTo(name, definition) {
69 const meta = {
70 ...definition,
71 name,
72 type: index_1.RelationType.belongsTo,
73 targetsMany: false,
74 };
75 return this.addRelation(meta);
76 }
77 /**
78 * Define a new hasOne relation.
79 * @param name - The name of the hasOne relation.
80 * @param definition - The definition of the hasOne relation.
81 */
82 hasOne(name, definition) {
83 const meta = {
84 ...definition,
85 name,
86 type: index_1.RelationType.hasOne,
87 targetsMany: false,
88 };
89 return this.addRelation(meta);
90 }
91 /**
92 * Define a new hasMany relation.
93 * @param name - The name of the hasMany relation.
94 * @param definition - The definition of the hasMany relation.
95 */
96 hasMany(name, definition) {
97 const meta = {
98 ...definition,
99 name,
100 type: index_1.RelationType.hasMany,
101 targetsMany: true,
102 };
103 return this.addRelation(meta);
104 }
105 /**
106 * Define a new referencesMany relation.
107 * @param name - The name of the referencesMany relation.
108 * @param definition - The definition of the referencesMany relation.
109 */
110 referencesMany(name, definition) {
111 const meta = {
112 ...definition,
113 name,
114 type: index_1.RelationType.referencesMany,
115 targetsMany: true,
116 };
117 return this.addRelation(meta);
118 }
119 /**
120 * Get an array of names of ID properties, which are specified in
121 * the model settings or properties with `id` attribute.
122 *
123 * @example
124 * ```ts
125 * {
126 * settings: {
127 * id: ['id']
128 * }
129 * properties: {
130 * id: {
131 * type: 'string',
132 * id: true
133 * }
134 * }
135 * }
136 * ```
137 */
138 idProperties() {
139 if (typeof this.settings.id === 'string') {
140 return [this.settings.id];
141 }
142 else if (Array.isArray(this.settings.id)) {
143 return this.settings.id;
144 }
145 const idProps = Object.keys(this.properties).filter(prop => this.properties[prop].id);
146 return idProps;
147 }
148}
149exports.ModelDefinition = ModelDefinition;
150function asJSON(value) {
151 if (value == null)
152 return value;
153 if (typeof value.toJSON === 'function') {
154 return value.toJSON();
155 }
156 // Handle arrays
157 if (Array.isArray(value)) {
158 return value.map(item => asJSON(item));
159 }
160 return value;
161}
162/**
163 * Convert a value to a plain object as DTO.
164 *
165 * - The prototype of the value in primitive types are preserved,
166 * like `Date`, `ObjectId`.
167 * - If the value is an instance of custom model, call `toObject` to convert.
168 * - If the value is an array, convert each element recursively.
169 *
170 * @param value the value to convert
171 * @param options the options
172 */
173function asObject(value, options) {
174 if (value == null)
175 return value;
176 if (typeof value.toObject === 'function') {
177 return value.toObject(options);
178 }
179 if (Array.isArray(value)) {
180 return value.map(item => asObject(item, options));
181 }
182 return value;
183}
184/**
185 * Base class for models
186 */
187class Model {
188 constructor(data) {
189 Object.assign(this, data);
190 }
191 static get modelName() {
192 var _a;
193 return ((_a = this.definition) === null || _a === void 0 ? void 0 : _a.name) || this.name;
194 }
195 /**
196 * Serialize into a plain JSON object
197 */
198 toJSON() {
199 const def = this.constructor.definition;
200 if (def == null || def.settings.strict === false) {
201 return this.toObject({ ignoreUnknownProperties: false });
202 }
203 const copyPropertyAsJson = (key) => {
204 const val = asJSON(this[key]);
205 if (val !== undefined) {
206 json[key] = val;
207 }
208 };
209 const json = {};
210 const hiddenProperties = def.settings.hiddenProperties || [];
211 for (const p in def.properties) {
212 if (p in this && !hiddenProperties.includes(p)) {
213 copyPropertyAsJson(p);
214 }
215 }
216 for (const r in def.relations) {
217 const relName = def.relations[r].name;
218 if (relName in this) {
219 copyPropertyAsJson(relName);
220 }
221 }
222 return json;
223 }
224 /**
225 * Convert to a plain object as DTO
226 *
227 * If `ignoreUnknownProperty` is set to false, convert all properties in the
228 * model instance, otherwise only convert the ones defined in the model
229 * definitions.
230 *
231 * See function `asObject` for each property's conversion rules.
232 */
233 toObject(options) {
234 const def = this.constructor.definition;
235 const obj = {};
236 if ((options === null || options === void 0 ? void 0 : options.ignoreUnknownProperties) === false) {
237 const hiddenProperties = (def === null || def === void 0 ? void 0 : def.settings.hiddenProperties) || [];
238 for (const p in this) {
239 if (!hiddenProperties.includes(p)) {
240 const val = this[p];
241 obj[p] = asObject(val, options);
242 }
243 }
244 return obj;
245 }
246 if (def === null || def === void 0 ? void 0 : def.relations) {
247 for (const r in def.relations) {
248 const relName = def.relations[r].name;
249 if (relName in this) {
250 obj[relName] = asObject(this[relName], {
251 ...options,
252 ignoreUnknownProperties: false,
253 });
254 }
255 }
256 }
257 const props = def.properties;
258 const keys = Object.keys(props);
259 for (const i in keys) {
260 const propertyName = keys[i];
261 const val = this[propertyName];
262 if (val === undefined)
263 continue;
264 obj[propertyName] = asObject(val, options);
265 }
266 return obj;
267 }
268}
269exports.Model = Model;
270/**
271 * Base class for value objects - An object that contains attributes but has no
272 * conceptual identity. They should be treated as immutable.
273 */
274class ValueObject extends Model {
275}
276exports.ValueObject = ValueObject;
277/**
278 * Base class for entities which have unique ids
279 */
280class Entity extends Model {
281 /**
282 * Get the names of identity properties (primary keys).
283 */
284 static getIdProperties() {
285 return this.definition.idProperties();
286 }
287 /**
288 * Get the identity value for a given entity instance or entity data object.
289 *
290 * @param entityOrData - The data object for which to determine the identity
291 * value.
292 */
293 static getIdOf(entityOrData) {
294 if (typeof entityOrData.getId === 'function') {
295 return entityOrData.getId();
296 }
297 const idName = this.getIdProperties()[0];
298 return entityOrData[idName];
299 }
300 /**
301 * Get the identity value. If the identity is a composite key, returns
302 * an object.
303 */
304 getId() {
305 const definition = this.constructor.definition;
306 const idProps = definition.idProperties();
307 if (idProps.length === 1) {
308 return this[idProps[0]];
309 }
310 if (!idProps.length) {
311 throw new Error(`Invalid Entity ${this.constructor.name}:` +
312 'missing primary key (id) property');
313 }
314 return this.getIdObject();
315 }
316 /**
317 * Get the identity as an object, such as `{id: 1}` or
318 * `{schoolId: 1, studentId: 2}`
319 */
320 getIdObject() {
321 const definition = this.constructor.definition;
322 const idProps = definition.idProperties();
323 const idObj = {};
324 for (const idProp of idProps) {
325 idObj[idProp] = this[idProp];
326 }
327 return idObj;
328 }
329 /**
330 * Build the where object for the given id
331 * @param id - The id value
332 */
333 static buildWhereForId(id) {
334 const where = {};
335 const idProps = this.definition.idProperties();
336 if (idProps.length === 1) {
337 where[idProps[0]] = id;
338 }
339 else {
340 for (const idProp of idProps) {
341 where[idProp] = id[idProp];
342 }
343 }
344 return where;
345 }
346}
347exports.Entity = Entity;
348/**
349 * Domain events
350 */
351class Event {
352}
353exports.Event = Event;
354/**
355 * Check model data for navigational properties linking to related models.
356 * Throw a descriptive error if any such property is found.
357 *
358 * @param modelClass Model constructor, e.g. `Product`.
359 * @param entityData Model instance or a plain-data object,
360 * e.g. `{name: 'pen'}`.
361 */
362function rejectNavigationalPropertiesInData(modelClass, data) {
363 const def = modelClass.definition;
364 const props = def.properties;
365 for (const r in def.relations) {
366 const relName = def.relations[r].name;
367 if (!(relName in data))
368 continue;
369 let msg = 'Navigational properties are not allowed in model data ' +
370 `(model "${modelClass.modelName}" property "${relName}"), ` +
371 'please remove it.';
372 if (relName in props) {
373 msg +=
374 ' The error might be invoked by belongsTo relations, please make' +
375 ' sure the relation name is not the same as the property name.';
376 }
377 throw new Error(msg);
378 }
379}
380exports.rejectNavigationalPropertiesInData = rejectNavigationalPropertiesInData;
381//# sourceMappingURL=model.js.map
\No newline at end of file