1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | 'use strict';
|
7 |
|
8 | const assert = require('assert');
|
9 | const util = require('util');
|
10 | const EventEmitter = require('events').EventEmitter;
|
11 | const traverse = require('traverse');
|
12 | const ModelBaseClass = require('./model');
|
13 | const ModelBuilder = require('./model-builder');
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | module.exports = ModelDefinition;
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | function ModelDefinition(modelBuilder, name, properties, settings) {
|
31 | if (!(this instanceof ModelDefinition)) {
|
32 |
|
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 || {};
|
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 || {};
|
47 | this.settings = settings || {};
|
48 | }
|
49 | this.relations = [];
|
50 | this.properties = null;
|
51 | this.build();
|
52 | }
|
53 |
|
54 | util.inherits(ModelDefinition, EventEmitter);
|
55 |
|
56 |
|
57 | require('./types')(ModelDefinition);
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 | ModelDefinition.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 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 | ModelDefinition.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 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 | ModelDefinition.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 |
|
112 |
|
113 |
|
114 |
|
115 | ModelDefinition.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 |
|
131 |
|
132 |
|
133 | ModelDefinition.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 |
|
159 |
|
160 |
|
161 |
|
162 | ModelDefinition.prototype.idColumnName = function(connectorType) {
|
163 | return this.columnName(connectorType, this.idName());
|
164 | };
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 | ModelDefinition.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 |
|
181 |
|
182 |
|
183 | ModelDefinition.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 |
|
194 |
|
195 | ModelDefinition.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 |
|
213 |
|
214 |
|
215 | ModelDefinition.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 |
|
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 |
|
256 |
|
257 |
|
258 |
|
259 | ModelDefinition.prototype.defineProperty = function(propertyName, propertyDefinition) {
|
260 | this.rawProperties[propertyName] = propertyDefinition;
|
261 | this.build(true);
|
262 | };
|
263 |
|
264 | function isModelClass(cls) {
|
265 | if (!cls) {
|
266 | return false;
|
267 | }
|
268 | return cls.prototype instanceof ModelBaseClass;
|
269 | }
|
270 |
|
271 | ModelDefinition.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 |
|
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 |
|
313 | ModelDefinition.prototype.hasPK = function() {
|
314 | return this.ids().length > 0;
|
315 | };
|