UNPKG

8.3 kBJavaScriptView Raw
1// Copyright IBM Corp. 2013,2019. All Rights Reserved.
2// Node module: loopback-datasource-juggler
3// This file is licensed under the MIT License.
4// License text available at https://opensource.org/licenses/MIT
5
6'use strict';
7
8const assert = require('assert');
9const util = require('util');
10const EventEmitter = require('events').EventEmitter;
11const traverse = require('traverse');
12const ModelBaseClass = require('./model');
13const ModelBuilder = require('./model-builder');
14
15/**
16 * Model definition
17 */
18module.exports = ModelDefinition;
19
20/**
21 * Constructor for ModelDefinition
22 * @param {ModelBuilder} modelBuilder A model builder instance
23 * @param {String|Object} name The model name or the schema object
24 * @param {Object} properties The model properties, optional
25 * @param {Object} settings The model settings, optional
26 * @returns {ModelDefinition}
27 * @constructor
28 *
29 */
30function ModelDefinition(modelBuilder, name, properties, settings) {
31 if (!(this instanceof ModelDefinition)) {
32 // Allow to call ModelDefinition without new
33 return new ModelDefinition(modelBuilder, name, properties, settings);
34 }
35 this.modelBuilder = modelBuilder || ModelBuilder.defaultInstance;
36 assert(name, 'name is missing');
37
38 if (arguments.length === 2 && typeof name === 'object') {
39 const schema = name;
40 this.name = schema.name;
41 this.rawProperties = schema.properties || {}; // Keep the raw property definitions
42 this.settings = schema.settings || {};
43 } else {
44 assert(typeof name === 'string', 'name must be a string');
45 this.name = name;
46 this.rawProperties = properties || {}; // Keep the raw property definitions
47 this.settings = settings || {};
48 }
49 this.relations = [];
50 this.properties = null;
51 this.build();
52}
53
54util.inherits(ModelDefinition, EventEmitter);
55
56// Set up types
57require('./types')(ModelDefinition);
58
59/**
60 * Return table name for specified `modelName`
61 * @param {String} connectorType The connector type, such as 'oracle' or 'mongodb'
62 */
63ModelDefinition.prototype.tableName = function(connectorType) {
64 const settings = this.settings;
65 if (settings[connectorType]) {
66 return settings[connectorType].table || settings[connectorType].tableName || this.name;
67 } else {
68 return this.name;
69 }
70};
71
72/**
73 * Return column name for specified modelName and propertyName
74 * @param {String} connectorType The connector type, such as 'oracle' or 'mongodb'
75 * @param propertyName The property name
76 * @returns {String} columnName
77 */
78ModelDefinition.prototype.columnName = function(connectorType, propertyName) {
79 if (!propertyName) {
80 return propertyName;
81 }
82 this.build();
83 const property = this.properties[propertyName];
84 if (property && property[connectorType]) {
85 return property[connectorType].column || property[connectorType].columnName || propertyName;
86 } else {
87 return propertyName;
88 }
89};
90
91/**
92 * Return column metadata for specified modelName and propertyName
93 * @param {String} connectorType The connector type, such as 'oracle' or 'mongodb'
94 * @param propertyName The property name
95 * @returns {Object} column metadata
96 */
97ModelDefinition.prototype.columnMetadata = function(connectorType, propertyName) {
98 if (!propertyName) {
99 return propertyName;
100 }
101 this.build();
102 const property = this.properties[propertyName];
103 if (property && property[connectorType]) {
104 return property[connectorType];
105 } else {
106 return null;
107 }
108};
109
110/**
111 * Return column names for specified modelName
112 * @param {String} connectorType The connector type, such as 'oracle' or 'mongodb'
113 * @returns {String[]} column names
114 */
115ModelDefinition.prototype.columnNames = function(connectorType) {
116 this.build();
117 const props = this.properties;
118 const cols = [];
119 for (const p in props) {
120 if (props[p][connectorType]) {
121 cols.push(props[p][connectorType].column || props[p][connectorType].columnName || p);
122 } else {
123 cols.push(p);
124 }
125 }
126 return cols;
127};
128
129/**
130 * Find the ID properties sorted by the index
131 * @returns {Object[]} property name/index for IDs
132 */
133ModelDefinition.prototype.ids = function() {
134 if (this._ids) {
135 return this._ids;
136 }
137 const ids = [];
138 this.build();
139 const props = this.properties;
140 for (const key in props) {
141 let id = props[key].id;
142 if (!id) {
143 continue;
144 }
145 if (typeof id !== 'number') {
146 id = 1;
147 }
148 ids.push({name: key, id: id, property: props[key]});
149 }
150 ids.sort(function(a, b) {
151 return a.id - b.id;
152 });
153 this._ids = ids;
154 return ids;
155};
156
157/**
158 * Find the ID column name
159 * @param {String} modelName The model name
160 * @returns {String} columnName for ID
161 */
162ModelDefinition.prototype.idColumnName = function(connectorType) {
163 return this.columnName(connectorType, this.idName());
164};
165
166/**
167 * Find the ID property name
168 * @returns {String} property name for ID
169 */
170ModelDefinition.prototype.idName = function() {
171 const id = this.ids()[0];
172 if (this.properties.id && this.properties.id.id) {
173 return 'id';
174 } else {
175 return id && id.name;
176 }
177};
178
179/**
180 * Find the ID property names sorted by the index
181 * @returns {String[]} property names for IDs
182 */
183ModelDefinition.prototype.idNames = function() {
184 const ids = this.ids();
185 const names = ids.map(function(id) {
186 return id.name;
187 });
188 return names;
189};
190
191/**
192 *
193 * @returns {{}}
194 */
195ModelDefinition.prototype.indexes = function() {
196 this.build();
197 const indexes = {};
198 if (this.settings.indexes) {
199 for (const i in this.settings.indexes) {
200 indexes[i] = this.settings.indexes[i];
201 }
202 }
203 for (const p in this.properties) {
204 if (this.properties[p].index) {
205 indexes[p + '_index'] = this.properties[p].index;
206 }
207 }
208 return indexes;
209};
210
211/**
212 * Build a model definition
213 * @param {Boolean} force Forcing rebuild
214 */
215ModelDefinition.prototype.build = function(forceRebuild) {
216 if (forceRebuild) {
217 this.properties = null;
218 this.relations = [];
219 this._ids = null;
220 this.json = null;
221 }
222 if (this.properties) {
223 return this.properties;
224 }
225 this.properties = {};
226 for (const p in this.rawProperties) {
227 const prop = this.rawProperties[p];
228 const type = this.modelBuilder.resolveType(prop);
229 if (typeof type === 'string') {
230 this.relations.push({
231 source: this.name,
232 target: type,
233 type: Array.isArray(prop) ? 'hasMany' : 'belongsTo',
234 as: p,
235 });
236 } else {
237 const typeDef = {
238 type: type,
239 };
240 if (typeof prop === 'object' && prop !== null) {
241 for (const a in prop) {
242 // Skip the type property but don't delete it Model.extend() shares same instances of the properties from the base class
243 if (a !== 'type') {
244 typeDef[a] = prop[a];
245 }
246 }
247 }
248 this.properties[p] = typeDef;
249 }
250 }
251 return this.properties;
252};
253
254/**
255 * Define a property
256 * @param {String} propertyName The property name
257 * @param {Object} propertyDefinition The property definition
258 */
259ModelDefinition.prototype.defineProperty = function(propertyName, propertyDefinition) {
260 this.rawProperties[propertyName] = propertyDefinition;
261 this.build(true);
262};
263
264function isModelClass(cls) {
265 if (!cls) {
266 return false;
267 }
268 return cls.prototype instanceof ModelBaseClass;
269}
270
271ModelDefinition.prototype.toJSON = function(forceRebuild) {
272 if (forceRebuild) {
273 this.json = null;
274 }
275 if (this.json) {
276 return this.json;
277 }
278 const json = {
279 name: this.name,
280 properties: {},
281 settings: this.settings,
282 };
283 this.build(forceRebuild);
284
285 const mapper = function(val) {
286 if (val === undefined || val === null) {
287 return val;
288 }
289 if ('function' === typeof val.toJSON) {
290 // The value has its own toJSON() object
291 return val.toJSON();
292 }
293 if ('function' === typeof val) {
294 if (isModelClass(val)) {
295 if (val.settings && val.settings.anonymous) {
296 return val.definition && val.definition.toJSON().properties;
297 } else {
298 return val.modelName;
299 }
300 }
301 return val.name;
302 } else {
303 return val;
304 }
305 };
306 for (const p in this.properties) {
307 json.properties[p] = traverse(this.properties[p]).map(mapper);
308 }
309 this.json = json;
310 return json;
311};
312
313ModelDefinition.prototype.hasPK = function() {
314 return this.ids().length > 0;
315};