UNPKG

21 kBJavaScriptView Raw
1System.register(['@angular/core', '@angular/http', 'rxjs/Observable', 'rxjs/add/operator/delay', './http-status-codes'], function(exports_1, context_1) {
2 "use strict";
3 var __moduleName = context_1 && context_1.id;
4 var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
5 var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
6 if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
7 else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
8 return c > 3 && r && Object.defineProperty(target, key, r), r;
9 };
10 var __metadata = (this && this.__metadata) || function (k, v) {
11 if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
12 };
13 var __param = (this && this.__param) || function (paramIndex, decorator) {
14 return function (target, key) { decorator(target, key, paramIndex); }
15 };
16 var core_1, http_1, Observable_1, http_status_codes_1;
17 var SEED_DATA, InMemoryBackendConfig, isSuccess, InMemoryBackendService;
18 return {
19 setters:[
20 function (core_1_1) {
21 core_1 = core_1_1;
22 },
23 function (http_1_1) {
24 http_1 = http_1_1;
25 },
26 function (Observable_1_1) {
27 Observable_1 = Observable_1_1;
28 },
29 function (_1) {},
30 function (http_status_codes_1_1) {
31 http_status_codes_1 = http_status_codes_1_1;
32 }],
33 execute: function() {
34 /**
35 * Seed data for in-memory database
36 * Must implement InMemoryDbService.
37 */
38 exports_1("SEED_DATA", SEED_DATA = new core_1.OpaqueToken('seedData'));
39 /**
40 * InMemoryBackendService configuration options
41 * Usage:
42 * provide(InMemoryBackendConfig, {useValue: {delay:600}}),
43 */
44 InMemoryBackendConfig = (function () {
45 function InMemoryBackendConfig(config) {
46 if (config === void 0) { config = {}; }
47 Object.assign(this, {
48 defaultResponseOptions: new http_1.BaseResponseOptions(),
49 delay: 500,
50 delete404: false
51 }, config);
52 }
53 return InMemoryBackendConfig;
54 }());
55 exports_1("InMemoryBackendConfig", InMemoryBackendConfig);
56 exports_1("isSuccess", isSuccess = function (status) { return (status >= 200 && status < 300); });
57 /**
58 * Simulate the behavior of a RESTy web api
59 * backed by the simple in-memory data store provided by the injected InMemoryDataService service.
60 * Conforms mostly to behavior described here:
61 * http://www.restapitutorial.com/lessons/httpmethods.html
62 *
63 * ### Usage
64 *
65 * Create InMemoryDataService class the implements IInMemoryDataService.
66 * Register both this service and the seed data as in:
67 * ```
68 * // other imports
69 * import { HTTP_PROVIDERS, XHRBackend } from 'angular2/http';
70 * import { InMemoryBackendConfig, InMemoryBackendService, SEED_DATA } from '../in-memory-backend/in-memory-backend.service';
71 * import { InMemoryStoryService } from '../api/in-memory-story.service';
72 *
73 * @Component({
74 * selector: ...,
75 * templateUrl: ...,
76 * providers: [
77 * HTTP_PROVIDERS,
78 * provide(XHRBackend, { useClass: InMemoryBackendService }),
79 * provide(SEED_DATA, { useClass: InMemoryStoryService }),
80 * provide(InMemoryBackendConfig, { useValue: { delay: 600 } }),
81 * ]
82 * })
83 * export class AppComponent { ... }
84 * ```
85 */
86 InMemoryBackendService = (function () {
87 function InMemoryBackendService(_seedData, config) {
88 this._seedData = _seedData;
89 this._config = new InMemoryBackendConfig();
90 this._resetDb();
91 var loc = this._getLocation('./');
92 this._config.host = loc.host;
93 this._config.rootPath = loc.pathname;
94 Object.assign(this._config, config);
95 }
96 InMemoryBackendService.prototype.createConnection = function (req) {
97 var res = this._handleRequest(req);
98 var response = new Observable_1.Observable(function (responseObserver) {
99 if (isSuccess(res.status)) {
100 responseObserver.next(res);
101 responseObserver.complete();
102 }
103 else {
104 responseObserver.error(res);
105 }
106 return function () { }; // unsubscribe function
107 });
108 response = response.delay(this._config.delay || 500);
109 return { response: response };
110 };
111 //// protected /////
112 /**
113 * Process Request and return an Http Response object
114 * in the manner of a RESTy web api.
115 *
116 * Expect URI pattern in the form :base/:collectionName/:id?
117 * Examples:
118 * api/characters
119 * api/characters/42
120 * api/characters.json/42 // ignores the ".json"
121 * commands/resetDb // resets the "database"
122 */
123 InMemoryBackendService.prototype._handleRequest = function (req) {
124 var _a = this._parseUrl(req.url), base = _a.base, collectionName = _a.collectionName, id = _a.id, resourceUrl = _a.resourceUrl;
125 var reqInfo = {
126 req: req,
127 base: base,
128 collection: this._db[collectionName],
129 collectionName: collectionName,
130 headers: new http_1.Headers({ 'Content-Type': 'application/json' }),
131 id: this._parseId(id),
132 resourceUrl: resourceUrl
133 };
134 var options;
135 try {
136 if ('commands' === reqInfo.base.toLowerCase()) {
137 options = this._commands(reqInfo);
138 }
139 else if (reqInfo.collection) {
140 switch (req.method) {
141 case http_1.RequestMethod.Get:
142 options = this._get(reqInfo);
143 break;
144 case http_1.RequestMethod.Post:
145 options = this._post(reqInfo);
146 break;
147 case http_1.RequestMethod.Put:
148 options = this._put(reqInfo);
149 break;
150 case http_1.RequestMethod.Delete:
151 options = this._delete(reqInfo);
152 break;
153 default:
154 options = this._createErrorResponse(http_status_codes_1.STATUS.METHOD_NOT_ALLOWED, 'Method not allowed');
155 break;
156 }
157 }
158 else {
159 options = this._createErrorResponse(http_status_codes_1.STATUS.NOT_FOUND, "Collection '" + collectionName + "' not found");
160 }
161 }
162 catch (error) {
163 var err = error.message || error;
164 options = this._createErrorResponse(http_status_codes_1.STATUS.INTERNAL_SERVER_ERROR, "" + err);
165 }
166 options = this._setStatusText(options);
167 if (this._config.defaultResponseOptions) {
168 options = this._config.defaultResponseOptions.merge(options);
169 }
170 return new http_1.Response(options);
171 };
172 InMemoryBackendService.prototype._clone = function (data) {
173 return JSON.parse(JSON.stringify(data));
174 };
175 /**
176 * When the `base`="commands", the `collectionName` is the command
177 * Example URLs:
178 * commands/resetdb // Reset the "database" to its original state
179 * commands/config (GET) // Return this service's config object
180 * commands/config (!GET) // Update the config (e.g. delay)
181 *
182 * Usage:
183 * http.post('commands/resetdb', null);
184 * http.get('commands/config');
185 * http.post('commands/config', '{"delay":1000}');
186 */
187 InMemoryBackendService.prototype._commands = function (reqInfo) {
188 var command = reqInfo.collectionName.toLowerCase();
189 var method = reqInfo.req.method;
190 var options;
191 switch (command) {
192 case 'resetdb':
193 this._resetDb();
194 options = new http_1.ResponseOptions({ status: http_status_codes_1.STATUS.OK });
195 break;
196 case 'config':
197 if (method === http_1.RequestMethod.Get) {
198 options = new http_1.ResponseOptions({
199 body: this._clone(this._config),
200 status: http_status_codes_1.STATUS.OK
201 });
202 }
203 else {
204 // Be nice ... any other method is a config update
205 var body = JSON.parse(reqInfo.req.text() || '{}');
206 Object.assign(this._config, body);
207 options = new http_1.ResponseOptions({ status: http_status_codes_1.STATUS.NO_CONTENT });
208 }
209 break;
210 default:
211 options = this._createErrorResponse(http_status_codes_1.STATUS.INTERNAL_SERVER_ERROR, "Unknown command \"" + command + "\"");
212 }
213 return options;
214 };
215 InMemoryBackendService.prototype._createErrorResponse = function (status, message) {
216 return new http_1.ResponseOptions({
217 body: { 'error': "" + message },
218 headers: new http_1.Headers({ 'Content-Type': 'application/json' }),
219 status: status
220 });
221 };
222 InMemoryBackendService.prototype._delete = function (_a) {
223 var id = _a.id, collection = _a.collection, collectionName = _a.collectionName, headers = _a.headers;
224 if (!id) {
225 return this._createErrorResponse(http_status_codes_1.STATUS.NOT_FOUND, "Missing \"" + collectionName + "\" id");
226 }
227 var exists = this._removeById(collection, id);
228 return new http_1.ResponseOptions({
229 headers: headers,
230 status: (exists || !this._config.delete404) ? http_status_codes_1.STATUS.NO_CONTENT : http_status_codes_1.STATUS.NOT_FOUND
231 });
232 };
233 InMemoryBackendService.prototype._findById = function (collection, id) {
234 return collection.find(function (item) { return item.id === id; });
235 };
236 InMemoryBackendService.prototype._genId = function (collection) {
237 // assumes numeric ids
238 var maxId = 0;
239 collection.reduce(function (prev, item) {
240 maxId = Math.max(maxId, typeof item.id === 'number' ? item.id : maxId);
241 }, null);
242 return maxId + 1;
243 };
244 InMemoryBackendService.prototype._get = function (_a) {
245 var id = _a.id, collection = _a.collection, collectionName = _a.collectionName, headers = _a.headers;
246 var data = (id) ? this._findById(collection, id) : collection;
247 if (!data) {
248 return this._createErrorResponse(http_status_codes_1.STATUS.NOT_FOUND, "'" + collectionName + "' with id='" + id + "' not found");
249 }
250 return new http_1.ResponseOptions({
251 body: { data: this._clone(data) },
252 headers: headers,
253 status: http_status_codes_1.STATUS.OK
254 });
255 };
256 InMemoryBackendService.prototype._getLocation = function (href) {
257 var l = document.createElement('a');
258 l.href = href;
259 return l;
260 };
261 ;
262 InMemoryBackendService.prototype._indexOf = function (collection, id) {
263 return collection.findIndex(function (item) { return item.id === id; });
264 };
265 // tries to parse id as integer; returns input id if not an integer.
266 InMemoryBackendService.prototype._parseId = function (id) {
267 if (!id) {
268 return null;
269 }
270 var idNum = parseInt(id, 10);
271 return isNaN(idNum) ? id : idNum;
272 };
273 InMemoryBackendService.prototype._parseUrl = function (url) {
274 try {
275 var loc = this._getLocation(url);
276 var drop = this._config.rootPath.length;
277 var urlRoot = '';
278 if (loc.host !== this._config.host) {
279 // url for a server on a different host!
280 // assume it's collection is actually here too.
281 drop = 1; // the leading slash
282 urlRoot = loc.protocol + '//' + loc.host + '/';
283 }
284 var path = loc.pathname.substring(drop);
285 var _a = path.split('/'), base = _a[0], collectionName = _a[1], id = _a[2];
286 var resourceUrl = urlRoot + base + '/' + collectionName + '/';
287 collectionName = collectionName.split('.')[0]; // ignore anything after the '.', e.g., '.json'
288 return { base: base, id: id, collectionName: collectionName, resourceUrl: resourceUrl };
289 }
290 catch (err) {
291 var msg = "unable to parse url '" + url + "'; original error: " + err.message;
292 throw new Error(msg);
293 }
294 };
295 InMemoryBackendService.prototype._post = function (_a) {
296 var collection = _a.collection, headers = _a.headers, id = _a.id, req = _a.req, resourceUrl = _a.resourceUrl;
297 var item = JSON.parse(req.text());
298 if (!item.id) {
299 item.id = id || this._genId(collection);
300 }
301 // ignore the request id, if any. Alternatively,
302 // could reject request if id differs from item.id
303 id = item.id;
304 var existingIx = this._indexOf(collection, id);
305 if (existingIx > -1) {
306 collection[existingIx] = item;
307 return new http_1.ResponseOptions({
308 headers: headers,
309 status: http_status_codes_1.STATUS.NO_CONTENT
310 });
311 }
312 else {
313 collection.push(item);
314 headers.set('Location', resourceUrl + '/' + id);
315 return new http_1.ResponseOptions({
316 headers: headers,
317 body: { data: this._clone(item) },
318 status: http_status_codes_1.STATUS.CREATED
319 });
320 }
321 };
322 InMemoryBackendService.prototype._put = function (_a) {
323 var id = _a.id, collection = _a.collection, collectionName = _a.collectionName, headers = _a.headers, req = _a.req;
324 var item = JSON.parse(req.text());
325 if (!id) {
326 return this._createErrorResponse(http_status_codes_1.STATUS.NOT_FOUND, "Missing '" + collectionName + "' id");
327 }
328 if (id !== item.id) {
329 return this._createErrorResponse(http_status_codes_1.STATUS.BAD_REQUEST, "\"" + collectionName + "\" id does not match item.id");
330 }
331 var existingIx = this._indexOf(collection, id);
332 if (existingIx > -1) {
333 collection[existingIx] = item;
334 return new http_1.ResponseOptions({
335 headers: headers,
336 status: http_status_codes_1.STATUS.NO_CONTENT // successful; no content
337 });
338 }
339 else {
340 collection.push(item);
341 return new http_1.ResponseOptions({
342 body: { data: this._clone(item) },
343 headers: headers,
344 status: http_status_codes_1.STATUS.CREATED
345 });
346 }
347 };
348 InMemoryBackendService.prototype._removeById = function (collection, id) {
349 var ix = this._indexOf(collection, id);
350 if (ix > -1) {
351 collection.splice(ix, 1);
352 return true;
353 }
354 return false;
355 };
356 /**
357 * Reset the "database" to its original state
358 */
359 InMemoryBackendService.prototype._resetDb = function () {
360 this._db = this._seedData.createDb();
361 };
362 InMemoryBackendService.prototype._setStatusText = function (options) {
363 try {
364 var statusCode = http_status_codes_1.STATUS_CODE_INFO[options.status];
365 options['statusText'] = statusCode ? statusCode.text : 'Unknown Status';
366 return options;
367 }
368 catch (err) {
369 return new http_1.ResponseOptions({
370 status: http_status_codes_1.STATUS.INTERNAL_SERVER_ERROR,
371 statusText: 'Invalid Server Operation'
372 });
373 }
374 };
375 InMemoryBackendService = __decorate([
376 __param(0, core_1.Inject(SEED_DATA)),
377 __param(1, core_1.Inject(InMemoryBackendConfig)),
378 __param(1, core_1.Optional()),
379 __metadata('design:paramtypes', [Object, Object])
380 ], InMemoryBackendService);
381 return InMemoryBackendService;
382 }());
383 exports_1("InMemoryBackendService", InMemoryBackendService);
384 }
385 }
386});
387//# sourceMappingURL=in-memory-backend.service.js.map
\No newline at end of file