UNPKG

10.5 kBJavaScriptView Raw
1'use strict';
2
3function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
4
5var util = _interopDefault(require('util'));
6var chalk = _interopDefault(require('chalk'));
7var Inflector = _interopDefault(require('i'));
8var emberCliStringUtils = require('ember-cli-string-utils');
9
10function 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
18function 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
30const { singularize, pluralize } = Inflector();
31const targetNamespace = typeof global === 'object' ? global : window;
32
33var 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) { // EXAMPLE: { comments: Comment }
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) { // NOTE: add links 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) { // NOTE: test this
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({ // NOTE: id or uuid lookup?
234 [`${emberCliStringUtils.underscore(this.modelName)}_id`]: parentObject.id
235 });
236 }
237 }, options);
238};
239
240function 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// NOTE: if records were ordered by ID, then there could be performance benefit
256function 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
268module.exports = model;