1 | 'use strict';
|
2 |
|
3 | function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
4 |
|
5 | var util = _interopDefault(require('util'));
|
6 | var chalk = _interopDefault(require('chalk'));
|
7 | var Inflector = _interopDefault(require('i'));
|
8 | var emberCliStringUtils = require('ember-cli-string-utils');
|
9 |
|
10 | function generateUUID() {
|
11 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
12 | const r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8);
|
13 |
|
14 | return v.toString(16);
|
15 | });
|
16 | }
|
17 |
|
18 | function primaryKeyTypeSafetyCheck(targetPrimaryKeyType, primaryKey, modelName) {
|
19 | const primaryKeyType = typeof primaryKey;
|
20 |
|
21 | if (targetPrimaryKeyType === 'id' && (primaryKeyType !== 'number')) {
|
22 | throw new Error(chalk.red(`[MemServer] ${modelName} model primaryKey type is 'id'. Instead you've tried to enter id: ${primaryKey} with ${primaryKeyType} type`));
|
23 | } else if (targetPrimaryKeyType === 'uuid' && (primaryKeyType !== 'string')) {
|
24 | throw new Error(chalk.red(`[MemServer] ${modelName} model primaryKey type is 'uuid'. Instead you've tried to enter uuid: ${primaryKey} with ${primaryKeyType} type`));
|
25 | }
|
26 |
|
27 | return targetPrimaryKeyType;
|
28 | }
|
29 |
|
30 | const { singularize, pluralize } = Inflector();
|
31 | const targetNamespace = typeof global === 'object' ? global : window;
|
32 |
|
33 | var model = function(options) {
|
34 | return Object.assign({}, {
|
35 | modelName: null,
|
36 | primaryKey: null,
|
37 | defaultAttributes: {},
|
38 | attributes: [],
|
39 | count() {
|
40 | const models = Array.from(targetNamespace.MemServer.DB[this.modelName] || []);
|
41 |
|
42 | return models.length;
|
43 | },
|
44 | find(param) {
|
45 | if (!param) {
|
46 | throw new Error(chalk.red(`[MemServer] ${this.modelName}.find(id) cannot be called without a valid id`));
|
47 | } else if (Array.isArray(param)) {
|
48 | const models = Array.from(targetNamespace.MemServer.DB[this.modelName] || []);
|
49 |
|
50 | return models.reduce((result, model) => {
|
51 | const foundModel = param.includes(model.id) ? model : null;
|
52 |
|
53 | return foundModel ? result.concat([foundModel]) : result;
|
54 | }, []);
|
55 | } else if (typeof param !== 'number') {
|
56 | throw new Error(chalk.red(`[MemServer] ${this.modelName}.find(id) cannot be called without a valid id`));
|
57 | }
|
58 |
|
59 | const models = Array.from(targetNamespace.MemServer.DB[this.modelName] || []);
|
60 |
|
61 | return models.find((model) => model.id === param);
|
62 | },
|
63 | findBy(options) {
|
64 | if (!options) {
|
65 | throw new Error(chalk.red(`[MemServer] ${this.modelName}.findBy(id) cannot be called without a parameter`));
|
66 | }
|
67 |
|
68 | const keys = Object.keys(options);
|
69 | const models = targetNamespace.MemServer.DB[this.modelName] || [];
|
70 |
|
71 | return models.find((model) => comparison(model, options, keys, 0));
|
72 | },
|
73 | findAll(options={}) {
|
74 | const models = Array.from(targetNamespace.MemServer.DB[this.modelName] || []);
|
75 | const keys = Object.keys(options);
|
76 |
|
77 | if (keys.length === 0) {
|
78 | return models;
|
79 | }
|
80 |
|
81 | return models.filter((model) => comparison(model, options, keys, 0));
|
82 | },
|
83 | insert(options) {
|
84 | const models = targetNamespace.MemServer.DB[this.modelName] || [];
|
85 |
|
86 | if (!this.primaryKey && models.length === 0) {
|
87 | const recordsPrimaryKey = target.id || target.uuid;
|
88 |
|
89 | this.primaryKey = recordsPrimaryKey || 'id';
|
90 | this.attributes.push(this.primaryKey);
|
91 | }
|
92 |
|
93 | const defaultAttributes = this.attributes.reduce((result, attribute) => {
|
94 | if (attribute === this.primaryKey) {
|
95 | result[attribute] = this.primaryKey === 'id' ? incrementId(this) : generateUUID();
|
96 |
|
97 | return result;
|
98 | }
|
99 |
|
100 | const target = this.defaultAttributes[attribute];
|
101 |
|
102 | result[attribute] = typeof target === 'function' ? target() : target;
|
103 |
|
104 | return result;
|
105 | }, {});
|
106 | const target = Object.assign(defaultAttributes, options);
|
107 |
|
108 | primaryKeyTypeSafetyCheck(this.primaryKey, target[this.primaryKey], this.modelName);
|
109 |
|
110 | const existingRecord = target.id ? this.find(target.id) : this.findBy({ uuid: target.uuid });
|
111 |
|
112 | if (existingRecord) {
|
113 | throw new Error(chalk.red(`[MemServer] ${this.modelName} ${this.primaryKey} ${target[this.primaryKey]} already exists in the database! ${this.modelName}.insert(${util.inspect(options)}) fails`));
|
114 | }
|
115 |
|
116 | Object.keys(target)
|
117 | .filter((attribute) => !this.attributes.includes(attribute))
|
118 | .forEach((attribute) => this.attributes.push(attribute));
|
119 |
|
120 | models.push(target);
|
121 |
|
122 | return target;
|
123 | },
|
124 | update(record) {
|
125 | if (!record || (!record.id && !record.uuid)) {
|
126 | throw new Error(chalk.red(`[MemServer] ${this.modelName}.update(record) requires id or uuid primary key to update a record`));
|
127 | }
|
128 |
|
129 | const targetRecord = record.id ? this.find(record.id) : this.findBy({ uuid: record.uuid });
|
130 |
|
131 | if (!targetRecord) {
|
132 | throw new Error(chalk.red(`[MemServer] ${this.modelName}.update(record) failed because ${this.modelName} with ${this.primaryKey}: ${record[this.primaryKey]} does not exist`));
|
133 | }
|
134 |
|
135 | const recordsUnknownAttribute = Object.keys(record)
|
136 | .find((attribute) => !this.attributes.includes(attribute));
|
137 |
|
138 | if (recordsUnknownAttribute) {
|
139 | throw new Error(chalk.red(`[MemServer] ${this.modelName}.update ${this.primaryKey}: ${record[this.primaryKey]} fails, ${this.modelName} model does not have ${recordsUnknownAttribute} attribute to update`));
|
140 | }
|
141 |
|
142 | return Object.assign(targetRecord, record);
|
143 | },
|
144 | delete(record) {
|
145 | const models = targetNamespace.MemServer.DB[this.modelName] || [];
|
146 |
|
147 | if (models.length === 0) {
|
148 | throw new Error(chalk.red(`[MemServer] ${this.modelName} has no records in the database to delete. ${this.modelName}.delete(${util.inspect(record)}) failed`));
|
149 | } else if (!record) {
|
150 | throw new Error(chalk.red(`[MemServer] ${this.modelName}.delete(model) model object parameter required to delete a model`));
|
151 | }
|
152 |
|
153 | const targetRecord = record.id ? this.find(record.id) : this.findBy({ uuid: record.uuid });
|
154 |
|
155 | if (!targetRecord) {
|
156 | throw new Error(chalk.red(`[MemServer] Could not find ${this.modelName} with ${this.primaryKey} ${record[this.primaryKey]} to delete. ${this.modelName}.delete(${util.inspect(record)}) failed`));
|
157 | }
|
158 |
|
159 | const targetIndex = models.indexOf(targetRecord);
|
160 |
|
161 | targetNamespace.MemServer.DB[this.modelName].splice(targetIndex, 1);
|
162 |
|
163 | return targetRecord;
|
164 | },
|
165 | embed(relationship) {
|
166 | if (typeof relationship !== 'object' || relationship.modelName) {
|
167 | throw new Error(chalk.red(`[MemServer] ${this.modelName}.embed(relationshipObject) requires an object as a parameter: { relationshipKey: $RelationshipModel }`));
|
168 | }
|
169 |
|
170 | const key = Object.keys(relationship)[0];
|
171 |
|
172 | if (!relationship[key]) {
|
173 | throw new Error(chalk.red(`[MemServer] ${this.modelName}.embed() fails: ${key} Model reference is not a valid. Please put a valid $ModelName to ${this.modelName}.embed()`));
|
174 | }
|
175 |
|
176 | return Object.assign(this.embedReferences, relationship);
|
177 | },
|
178 | embedReferences: {},
|
179 | serializer(objectOrArray) {
|
180 | if (!objectOrArray) {
|
181 | return;
|
182 | } else if (Array.isArray(objectOrArray)) {
|
183 | return objectOrArray.map((object) => this.serialize(object), []);
|
184 | }
|
185 |
|
186 | return this.serialize(objectOrArray);
|
187 | },
|
188 | serialize(object) {
|
189 | if (Array.isArray(object)) {
|
190 | throw new Error(chalk.red(`[MemServer] ${this.modelName}.serialize(object) expects an object not an array. Use ${this.modelName}.serializer(data) for serializing array of records`));
|
191 | }
|
192 |
|
193 | const objectWithAllAttributes = this.attributes.reduce((result, attribute) => {
|
194 | if (result[attribute] === undefined) {
|
195 | result[attribute] = null;
|
196 | }
|
197 |
|
198 | return result;
|
199 | }, object);
|
200 |
|
201 | return Object.keys(this.embedReferences).reduce((result, embedKey) => {
|
202 | const embedModel = this.embedReferences[embedKey];
|
203 | const embeddedRecords = this.getRelationship(object, embedKey, embedModel);
|
204 |
|
205 | return Object.assign(result, { [embedKey]: embedModel.serializer(embeddedRecords) });
|
206 | }, objectWithAllAttributes);
|
207 | },
|
208 | getRelationship(parentObject, relationshipName, relationshipModel) {
|
209 | if (Array.isArray(parentObject)) {
|
210 | throw new Error(chalk.red(`[MemServer] ${this.modelName}.getRelationship expects model input to be an object not an array`));
|
211 | }
|
212 |
|
213 | const targetRelationshipModel = relationshipModel ||
|
214 | targetNamespace.MemServer.Models[emberCliStringUtils.classify(singularize(relationshipName))];
|
215 | const hasManyRelationship = pluralize(relationshipName) === relationshipName;
|
216 |
|
217 | if (!targetRelationshipModel) {
|
218 | throw new Error(chalk.red(`[MemServer] ${relationshipName} relationship could not be found on ${this.modelName} model. Please put the ${relationshipName} Model object as the third parameter to ${this.modelName}.getRelationship function`));
|
219 | } else if (hasManyRelationship) {
|
220 | const hasManyRecords = targetRelationshipModel.findAll({
|
221 | [`${emberCliStringUtils.underscore(this.modelName)}_id`]: parentObject.id
|
222 | });
|
223 |
|
224 | return hasManyRecords.length > 0 ? hasManyRecords : [];
|
225 | }
|
226 |
|
227 | const objectsReference = parentObject[`${emberCliStringUtils.underscore(targetRelationshipModel.modelName)}_id`];
|
228 |
|
229 | if (objectsReference) {
|
230 | return targetRelationshipModel.find(objectsReference);
|
231 | }
|
232 |
|
233 | return targetRelationshipModel.findBy({
|
234 | [`${emberCliStringUtils.underscore(this.modelName)}_id`]: parentObject.id
|
235 | });
|
236 | }
|
237 | }, options);
|
238 | };
|
239 |
|
240 | function incrementId(Model) {
|
241 | const ids = targetNamespace.MemServer.DB[Model.modelName];
|
242 |
|
243 | if (ids.length === 0) {
|
244 | return 1;
|
245 | }
|
246 |
|
247 | const lastIdInSequence = ids
|
248 | .map((model) => model.id)
|
249 | .sort((a, b) => a - b)
|
250 | .find((id, index, array) => index === array.length - 1 ? true : id + 1 !== array[index + 1]);
|
251 |
|
252 | return lastIdInSequence + 1;
|
253 | }
|
254 |
|
255 |
|
256 | function comparison(model, options, keys, index=0) {
|
257 | const key = keys[index];
|
258 |
|
259 | if (keys.length === index) {
|
260 | return model[key] === options[key];
|
261 | } else if (model[key] === options[key]) {
|
262 | return comparison(model, options, keys, index + 1);
|
263 | }
|
264 |
|
265 | return false;
|
266 | }
|
267 |
|
268 | module.exports = model;
|