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 = 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 = 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 = 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 = 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 | const defaultAttributes = this.attributes.reduce((result, attribute) => {
|
86 | if (attribute === this.primaryKey) {
|
87 | result[attribute] = this.primaryKey === 'id' ? incrementId(this) : generateUUID();
|
88 |
|
89 | return result;
|
90 | }
|
91 |
|
92 | const target = this.defaultAttributes[attribute];
|
93 |
|
94 | result[attribute] = typeof target === 'function' ? target() : target;
|
95 |
|
96 | return result;
|
97 | }, {});
|
98 | const target = Object.assign(defaultAttributes, options);
|
99 |
|
100 | primaryKeyTypeSafetyCheck(this.primaryKey, target[this.primaryKey], this.modelName);
|
101 |
|
102 | const existingRecord = target.id ? this.find(target.id) : this.findBy({ uuid: target.uuid });
|
103 |
|
104 | if (existingRecord) {
|
105 | 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`));
|
106 | }
|
107 |
|
108 | Object.keys(target)
|
109 | .filter((attribute) => !this.attributes.includes(attribute))
|
110 | .forEach((attribute) => this.attributes.push(attribute));
|
111 |
|
112 | models.push(target);
|
113 |
|
114 | return target;
|
115 | },
|
116 | update(record) {
|
117 | if (!record || (!record.id && !record.uuid)) {
|
118 | throw new Error(chalk.red(`[MemServer] ${this.modelName}.update(record) requires id or uuid primary key to update a record`));
|
119 | }
|
120 |
|
121 | const targetRecord = record.id ? this.find(record.id) : this.findBy({ uuid: record.uuid });
|
122 |
|
123 | if (!targetRecord) {
|
124 | throw new Error(chalk.red(`[MemServer] ${this.modelName}.update(record) failed because ${this.modelName} with ${this.primaryKey}: ${record[this.primaryKey]} does not exist`));
|
125 | }
|
126 |
|
127 | const recordsUnknownAttribute = Object.keys(record)
|
128 | .find((attribute) => !this.attributes.includes(attribute));
|
129 |
|
130 | if (recordsUnknownAttribute) {
|
131 | 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`));
|
132 | }
|
133 |
|
134 | return Object.assign(targetRecord, record);
|
135 | },
|
136 | delete(record) {
|
137 | const models = targetNamespace.MemServer.DB[this.modelName] || [];
|
138 |
|
139 | if (models.length === 0) {
|
140 | throw new Error(chalk.red(`[MemServer] ${this.modelName} has no records in the database to delete. ${this.modelName}.delete(${util.inspect(record)}) failed`));
|
141 | } else if (!record) {
|
142 | throw new Error(chalk.red(`[MemServer] ${this.modelName}.delete(model) model object parameter required to delete a model`));
|
143 | }
|
144 |
|
145 | const targetRecord = record.id ? this.find(record.id) : this.findBy({ uuid: record.uuid });
|
146 |
|
147 | if (!targetRecord) {
|
148 | 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`));
|
149 | }
|
150 |
|
151 | const targetIndex = models.indexOf(targetRecord);
|
152 |
|
153 | targetNamespace.MemServer.DB[this.modelName].splice(targetIndex, 1);
|
154 |
|
155 | return targetRecord;
|
156 | },
|
157 | embed(relationship) {
|
158 | if (typeof relationship !== 'object' || relationship.modelName) {
|
159 | throw new Error(chalk.red(`[MemServer] ${this.modelName}.embed(relationshipObject) requires an object as a parameter: { relationshipKey: $RelationshipModel }`));
|
160 | }
|
161 |
|
162 | const key = Object.keys(relationship)[0];
|
163 |
|
164 | if (!relationship[key]) {
|
165 | 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()`));
|
166 | }
|
167 |
|
168 | return Object.assign(this.embedReferences, relationship);
|
169 | },
|
170 | embedReferences: {},
|
171 | serializer(objectOrArray) {
|
172 | if (!objectOrArray) {
|
173 | return;
|
174 | } else if (Array.isArray(objectOrArray)) {
|
175 | return objectOrArray.map((object) => this.serialize(object), []);
|
176 | }
|
177 |
|
178 | return this.serialize(objectOrArray);
|
179 | },
|
180 | serialize(object) {
|
181 | if (Array.isArray(object)) {
|
182 | 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`));
|
183 | }
|
184 |
|
185 | const objectWithAllAttributes = this.attributes.reduce((result, attribute) => {
|
186 | if (result[attribute] === undefined) {
|
187 | result[attribute] = null;
|
188 | }
|
189 |
|
190 | return result;
|
191 | }, object);
|
192 |
|
193 | return Object.keys(this.embedReferences).reduce((result, embedKey) => {
|
194 | const embedModel = this.embedReferences[embedKey];
|
195 | const embeddedRecords = this.getRelationship(object, embedKey, embedModel);
|
196 |
|
197 | return Object.assign(result, { [embedKey]: embedModel.serializer(embeddedRecords) });
|
198 | }, objectWithAllAttributes);
|
199 | },
|
200 | getRelationship(parentObject, relationshipName, relationshipModel) {
|
201 | if (Array.isArray(parentObject)) {
|
202 | throw new Error(chalk.red(`[MemServer] ${this.modelName}.getRelationship expects model input to be an object not an array`));
|
203 | }
|
204 |
|
205 | const targetRelationshipModel = relationshipModel ||
|
206 | targetNamespace.MemServer.Models[emberCliStringUtils.classify(singularize(relationshipName))];
|
207 | const hasManyRelationship = pluralize(relationshipName) === relationshipName;
|
208 |
|
209 | if (!targetRelationshipModel) {
|
210 | 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`));
|
211 | } else if (hasManyRelationship) {
|
212 | const hasManyRecords = targetRelationshipModel.findAll({
|
213 | [`${emberCliStringUtils.underscore(this.modelName)}_id`]: parentObject.id
|
214 | });
|
215 |
|
216 | return hasManyRecords.length > 0 ? hasManyRecords : [];
|
217 | }
|
218 |
|
219 | const objectsReference = parentObject[`${emberCliStringUtils.underscore(targetRelationshipModel.modelName)}_id`];
|
220 |
|
221 | if (objectsReference) {
|
222 | return targetRelationshipModel.find(objectsReference);
|
223 | }
|
224 |
|
225 | return targetRelationshipModel.findBy({
|
226 | [`${emberCliStringUtils.underscore(this.modelName)}_id`]: parentObject.id
|
227 | });
|
228 | }
|
229 | }, options);
|
230 | };
|
231 |
|
232 | function incrementId(Model) {
|
233 | const ids = targetNamespace.MemServer.DB[Model.modelName];
|
234 |
|
235 | if (ids.length === 0) {
|
236 | return 1;
|
237 | }
|
238 |
|
239 | const lastIdInSequence = ids
|
240 | .map((model) => model.id)
|
241 | .sort((a, b) => a - b)
|
242 | .find((id, index, array) => index === array.length - 1 ? true : id + 1 !== array[index + 1]);
|
243 |
|
244 | return lastIdInSequence + 1;
|
245 | }
|
246 |
|
247 |
|
248 | function comparison(model, options, keys, index=0) {
|
249 | const key = keys[index];
|
250 |
|
251 | if (keys.length === index) {
|
252 | return model[key] === options[key];
|
253 | } else if (model[key] === options[key]) {
|
254 | return comparison(model, options, keys, index + 1);
|
255 | }
|
256 |
|
257 | return false;
|
258 | }
|
259 |
|
260 | module.exports = model;
|