UNPKG

10.3 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 = 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) { // EXAMPLE: { comments: Comment }
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) { // NOTE: add links 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) { // NOTE: test this
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({ // NOTE: id or uuid lookup?
226 [`${emberCliStringUtils.underscore(this.modelName)}_id`]: parentObject.id
227 });
228 }
229 }, options);
230};
231
232function 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// NOTE: if records were ordered by ID, then there could be performance benefit
248function 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
260module.exports = model;