UNPKG

171 kBJavaScriptView Raw
1
2(function denali() {
3 loader.scope('denali', '0.0.42', (loader) => {
4
5
6
7// app/actions/error.js
8//========================================
9
10loader.add('/app/actions/error.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
11
12"use strict";
13Object.defineProperty(exports, "__esModule", { value: true });
14const assert = require("assert");
15const action_1 = require("../../lib/runtime/action");
16const container_1 = require("../../lib/metal/container");
17/**
18 * The default error action. When Denali encounters an error while processing a request, it will
19 * attempt to hand off that error to the `error` action, which can determine how to respond. This is
20 * a good spot to do things like report the error to an error-tracking service, sanitize the error
21 * response based on environment (i.e. a full stack trace in dev, but limited info in prod), etc.
22 *
23 * @export
24 * @class ErrorAction
25 * @extends {Action}
26 */
27class ErrorAction extends action_1.default {
28 constructor() {
29 super(...arguments);
30 this.logger = container_1.lookup('app:logger');
31 this.parser = container_1.lookup('parser:json');
32 }
33 get originalAction() {
34 return this.request._originalAction;
35 }
36 /**
37 * Respond with JSON by default
38 */
39 async respond({ params }) {
40 let error = params.error;
41 assert(error, 'Error action must be invoked with an error as a param');
42 // Print the error to the logs
43 if ((!error.status || error.status >= 500) && this.config.environment !== 'test') {
44 this.logger.error(`Request ${this.request.id} errored:\n${error.stack || error.message}`);
45 }
46 // Ensure a default status code of 500
47 error.status = error.statusCode = error.statusCode || 500;
48 // If debugging info is allowed, attach some debugging info to standard
49 // locations.
50 if (this.config.getWithDefault('logging', 'showDebuggingInfo', this.config.environment !== 'production')) {
51 error.meta = error.meta || {};
52 Object.assign(error.meta, {
53 stack: error.stack.split('\n'),
54 action: this.originalAction
55 });
56 // Otherwise, sanitize the output
57 }
58 else {
59 if (error.statusCode === 500) {
60 error.message = 'Internal Error';
61 }
62 delete error.stack;
63 }
64 if (this.request.accepts('html') && this.config.environment !== 'production') {
65 this.render(error.status, error, { view: 'error.html' });
66 }
67 else {
68 this.render(error.status, error);
69 }
70 }
71}
72exports.default = ErrorAction;
73
74});
75
76
77
78// app/actions/index.js
79//========================================
80
81loader.add('/app/actions/index.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
82
83"use strict";
84Object.defineProperty(exports, "__esModule", { value: true });
85const action_1 = require("../../lib/runtime/action");
86class IndexAction extends action_1.default {
87 respond() {
88 return this.render(200, { hello: 'world' }, { serializer: 'json' });
89 }
90}
91exports.default = IndexAction;
92
93});
94
95
96
97// app/addon.js
98//========================================
99
100loader.add('/app/addon.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
101
102"use strict";
103Object.defineProperty(exports, "__esModule", { value: true });
104const lib_1 = require("../lib");
105class MyAddonAddon extends lib_1.Addon {
106}
107exports.default = MyAddonAddon;
108
109});
110
111
112
113// app/logger.js
114//========================================
115
116loader.add('/app/logger.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
117
118"use strict";
119Object.defineProperty(exports, "__esModule", { value: true });
120var logger_1 = require("../lib/runtime/logger");
121exports.default = logger_1.default;
122
123});
124
125
126
127// app/orm-adapters/application.js
128//========================================
129
130loader.add('/app/orm-adapters/application.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
131
132"use strict";
133Object.defineProperty(exports, "__esModule", { value: true });
134var memory_1 = require("../../lib/data/memory");
135exports.default = memory_1.default;
136
137});
138
139
140
141// app/orm-adapters/memory.js
142//========================================
143
144loader.add('/app/orm-adapters/memory.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
145
146"use strict";
147Object.defineProperty(exports, "__esModule", { value: true });
148var memory_1 = require("../../lib/data/memory");
149exports.default = memory_1.default;
150
151});
152
153
154
155// app/parsers/application.js
156//========================================
157
158loader.add('/app/parsers/application.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
159
160"use strict";
161Object.defineProperty(exports, "__esModule", { value: true });
162var json_api_1 = require("./json-api");
163exports.default = json_api_1.default;
164
165});
166
167
168
169// app/parsers/json-api.js
170//========================================
171
172loader.add('/app/parsers/json-api.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
173
174"use strict";
175Object.defineProperty(exports, "__esModule", { value: true });
176var json_api_1 = require("../../lib/parse/json-api");
177exports.default = json_api_1.default;
178
179});
180
181
182
183// app/parsers/json.js
184//========================================
185
186loader.add('/app/parsers/json.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
187
188"use strict";
189Object.defineProperty(exports, "__esModule", { value: true });
190var json_1 = require("../../lib/parse/json");
191exports.default = json_1.default;
192
193});
194
195
196
197// app/router.js
198//========================================
199
200loader.add('/app/router.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
201
202"use strict";
203Object.defineProperty(exports, "__esModule", { value: true });
204var router_1 = require("../lib/runtime/router");
205exports.default = router_1.default;
206
207});
208
209
210
211// app/serializers/application.js
212//========================================
213
214loader.add('/app/serializers/application.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
215
216"use strict";
217Object.defineProperty(exports, "__esModule", { value: true });
218var json_api_1 = require("./json-api");
219exports.default = json_api_1.default;
220
221});
222
223
224
225// app/serializers/json-api.js
226//========================================
227
228loader.add('/app/serializers/json-api.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
229
230"use strict";
231Object.defineProperty(exports, "__esModule", { value: true });
232var json_api_1 = require("../../lib/render/json-api");
233exports.default = json_api_1.default;
234
235});
236
237
238
239// app/serializers/json.js
240//========================================
241
242loader.add('/app/serializers/json.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
243
244"use strict";
245Object.defineProperty(exports, "__esModule", { value: true });
246var json_1 = require("../../lib/render/json");
247exports.default = json_1.default;
248
249});
250
251
252
253// app/services/config.js
254//========================================
255
256loader.add('/app/services/config.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
257
258"use strict";
259Object.defineProperty(exports, "__esModule", { value: true });
260var config_1 = require("../../lib/runtime/config");
261exports.default = config_1.default;
262
263});
264
265
266
267// app/views/error.js
268//========================================
269
270loader.add('/app/views/error.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
271
272"use strict";
273Object.defineProperty(exports, "__esModule", { value: true });
274const lodash_1 = require("lodash");
275const view_1 = require("../../lib/render/view");
276let template = lodash_1.template(`
277 <html>
278 <head>
279 <style>
280 body {
281 font-family: Arial, Helvetica, sans-serif;
282 background: #f7f7f7;
283 margin: 0;
284 }
285 pre {
286 font-family: Inconsolata, Monaco, Menlo, monospace;
287 }
288 .headline {
289 background: #fff;
290 padding: 30px;
291 color: #DC4B4B;
292 font-family: Inconsolata, Monaco, Menlo, monospace;
293 border-bottom: 1px solid #ddd;
294 margin-bottom: 0;
295 }
296 .lead {
297 display: block;
298 margin-bottom: 7px;
299 color: #aaa;
300 font-size: 14px;
301 font-family: Arial, Helvetica, sans-serif;
302 font-weight: 300;
303 }
304 .details {
305 padding: 30px;
306 }
307 </style>
308 </head>
309 <body>
310 <h1 class='headline'>
311 <small class='lead'>There was an error with this request:</small>
312 <%= data.error.message %>
313 </h1>
314 <div class='details'>
315 <% if (data.error.action) { %>
316 <h2 class='source'>from <%= data.error.action %></h2>
317 <% } %>
318 <h5>Stacktrace:</h5>
319 <pre><code><%= data.error.stack %></code></pre>
320 </div>
321 </body>
322 </html>
323`, {
324 variable: 'data'
325});
326class ErrorView extends view_1.default {
327 async render(action, response, error, options) {
328 response.setHeader('Content-type', 'text/html');
329 error.stack = error.stack.replace('\\n', '\n');
330 response.write(template({ error }));
331 response.end();
332 }
333}
334exports.default = ErrorView;
335
336});
337
338
339
340// config/environment.js
341//========================================
342
343loader.add('/config/environment.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
344
345module.exports = function baseConfig(environment, appConfig) {
346 if (!appConfig.logging) {
347 appConfig.logging = {};
348 }
349 if (!appConfig.logging.hasOwnProperty('showDebuggingInfo')) {
350 appConfig.logging.showDebuggingInfo = environment !== 'production';
351 }
352};
353
354});
355
356
357
358// config/initializers/define-orm-models.js
359//========================================
360
361loader.add('/config/initializers/define-orm-models.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
362
363"use strict";
364Object.defineProperty(exports, "__esModule", { value: true });
365const lodash_1 = require("lodash");
366const container_1 = require("../../lib/metal/container");
367exports.default = {
368 name: 'define-orm-models',
369 /**
370 * Find all models, group them by their orm adapter, then give each adapter the chance to define
371 * any internal model representation necessary.
372 */
373 async initialize(application) {
374 let models = container_1.default.lookupAll('model');
375 let modelsGroupedByAdapter = new Map();
376 lodash_1.forEach(models, (ModelClass, modelName) => {
377 if (ModelClass.hasOwnProperty('abstract') && ModelClass.abstract) {
378 return;
379 }
380 let Adapter = container_1.lookup(`orm-adapter:${modelName}`, { loose: true }) || container_1.default.lookup('orm-adapter:application');
381 if (!modelsGroupedByAdapter.has(Adapter)) {
382 modelsGroupedByAdapter.set(Adapter, []);
383 }
384 modelsGroupedByAdapter.get(Adapter).push(ModelClass);
385 });
386 for (let [Adapter, models] of modelsGroupedByAdapter) {
387 if (Adapter.defineModels) {
388 await Adapter.defineModels(models);
389 }
390 }
391 }
392};
393
394});
395
396
397
398// config/middleware.js
399//========================================
400
401loader.add('/config/middleware.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
402
403"use strict";
404Object.defineProperty(exports, "__esModule", { value: true });
405const lodash_1 = require("lodash");
406const timing = require("response-time");
407const compression = require("compression");
408const cookies = require("cookie-parser");
409const cors = require("cors");
410const helmet = require("helmet");
411const morgan = require("morgan");
412/**
413 * Denali ships with several base middleware included, each of which can be enabled/disabled
414 * individually through config options.
415 */
416function baseMiddleware(router, application) {
417 let config = application.config;
418 /**
419 * Returns true if the given property either does not exist on the config object, or it does exist
420 * and it's `enabled` property is not `false`. All the middleware here are opt out, so to disable
421 * you must define set that middleware's root config property to `{ enabled: false }`
422 */
423 function isEnabled(prop) {
424 return config.get(prop) == null || config.get(prop, 'enabled') !== false;
425 }
426 if (isEnabled('timing')) {
427 router.use(timing());
428 }
429 if (isEnabled('logging')) {
430 let defaultLoggingFormat = application.environment === 'production' ? 'combined' : 'dev';
431 let defaultLoggingOptions = {
432 // tslint:disable-next-line:completed-docs
433 skip() {
434 return application.environment === 'test';
435 }
436 };
437 let format = config.getWithDefault('logging', 'format', defaultLoggingFormat);
438 let options = lodash_1.defaults(config.getWithDefault('logging', {}), defaultLoggingOptions);
439 router.use(morgan(format, options));
440 // Patch morgan to read from our non-express response
441 morgan.token('res', (req, res, field) => {
442 let header = res.getHeader(field);
443 if (typeof header === 'number') {
444 header = String(header);
445 }
446 else if (Array.isArray(header)) {
447 header = header.join(', ');
448 }
449 return header;
450 });
451 }
452 if (isEnabled('compression')) {
453 router.use(compression());
454 }
455 if (isEnabled('cookies')) {
456 router.use(cookies(config.get('cookies')));
457 }
458 if (isEnabled('cors')) {
459 router.use(cors(config.get('cors')));
460 }
461 if (isEnabled('xssFilter')) {
462 router.use(helmet.xssFilter());
463 }
464 if (isEnabled('frameguard')) {
465 router.use(helmet.frameguard());
466 }
467 if (isEnabled('hidePoweredBy')) {
468 router.use(helmet.hidePoweredBy());
469 }
470 if (isEnabled('ieNoOpen')) {
471 router.use(helmet.ieNoOpen());
472 }
473 if (isEnabled('noSniff')) {
474 router.use(helmet.noSniff());
475 }
476}
477exports.default = baseMiddleware;
478
479});
480
481
482
483// lib/data/descriptors.js
484//========================================
485
486loader.add('/lib/data/descriptors.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
487
488"use strict";
489Object.defineProperty(exports, "__esModule", { value: true });
490/**
491 * Base Descriptor class
492 *
493 * @package data
494 */
495class Descriptor {
496 /**
497 * Creates an instance of Descriptor.
498 */
499 constructor(options = {}) {
500 this.options = options;
501 }
502}
503exports.Descriptor = Descriptor;
504/**
505 * The Attribute class is used to tell Denali what the available attributes are
506 * on your Model. You shouldn't use the Attribute class directly; instead,
507 * import the `attr()` method from Denali, and use it to define an attribute:
508 *
509 * import { attr } from 'denali';
510 * class Post extends ApplicationModel {
511 * static title = attr('text');
512 * }
513 *
514 * Note that attributes must be defined as `static` properties on your Model
515 * class.
516 *
517 * The `attr()` method takes two arguments:
518 *
519 * * `type` - a string indicating the type of this attribute. Denali doesn't
520 * care what this string is. Your ORM adapter should specify what types it
521 * expects.
522 * * `options` - any additional options for this attribute. At the moment,
523 * these are used solely by your ORM adapter, there are no additional options
524 * that Denali expects itself.
525 *
526 * @package data
527 * @since 0.1.0
528 */
529class AttributeDescriptor extends Descriptor {
530 constructor(datatype, options) {
531 super(options);
532 this.datatype = datatype;
533 /**
534 * Convenience flag for checking if this is an attribute
535 *
536 * @since 0.1.0
537 */
538 this.isAttribute = true;
539 }
540}
541exports.AttributeDescriptor = AttributeDescriptor;
542/**
543 * Syntax sugar factory method for creating Attributes
544 *
545 * @package data
546 * @since 0.1.0
547 */
548function attr(datatype, options) {
549 return new AttributeDescriptor(datatype, options);
550}
551exports.attr = attr;
552/**
553 * The HasManyRelationship class is used to describe a 1 to many or many to
554 * many relationship on your Model. You shouldn't use the HasManyRelationship
555 * class directly; instead, import the `hasMany()` method from Denali, and use
556 * it to define a relationship:
557 *
558 * import { hasMany } from 'denali';
559 * class Post extends ApplicationModel {
560 * static comments = hasMany('comment');
561 * }
562 *
563 * Note that relationships must be defined as `static` properties on your Model
564 * class.
565 *
566 * The `hasMany()` method takes two arguments:
567 *
568 * * `type` - a string indicating the type of model for this relationship.
569 * * `options` - any additional options for this attribute. At the moment,
570 * these are used solely by your ORM adapter, there are no additional options
571 * that Denali expects itself.
572 *
573 * @package data
574 * @since 0.1.0
575 */
576class HasManyRelationshipDescriptor extends Descriptor {
577 constructor(relatedModelName, options) {
578 super(options);
579 this.relatedModelName = relatedModelName;
580 /**
581 * Convenience flag for checking if this is a relationship
582 *
583 * @since 0.1.0
584 */
585 this.isRelationship = true;
586 /**
587 * Relationship mode, i.e. 1 -> 1 or 1 -> N
588 *
589 * @since 0.1.0
590 */
591 this.mode = 'hasMany';
592 }
593}
594exports.HasManyRelationshipDescriptor = HasManyRelationshipDescriptor;
595/**
596 * Syntax sugar factory function for creating HasManyRelationships
597 *
598 * @package data
599 * @since 0.1.0
600 */
601function hasMany(relatedModelName, options) {
602 return new HasManyRelationshipDescriptor(relatedModelName, options);
603}
604exports.hasMany = hasMany;
605/**
606 * The HasOneRelationship class is used to describe a 1 to many or 1 to 1
607 * relationship on your Model. You shouldn't use the HasOneRelationship class
608 * directly; instead, import the `hasOne()` method from Denali, and use it to
609 * define a relationship:
610 *
611 * import { hasOne } from 'denali';
612 * class Post extends ApplicationModel {
613 * static author = hasOne('user');
614 * }
615 *
616 * Note that relationships must be defined as `static` properties on your Model
617 * class.
618 *
619 * The `hasOne()` method takes two arguments:
620 *
621 * * `type` - a string indicating the type of model for this relationship.
622 * * `options` - any additional options for this attribute. At the moment,
623 * these are used solely by your ORM adapter, there are no additional options
624 * that Denali expects itself.
625 *
626 * @package data
627 * @since 0.1.0
628 */
629class HasOneRelationshipDescriptor extends Descriptor {
630 constructor(relatedModelName, options) {
631 super(options);
632 this.relatedModelName = relatedModelName;
633 /**
634 * Convenience flag for checking if this is a relationship
635 *
636 * @since 0.1.0
637 */
638 this.isRelationship = true;
639 /**
640 * Relationship mode, i.e. 1 -> 1 or 1 -> N
641 *
642 * @since 0.1.0
643 */
644 this.mode = 'hasOne';
645 }
646}
647exports.HasOneRelationshipDescriptor = HasOneRelationshipDescriptor;
648/**
649 * Syntax sugar factory function for creating HasOneRelationships
650 *
651 * @package data
652 * @since 0.1.0
653 */
654function hasOne(relatedModelName, options) {
655 return new HasOneRelationshipDescriptor(relatedModelName, options);
656}
657exports.hasOne = hasOne;
658
659});
660
661
662
663// lib/data/memory.js
664//========================================
665
666loader.add('/lib/data/memory.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
667
668"use strict";
669Object.defineProperty(exports, "__esModule", { value: true });
670const lodash_1 = require("lodash");
671const orm_adapter_1 = require("./orm-adapter");
672const assert = require("assert");
673const inflection_1 = require("inflection");
674let guid = 0;
675/**
676 * An in-memory ORM adapter for getting started quickly, testing, and
677 * debugging. Should **not** be used for production data.
678 *
679 * @package data
680 * @since 0.1.0
681 */
682class MemoryAdapter extends orm_adapter_1.default {
683 constructor() {
684 super(...arguments);
685 /**
686 * An in-memory cache of records. Top level objects are collections of
687 * records by type, indexed by record id.
688 */
689 this._cache = {};
690 }
691 /**
692 * Get the collection of records for a given type, indexed by record id. If
693 * the collection doesn't exist yet, create it and return the empty
694 * collection.
695 */
696 _cacheFor(type) {
697 if (!this._cache[type]) {
698 this._cache[type] = {};
699 }
700 return this._cache[type];
701 }
702 // tslint:disable:completed-docs
703 async find(type, id) {
704 return this._cacheFor(type)[id] || null;
705 }
706 async queryOne(type, query) {
707 return lodash_1.find(this._cacheFor(type), query) || null;
708 }
709 async all(type) {
710 return lodash_1.values(this._cacheFor(type));
711 }
712 async query(type, query) {
713 return lodash_1.filter(this._cacheFor(type), query);
714 }
715 buildRecord(type, data = {}) {
716 this._cacheFor(type);
717 return data;
718 }
719 idFor(model) {
720 return model.record.id;
721 }
722 setId(model, value) {
723 let collection = this._cacheFor(model.modelName);
724 delete collection[model.record.id];
725 model.record.id = value;
726 collection[value] = model.record;
727 }
728 getAttribute(model, property) {
729 return model.record[property] === undefined ? null : model.record[property];
730 }
731 setAttribute(model, property, value) {
732 model.record[property] = value;
733 return true;
734 }
735 deleteAttribute(model, property) {
736 model.record[property] = null;
737 return true;
738 }
739 async getRelated(model, relationship, descriptor, query) {
740 let relatedCollection = this._cacheFor(descriptor.relatedModelName);
741 if (descriptor.mode === 'hasMany') {
742 let related = lodash_1.filter(relatedCollection, (relatedRecord) => {
743 let relatedIds = model.record[`${inflection_1.singularize(relationship)}_ids`];
744 return relatedIds && relatedIds.includes(relatedRecord.id);
745 });
746 if (query) {
747 related = lodash_1.filter(related, query);
748 }
749 return related;
750 }
751 return this.queryOne(descriptor.relatedModelName, { id: model.record[`${relationship}_id`] });
752 }
753 async setRelated(model, relationship, descriptor, relatedModels) {
754 if (Array.isArray(relatedModels)) {
755 assert(descriptor.mode === 'hasMany', `You tried to set ${relationship} to an array of related records, but it is a hasOne relationship`);
756 model.record[`${inflection_1.singularize(relationship)}_ids`] = lodash_1.map(relatedModels, 'record.id');
757 }
758 else {
759 model.record[`${relationship}_id`] = relatedModels.record.id;
760 }
761 }
762 async addRelated(model, relationship, descriptor, relatedModel) {
763 let relatedIds = model.record[`${inflection_1.singularize(relationship)}_ids`];
764 if (!relatedIds) {
765 relatedIds = model.record[`${inflection_1.singularize(relationship)}_ids`] = [];
766 }
767 relatedIds.push(relatedModel.id);
768 }
769 async removeRelated(model, relationship, descriptor, relatedModel) {
770 lodash_1.remove(model.record[`${inflection_1.singularize(relationship)}_ids`], (id) => id === relatedModel.id);
771 }
772 async saveRecord(model) {
773 let collection = this._cacheFor(model.modelName);
774 if (model.record.id == null) {
775 guid += 1;
776 model.record.id = guid;
777 }
778 collection[model.record.id] = model.record;
779 return model.record;
780 }
781 async deleteRecord(model) {
782 let collection = this._cacheFor(model.modelName);
783 delete collection[model.record.id];
784 }
785}
786exports.default = MemoryAdapter;
787
788});
789
790
791
792// lib/data/model.js
793//========================================
794
795loader.add('/lib/data/model.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
796
797"use strict";
798Object.defineProperty(exports, "__esModule", { value: true });
799const assert = require("assert");
800const util = require("util");
801const createDebug = require("debug");
802const inflection_1 = require("inflection");
803const lodash_1 = require("lodash");
804const object_1 = require("../metal/object");
805const container_1 = require("../metal/container");
806const debug = createDebug('denali:model');
807const augmentedWithAccessors = Symbol();
808/**
809 * The Model class is the core of Denali's unique approach to data and ORMs. It
810 * acts as a wrapper and translation layer that provides a unified interface to
811 * access and manipulate data, but translates those interactions into ORM
812 * specific operations via ORM adapters.
813 *
814 * The primary purpose of having Models in Denali is to allow Denali addons to
815 * access a common interface for manipulating data. Importantly, the goal is
816 * **not** to let you swap different ORMs / databases with little effort. Don't
817 * be surprised if you find your app littered with ORM specific code - that is
818 * expected and even encouraged. For more on this concept, check out the Denali
819 * blog.
820 *
821 * TODO: link to blog post on ^
822 *
823 * @package data
824 * @since 0.1.0
825 */
826class Model extends object_1.default {
827 /**
828 * Tell the underlying ORM to build this record
829 */
830 constructor(data, options) {
831 super();
832 /**
833 * The underlying ORM adapter record. An opaque value to Denali, handled
834 * entirely by the ORM adapter.
835 *
836 * @since 0.1.0
837 */
838 this.record = null;
839 this.constructor._augmentWithSchemaAccessors();
840 this.record = this.constructor.adapter.buildRecord(this.modelName, data, options);
841 }
842 /**
843 * Returns the schema filtered down to just the attribute fields
844 */
845 static get attributes() {
846 // <any> is needed here because pickBy doesn't properly act as a type guard
847 // see: https://github.com/Microsoft/TypeScript/issues/7657
848 return lodash_1.pickBy(this.schema, (descriptor) => descriptor.isAttribute);
849 }
850 /**
851 * Returns the schema filtered down to just the relationship fields
852 */
853 static get relationships() {
854 // <any> is needed here because pickBy doesn't properly act as a type guard
855 // see: https://github.com/Microsoft/TypeScript/issues/7657
856 return lodash_1.pickBy(this.schema, (descriptor) => descriptor.isRelationship);
857 }
858 static _augmentWithSchemaAccessors() {
859 if (this.prototype[augmentedWithAccessors]) {
860 return;
861 }
862 this.prototype[augmentedWithAccessors] = true;
863 lodash_1.forEach(this.schema, (descriptor, name) => {
864 if (descriptor.isAttribute) {
865 Object.defineProperty(this.prototype, name, {
866 configurable: true,
867 get() {
868 return this.constructor.adapter.getAttribute(this, name);
869 },
870 set(newValue) {
871 return this.constructor.adapter.setAttribute(this, name, newValue);
872 }
873 });
874 }
875 else {
876 // author => "Author"
877 let methodRoot = lodash_1.upperFirst(name);
878 // getAuthor(options?)
879 Object.defineProperty(this.prototype, `get${methodRoot}`, {
880 configurable: true,
881 value(options) { return this.getRelated(name, options); }
882 });
883 // setAuthor(comments, options?)
884 Object.defineProperty(this.prototype, `set${methodRoot}`, {
885 configurable: true,
886 value(relatedModels, options) {
887 return this.setRelated(name, relatedModels, options);
888 }
889 });
890 if (descriptor.mode === 'hasMany') {
891 let singularRoot = inflection_1.singularize(methodRoot);
892 // addComment(comment, options?)
893 Object.defineProperty(this.prototype, `add${singularRoot}`, {
894 configurable: true,
895 value(relatedModel, options) {
896 return this.addRelated(name, relatedModel, options);
897 }
898 });
899 // removeComment(comment, options?)
900 Object.defineProperty(this.prototype, `remove${singularRoot}`, {
901 configurable: true,
902 value(relatedModel, options) {
903 return this.removeRelated(name, relatedModel, options);
904 }
905 });
906 }
907 }
908 });
909 }
910 /**
911 * Builds a new Model instance from an already existing ORM record reference
912 *
913 * @param record The ORM adapter record object
914 */
915 static build(record) {
916 let model = new this();
917 model.record = record;
918 return model;
919 }
920 /**
921 * Retrieve a single record by it's id
922 *
923 * @param id The id of the record you want to lookup
924 * @param options Options passed through to the ORM adapter
925 * @since 0.1.0
926 */
927 static async find(id, options) {
928 assert(id != null, `You must pass an id to Model.find(id)`);
929 debug(`${this.modelName} find: ${id}`);
930 let record = await this.adapter.find(this.modelName, id, options);
931 if (!record) {
932 return null;
933 }
934 return this.build(record);
935 }
936 /**
937 * Retrieve the first record matching the given query
938 *
939 * @param query The query to pass through to the ORM adapter
940 * @param options An options object passed through to the ORM adapter
941 * @since 0.1.0
942 */
943 static async queryOne(query, options) {
944 assert(query != null, `You must pass a query to Model.queryOne(conditions)`);
945 debug(`${this.modelName} queryOne: ${util.inspect(query)}`);
946 let record = await this.adapter.queryOne(this.modelName, query, options);
947 if (!record) {
948 return null;
949 }
950 return this.build(record);
951 }
952 /**
953 * Fetch all records matching the given query
954 *
955 * @param query The query to pass through to the ORM adapter
956 * @param options An options object passed through to the ORM adapter
957 * @since 0.1.0
958 */
959 static async query(query, options) {
960 assert(query != null, `You must pass a query to Model.query(conditions)`);
961 debug(`${this.modelName} query: ${util.inspect(query)}`);
962 let records = await this.adapter.query(this.modelName, query, options);
963 return records.map((record) => this.build(record));
964 }
965 /**
966 * Fetch all records of this type
967 *
968 * @param options An options object passed through to the ORM adapter
969 * @since 0.1.0
970 */
971 static async all(options) {
972 debug(`${this.modelName} all`);
973 let result = await this.adapter.all(this.modelName, options);
974 return result.map((record) => this.build(record));
975 }
976 /**
977 * Create a new Model instance with the supplied data, and immediately
978 * persist it
979 *
980 * @param data Data to populate the new Model instance with
981 * @param options An options object passed through to the ORM adapter
982 * @since 0.1.0
983 */
984 static async create(data = {}, options) {
985 let model = new this(data, options);
986 return model.save();
987 }
988 /**
989 * The ORM adapter specific to this model type. Defaults to the application's
990 * ORM adapter if none for this specific model type is found.
991 *
992 * @since 0.1.0
993 */
994 static get adapter() {
995 return container_1.lookup(`orm-adapter:${this.modelName}`);
996 }
997 /**
998 * The name of this Model type. Used in a variety of use cases, including
999 * serialization.
1000 *
1001 * @since 0.1.0
1002 */
1003 static get modelName() {
1004 let name = this.name;
1005 if (name.endsWith('Model')) {
1006 name = name.substring(0, name.length - 'Model'.length);
1007 }
1008 name = lodash_1.kebabCase(name);
1009 return name;
1010 }
1011 /**
1012 * The name of this Model type. Used in a variety of use cases, including
1013 * serialization.
1014 *
1015 * @since 0.1.0
1016 */
1017 get modelName() {
1018 return this.constructor.modelName;
1019 }
1020 /**
1021 * The id of the record
1022 *
1023 * @since 0.1.0
1024 */
1025 get id() {
1026 return this.constructor.adapter.idFor(this);
1027 }
1028 set id(value) {
1029 this.constructor.adapter.setId(this, value);
1030 }
1031 /**
1032 * Persist this model.
1033 *
1034 * @since 0.1.0
1035 */
1036 async save(options) {
1037 debug(`saving ${this.toString()}`);
1038 await this.constructor.adapter.saveRecord(this, options);
1039 return this;
1040 }
1041 /**
1042 * Delete this model.
1043 *
1044 * @since 0.1.0
1045 */
1046 async delete(options) {
1047 debug(`deleting ${this.toString()}`);
1048 await this.constructor.adapter.deleteRecord(this, options);
1049 }
1050 /**
1051 * Returns the related record(s) for the given relationship.
1052 *
1053 * @since 0.1.0
1054 */
1055 async getRelated(relationshipName, options) {
1056 let descriptor = this.constructor.schema[relationshipName];
1057 assert(descriptor && descriptor.isRelationship, `You tried to fetch related ${relationshipName}, but no such relationship exists on ${this.modelName}`);
1058 let RelatedModel = container_1.lookup(`model:${descriptor.relatedModelName}`);
1059 let results = await this.constructor.adapter.getRelated(this, relationshipName, descriptor, options);
1060 if (descriptor.mode === 'hasOne') {
1061 assert(!Array.isArray(results), `The ${this.modelName} ORM adapter returned an array for the hasOne '${relationshipName}' relationship - it should return either an ORM record or null.`);
1062 return results ? RelatedModel.create(results) : null;
1063 }
1064 assert(Array.isArray(results), `The ${this.modelName} ORM adapter did not return an array for the hasMany '${relationshipName}' relationship - it should return an array (empty if no related records exist).`);
1065 return results.map((record) => RelatedModel.build(record));
1066 }
1067 /**
1068 * Replaces the related records for the given relationship with the supplied
1069 * related records.
1070 *
1071 * @since 0.1.0
1072 */
1073 async setRelated(relationshipName, relatedModels, options) {
1074 let descriptor = this.constructor.schema[relationshipName];
1075 await this.constructor.adapter.setRelated(this, relationshipName, descriptor, relatedModels, options);
1076 }
1077 /**
1078 * Add a related record to a hasMany relationship.
1079 *
1080 * @since 0.1.0
1081 */
1082 async addRelated(relationshipName, relatedModel, options) {
1083 let descriptor = this.constructor.schema[inflection_1.pluralize(relationshipName)];
1084 await this.constructor.adapter.addRelated(this, relationshipName, descriptor, relatedModel, options);
1085 }
1086 /**
1087 * Remove the given record from the hasMany relationship
1088 *
1089 * @since 0.1.0
1090 */
1091 async removeRelated(relationshipName, relatedModel, options) {
1092 let descriptor = this.constructor.schema[inflection_1.pluralize(relationshipName)];
1093 await this.constructor.adapter.removeRelated(this, relationshipName, descriptor, relatedModel, options);
1094 }
1095 /**
1096 * Return an human-friendly string representing this Model instance, with a
1097 * summary of it's attributes
1098 *
1099 * @since 0.1.0
1100 */
1101 inspect() {
1102 let attributesSummary = [];
1103 lodash_1.forEach(this.constructor.schema, (descriptor, name) => {
1104 attributesSummary.push(`${name}=${util.inspect(this[name])}`);
1105 });
1106 return `<${lodash_1.startCase(this.modelName)}:${this.id == null ? '-new-' : this.id} ${attributesSummary.join(', ')}>`;
1107 }
1108 /**
1109 * Return an human-friendly string representing this Model instance
1110 *
1111 * @since 0.1.0
1112 */
1113 toString() {
1114 return `<${lodash_1.startCase(this.modelName)}:${this.id == null ? '-new-' : this.id}>`;
1115 }
1116}
1117/**
1118 * Marks the Model as an abstract base model, so ORM adapters can know not to
1119 * create tables or other supporting infrastructure.
1120 *
1121 * @since 0.1.0
1122 */
1123Model.abstract = false;
1124/**
1125 * The schema definition for this model. Keys are the field names, and values
1126 * should be either `attr(...)', `hasOne(...)`, or `hasMany(...)`
1127 */
1128Model.schema = {};
1129exports.default = Model;
1130
1131});
1132
1133
1134
1135// lib/data/orm-adapter.js
1136//========================================
1137
1138loader.add('/lib/data/orm-adapter.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
1139
1140"use strict";
1141Object.defineProperty(exports, "__esModule", { value: true });
1142const object_1 = require("../metal/object");
1143/**
1144 * The ORMAdapter class is responsible for enabling Denali to communicate with
1145 * the ORM of your choice. It does this by boiling down the possible actions
1146 * that a user might before against a Model that would involve persistence into
1147 * a set of basic operations. Your adapter then implements these operations,
1148 * and Denali can build on top of that.
1149 *
1150 * Most of the values passed between your application code and the underlying
1151 * ORM are opaque to Denali. This means that Denali has no idea what that
1152 * `query` object looks like or does - it just blindly hands it off to your
1153 * ORM adapter, which can plug it into the ORM interface appropriately.
1154 *
1155 * Most instance-level methods are given a reference to the Denali Model
1156 * instance that is being acted upon. Most ORM adapters will use that model
1157 * instance to access `model.record`, which is the slot used to store the
1158 * instance of the ORM's record. However, the full Model instance is supplied
1159 * to provide the adapter with maximum flexibility.
1160 *
1161 * The ORM adapter interface was designed to be a high enough abstraction that
1162 * most data stores should support most of the operations. However, it is not
1163 * required that an adapter support all of the operations - for example, not
1164 * all data stores support changing a record's ID. In those cases, the adapter
1165 * method may simply throw.
1166 *
1167 * @package data
1168 * @since 0.1.0
1169 */
1170class ORMAdapter extends object_1.default {
1171}
1172exports.default = ORMAdapter;
1173
1174});
1175
1176
1177
1178// lib/index.js
1179//========================================
1180
1181loader.add('/lib/index.js', {"isMain":true}, function(module, exports, require, __dirname, __filename) {
1182
1183"use strict";
1184/**
1185 *
1186 * This is the main module exported by Denali when it is loaded via
1187 * `require/import`.
1188 *
1189 * This exports convenient shortcuts to other modules within Denali.
1190 * Rather than having to `import Addon from 'denali/lib/runtime/addon'`,
1191 * you can just `import { Addon } from 'denali'`.
1192 *
1193 */
1194Object.defineProperty(exports, "__esModule", { value: true });
1195// Data
1196const descriptors_1 = require("./data/descriptors");
1197exports.attr = descriptors_1.attr;
1198exports.hasMany = descriptors_1.hasMany;
1199exports.hasOne = descriptors_1.hasOne;
1200exports.HasOneRelationshipDescriptor = descriptors_1.HasOneRelationshipDescriptor;
1201exports.HasManyRelationshipDescriptor = descriptors_1.HasManyRelationshipDescriptor;
1202exports.AttributeDescriptor = descriptors_1.AttributeDescriptor;
1203const model_1 = require("./data/model");
1204exports.Model = model_1.default;
1205const orm_adapter_1 = require("./data/orm-adapter");
1206exports.ORMAdapter = orm_adapter_1.default;
1207const memory_1 = require("./data/memory");
1208exports.MemoryAdapter = memory_1.default;
1209// Render
1210const serializer_1 = require("./render/serializer");
1211exports.Serializer = serializer_1.default;
1212const json_1 = require("./render/json");
1213exports.JSONSerializer = json_1.default;
1214const json_api_1 = require("./render/json-api");
1215exports.JSONAPISerializer = json_api_1.default;
1216const view_1 = require("./render/view");
1217exports.View = view_1.default;
1218// Parse
1219const parser_1 = require("./parse/parser");
1220exports.Parser = parser_1.default;
1221const json_2 = require("./parse/json");
1222exports.JSONParser = json_2.default;
1223const json_api_2 = require("./parse/json-api");
1224exports.JSONAPIParser = json_api_2.default;
1225// Metal
1226const instrumentation_1 = require("./metal/instrumentation");
1227exports.Instrumentation = instrumentation_1.default;
1228const mixin_1 = require("./metal/mixin");
1229exports.mixin = mixin_1.default;
1230exports.createMixin = mixin_1.createMixin;
1231const container_1 = require("./metal/container");
1232exports.container = container_1.default;
1233exports.lookup = container_1.lookup;
1234exports.Container = container_1.Container;
1235const resolver_1 = require("./metal/resolver");
1236exports.Resolver = resolver_1.default;
1237const object_1 = require("./metal/object");
1238exports.DenaliObject = object_1.default;
1239// Runtime
1240const action_1 = require("./runtime/action");
1241exports.Action = action_1.default;
1242const addon_1 = require("./runtime/addon");
1243exports.Addon = addon_1.default;
1244const application_1 = require("./runtime/application");
1245exports.Application = application_1.default;
1246const errors_1 = require("./runtime/errors");
1247exports.Errors = errors_1.default;
1248const logger_1 = require("./runtime/logger");
1249exports.Logger = logger_1.default;
1250const request_1 = require("./runtime/request");
1251exports.Request = request_1.default;
1252const router_1 = require("./runtime/router");
1253exports.Router = router_1.default;
1254const service_1 = require("./runtime/service");
1255exports.Service = service_1.default;
1256const config_1 = require("./runtime/config");
1257exports.ConfigService = config_1.default;
1258// Test
1259const acceptance_test_1 = require("./test/acceptance-test");
1260exports.setupAcceptanceTest = acceptance_test_1.default;
1261exports.AcceptanceTest = acceptance_test_1.AcceptanceTest;
1262const unit_test_1 = require("./test/unit-test");
1263exports.setupUnitTest = unit_test_1.default;
1264exports.UnitTest = unit_test_1.UnitTest;
1265const mock_request_1 = require("./test/mock-request");
1266exports.MockRequest = mock_request_1.default;
1267const mock_response_1 = require("./test/mock-response");
1268exports.MockResponse = mock_response_1.default;
1269
1270});
1271
1272
1273
1274// lib/metal/container.js
1275//========================================
1276
1277loader.add('/lib/metal/container.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
1278
1279"use strict";
1280Object.defineProperty(exports, "__esModule", { value: true });
1281const assert = require("assert");
1282const lodash_1 = require("lodash");
1283const resolver_1 = require("./resolver");
1284const strings_1 = require("../utils/strings");
1285const crawl = require("tree-crawl");
1286const DEFAULT_OPTIONS = {
1287 singleton: true,
1288 fallbacks: []
1289};
1290/**
1291 * The Container is the dependency injection solution for Denali. It is
1292 * responsible for managing the lifecycle of objects (i.e. singletons),
1293 * as well as orchestrating dependency lookup. This provides two key benefits:
1294 *
1295 * * Apps can consume classes that originate from anywhere in the addon
1296 * dependency tree, without needing to care/specify where.
1297 * * We can more easily test parts of the framework by mocking out container
1298 * entries instead of dealing with hardcoding dependencies
1299 *
1300 * @package metal
1301 * @since 0.1.0
1302 */
1303class Container {
1304 constructor() {
1305 /**
1306 * Manual registrations that should override resolver retrieved values
1307 */
1308 this.registry = {};
1309 /**
1310 * An array of resolvers used to retrieve container members. Resolvers are
1311 * tried in order, first to find the member wins. Normally, each addon will
1312 * supply it's own resolver, allowing for addon order and precedence when
1313 * looking up container entries.
1314 */
1315 this.resolvers = [];
1316 /**
1317 * Internal cache of lookup values
1318 */
1319 this.lookups = {};
1320 /**
1321 * Default options for container entries. Keyed on specifier or type. See
1322 * ContainerOptions.
1323 */
1324 this.options = {
1325 app: { singleton: true },
1326 'app:application': { singleton: false },
1327 action: { singleton: false },
1328 config: { singleton: false },
1329 initializer: { singleton: false },
1330 'orm-adapter': { singleton: true, fallbacks: ['orm-adapter:application'] },
1331 model: { singleton: false },
1332 parser: { singleton: true, fallbacks: ['parser:application'] },
1333 serializer: { singleton: true, fallbacks: ['serializer:application'] },
1334 service: { singleton: true },
1335 view: { singleton: true }
1336 };
1337 }
1338 /**
1339 * Take a top level bundle loader and load it into this container
1340 */
1341 loadBundle(loader) {
1342 this.loader = loader;
1343 crawl(loader, this.loadBundleScope.bind(this), {
1344 order: 'bfs',
1345 getChildren(loader) {
1346 return Array.from(loader.children.values());
1347 }
1348 });
1349 }
1350 /**
1351 * Load a bundle scope into the container. A bundle scope typically
1352 * corresponds to an addon. Each bundle scope can provide it's own resolver to
1353 * tell the consuming app how to look things up within the bundle scope. If
1354 * no resolver is supplied, Denali will use the default Denali resolver.
1355 */
1356 loadBundleScope(loader) {
1357 let LoaderResolver;
1358 try {
1359 LoaderResolver = loader.load('resolver');
1360 }
1361 catch (e) {
1362 LoaderResolver = resolver_1.default;
1363 }
1364 let resolver = new LoaderResolver(loader);
1365 this.resolvers.push(resolver);
1366 loader.resolver = resolver;
1367 }
1368 /**
1369 * Add a manual registration that will take precedence over any resolved
1370 * lookups.
1371 *
1372 * @since 0.1.0
1373 */
1374 register(specifier, entry, options) {
1375 this.registry[specifier] = entry;
1376 if (options) {
1377 lodash_1.forOwn(options, (value, key) => {
1378 this.setOption(specifier, key, value);
1379 });
1380 }
1381 }
1382 lookup(specifier, options = {}) {
1383 // Raw lookups skip caching and singleton behavior
1384 if (options.raw) {
1385 let entry = this.lookupRaw(specifier);
1386 if (entry === false && !options.loose) {
1387 throw new ContainerEntryNotFound(specifier, this.registry, this.resolvers);
1388 }
1389 return entry;
1390 }
1391 // I can haz cache hit plz?
1392 if (this.lookups[specifier]) {
1393 return this.lookups[specifier];
1394 }
1395 // Not in cache - first lookup. Get the underlying value first
1396 let entry = this.lookupRaw(specifier);
1397 // If that works - handle singletons, cache, and call it good
1398 if (entry !== false) {
1399 entry = this.instantiateSingletons(specifier, entry);
1400 this.lookups[specifier] = entry;
1401 return entry;
1402 }
1403 // Not in cache and no underlying entry found. Can we fallback?
1404 let fallbacks = this.getOption(specifier, 'fallbacks').slice(0);
1405 let fallback;
1406 while ((fallback = fallbacks.shift()) && (fallback !== specifier)) {
1407 entry = this.lookup(fallback, options);
1408 if (entry) {
1409 break;
1410 }
1411 }
1412 if (entry !== false) {
1413 // No singleton instantiation here - the recursion into lookup should
1414 // handle that
1415 this.lookups[specifier] = entry;
1416 return entry;
1417 }
1418 if (options.loose) {
1419 return false;
1420 }
1421 throw new ContainerEntryNotFound(specifier, this.registry, this.resolvers);
1422 }
1423 instantiateSingletons(specifier, entry) {
1424 // Instantiate if it's a singleton
1425 let singleton = this.getOption(specifier, 'singleton');
1426 if (singleton) {
1427 let Class = entry;
1428 assert(typeof Class === 'function', strings_1.default.ContainerEntryNotAConstructor(specifier, Class));
1429 return new Class();
1430 }
1431 return entry;
1432 }
1433 /**
1434 * Recursive lookup method that takes a specifier and fallback specifiers.
1435 * Checks manual registrations first, then iterates through each resolver. If
1436 * the entry is still not found, it recurses through the fallback options
1437 * before ultimatley throwing (or returning false if loose: true)
1438 */
1439 lookupRaw(specifier) {
1440 // Manual registrations take precedence
1441 let entry = this.registry[specifier];
1442 // Try each resolver in order
1443 if (!entry) {
1444 lodash_1.forEach(this.resolvers, (resolver) => {
1445 entry = resolver.retrieve(specifier);
1446 if (entry) {
1447 return false;
1448 }
1449 });
1450 }
1451 return entry == null ? false : entry;
1452 }
1453 /**
1454 * Lookup all the entries for a given type in the container. This will ask
1455 * all resolvers to eagerly load all classes for this type. Returns an object
1456 * whose keys are container specifiers and values are the looked up values
1457 * for those specifiers.
1458 *
1459 * @since 0.1.0
1460 */
1461 lookupAll(type) {
1462 let entries = this.availableForType(type);
1463 let values = entries.map((entry) => this.lookup(`${type}:${entry}`));
1464 return lodash_1.zipObject(entries, values);
1465 }
1466 /**
1467 * Returns an array of entry names for all entries under this type.
1468 *
1469 * @since 0.1.0
1470 */
1471 availableForType(type) {
1472 let registrations = Object.keys(this.registry).filter((specifier) => {
1473 return specifier.startsWith(type);
1474 });
1475 let resolved = this.resolvers.reverse().reduce((entries, resolver) => {
1476 return entries.concat(resolver.availableForType(type));
1477 }, []);
1478 return lodash_1.uniq(registrations.concat(resolved)).map((specifier) => specifier.split(':')[1]);
1479 }
1480 /**
1481 * Return the value for the given option on the given specifier. Specifier
1482 * may be a full specifier or just a type.
1483 *
1484 * @since 0.1.0
1485 */
1486 getOption(specifier, optionName) {
1487 let [type] = specifier.split(':');
1488 let options = lodash_1.defaults(this.options[specifier], this.options[type], DEFAULT_OPTIONS);
1489 return options[optionName];
1490 }
1491 /**
1492 * Set the option for the given specifier or type.
1493 *
1494 * @since 0.1.0
1495 */
1496 setOption(specifier, optionName, value) {
1497 if (!this.options[specifier]) {
1498 this.options[specifier] = Object.assign({}, DEFAULT_OPTIONS);
1499 }
1500 this.options[specifier][optionName] = value;
1501 }
1502 /**
1503 * Clear any cached lookups for this specifier. You probably don't want to
1504 * use this. The only significant use case is for testing to allow test
1505 * containers to override an already looked up value.
1506 */
1507 clearCache(specifier) {
1508 delete this.lookups[specifier];
1509 }
1510 /**
1511 * Empties the entire container, including removing all resolvers and the
1512 * loader, as well as emptying all caches. The primary use case is for
1513 * unit testing, when you want a clean slate environment to selectively
1514 * add things back to.
1515 */
1516 clear() {
1517 this.lookups = {};
1518 this.registry = {};
1519 this.resolvers = [];
1520 this.loader = null;
1521 }
1522 /**
1523 * Given container-managed singletons a chance to cleanup on application
1524 * shutdown
1525 *
1526 * @since 0.1.0
1527 */
1528 teardown() {
1529 lodash_1.forEach(this.lookups, (instance) => {
1530 if (typeof instance.teardown === 'function') {
1531 instance.teardown();
1532 }
1533 });
1534 }
1535}
1536exports.Container = Container;
1537class ContainerEntryNotFound extends Error {
1538 constructor(specifier, registry, resolvers) {
1539 let message = strings_1.default.ContainerEntryNotFound(specifier, Object.keys(registry), resolvers.map((r) => r.name));
1540 super(message);
1541 }
1542}
1543// The exports here are unusual - rather than exporting the Container class, we
1544// actually instantiate the container and export that. This is because all
1545// Denali code is loaded via a bundle, and there is only one container instance
1546// per bundle. Instantiating it here allows us to treat the container as
1547// effectively a global variable, which allows for *much* more convenient use
1548// (otherwise, the container would need to control instantiation of every object
1549// to ensure each object could get a reference to the container instance).
1550const container = new Container();
1551exports.default = container;
1552exports.lookup = container.lookup.bind(container);
1553
1554});
1555
1556
1557
1558// lib/metal/instrumentation.js
1559//========================================
1560
1561loader.add('/lib/metal/instrumentation.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
1562
1563"use strict";
1564Object.defineProperty(exports, "__esModule", { value: true });
1565const EventEmitter = require("events");
1566const lodash_1 = require("lodash");
1567/**
1568 * The Instrumentation class is a low level class for instrumenting your app's
1569 * code. It allows you to listen to framework level profiling events, as well
1570 * as creating and firing your own such events.
1571 *
1572 * For example, if you wanted to instrument how long a particular action was
1573 * taking:
1574 *
1575 * import { Instrumentation, Action } from 'denali';
1576 * export default class MyAction extends Action {
1577 * respond() {
1578 * let Post = this.modelFor('post');
1579 * return Instrumentation.instrument('post lookup', { currentUser: this.user.id }, () => {
1580 * Post.find({ user: this.user });
1581 * });
1582 * }
1583 * }
1584 *
1585 * @package metal
1586 */
1587class InstrumentationEvent {
1588 constructor(eventName, data) {
1589 this.eventName = eventName;
1590 this.data = data;
1591 this.startTime = process.hrtime();
1592 }
1593 /**
1594 * Subscribe to be notified when a particular instrumentation block completes.
1595 */
1596 static subscribe(eventName, callback) {
1597 this._emitter.on(eventName, callback);
1598 }
1599 /**
1600 * Unsubscribe from being notified when a particular instrumentation block completes.
1601 */
1602 static unsubscribe(eventName, callback) {
1603 this._emitter.removeListener(eventName, callback);
1604 }
1605 /**
1606 * Run the supplied function, timing how long it takes to complete. If the function returns a
1607 * promise, the timer waits until that promise resolves. Returns a promise that resolves with the
1608 * return value of the supplied function. Fires an event with the given event name and event data
1609 * (the function result is provided as well).
1610 */
1611 static instrument(eventName, data) {
1612 return new InstrumentationEvent(eventName, data);
1613 }
1614 /**
1615 * Emit an InstrumentationEvent to subscribers
1616 */
1617 static emit(eventName, event) {
1618 this._emitter.emit(eventName, event);
1619 }
1620 /**
1621 * Finish this event. Records the duration, and fires an event to any subscribers. Any data
1622 * provided here is merged with any previously provided data.
1623 */
1624 finish(data) {
1625 this.duration = process.hrtime(this.startTime)[1];
1626 this.data = lodash_1.merge({}, this.data, data);
1627 InstrumentationEvent.emit(this.eventName, this);
1628 }
1629}
1630/**
1631 * The internal event emitter used for notifications
1632 */
1633InstrumentationEvent._emitter = new EventEmitter();
1634exports.default = InstrumentationEvent;
1635
1636});
1637
1638
1639
1640// lib/metal/mixin.js
1641//========================================
1642
1643loader.add('/lib/metal/mixin.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
1644
1645"use strict";
1646Object.defineProperty(exports, "__esModule", { value: true });
1647const assert = require("assert");
1648/**
1649 * ES6 classes don't provide any native syntax or support for compositional
1650 * mixins. This helper method provides that support:
1651 *
1652 * import { mixin } from 'denali';
1653 * import MyMixin from '../mixins/my-mixin';
1654 * import ApplicationAction from './application';
1655 *
1656 * export default class MyAction extends mixin(ApplicationAction, MyMixin) {
1657 * // ...
1658 * }
1659 *
1660 * Objects that extend from Denali's Object class automatically get a static
1661 * `mixin` method to make the syntax a bit more familiar:
1662 *
1663 * export default class MyAction extends ApplicationAction.mixin(MyMixin) {
1664 *
1665 * ## How it works
1666 *
1667 * Since ES6 classes are based on prototype chains, and protoype chains are
1668 * purely linear (you can't have two prototypes of a single object), we
1669 * implement mixins by creating anonymous intermediate subclasses for each
1670 * applied mixin.
1671 *
1672 * Mixins are defined as factory functions that take a base class and extend it
1673 * with their own mixin properties/methods. When these mixin factory functions
1674 * are applied, they are called in order, with the result of the last mixin
1675 * feeding into the base class of the next mixin factory.
1676 *
1677 * ## Use sparingly!
1678 *
1679 * Mixins can be useful in certain circumstances, but use them sparingly. They
1680 * add indirection that can be surprising or confusing to later developers who
1681 * encounter the code. Wherever possible, try to find alternatives that make
1682 * the various dependencies of your code clear.
1683 *
1684 * @package metal
1685 * @since 0.1.0
1686 */
1687function mixin(baseClass, ...mixins) {
1688 return mixins.reduce((currentBase, mixinFactory) => {
1689 let appliedClass = mixinFactory._factory(currentBase, ...mixinFactory._args);
1690 assert(typeof appliedClass === 'function', `Invalid mixin (${appliedClass}) - did you forget to return your mixin class from the createMixin method?`);
1691 return appliedClass;
1692 }, baseClass);
1693}
1694exports.default = mixin;
1695/**
1696 * Creates a mixin factory function wrapper. These wrapper functions have the
1697 * special property that they can be invoked an arbitrary number of times, and
1698 * each time will cache the arguments to be handed off to the actual factory
1699 * function.
1700 *
1701 * This is useful to allow per-use options for your mixin. For example:
1702 *
1703 * class ProtectedAction extends Action.mixin(authenticate({ ... })) {
1704 *
1705 * In that example, the optons object provided to the `authenticate` mixin
1706 * function will be cached, and once the mixin factory function is invoked, it
1707 * will be provided as an additional argument:
1708 *
1709 * createMixin((BaseClass, options) => {
1710 *
1711 * @package metal
1712 * @since 0.1.0
1713 */
1714function createMixin(mixinFactory) {
1715 let cacheMixinArguments = function (...args) {
1716 cacheMixinArguments._args.push(...args);
1717 return cacheMixinArguments;
1718 };
1719 cacheMixinArguments._args = [];
1720 cacheMixinArguments._factory = mixinFactory;
1721 return cacheMixinArguments;
1722}
1723exports.createMixin = createMixin;
1724
1725});
1726
1727
1728
1729// lib/metal/object.js
1730//========================================
1731
1732loader.add('/lib/metal/object.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
1733
1734"use strict";
1735Object.defineProperty(exports, "__esModule", { value: true });
1736const mixin_1 = require("./mixin");
1737/**
1738 * The base object class for Denali classes. Adds mixin support and a stubbed
1739 * `teardown` method.
1740 *
1741 * @package metal
1742 * @since 0.1.0
1743 */
1744class DenaliObject {
1745 /**
1746 * Apply mixins using this class as the base class. Pure syntactic sugar for
1747 * the `mixin` helper.
1748 */
1749 static mixin(...mixins) {
1750 return mixin_1.default(this, ...mixins);
1751 }
1752 /**
1753 * A hook invoked when an application is torn down. Only invoked on
1754 * singletons stored in the container.
1755 */
1756 teardown() {
1757 // Default is no-op
1758 }
1759}
1760exports.default = DenaliObject;
1761
1762});
1763
1764
1765
1766// lib/metal/resolver.js
1767//========================================
1768
1769loader.add('/lib/metal/resolver.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
1770
1771"use strict";
1772Object.defineProperty(exports, "__esModule", { value: true });
1773const lodash_1 = require("lodash");
1774const path = require("path");
1775const inflection_1 = require("inflection");
1776const assert = require("assert");
1777const createDebug = require("debug");
1778class Resolver {
1779 constructor(loader) {
1780 /**
1781 * The internal cache of available references
1782 */
1783 this.registry = new Map();
1784 assert(loader, 'You must supply a loader that the resolver should use to load from');
1785 this.name = loader.pkgName;
1786 this.debug = createDebug(`denali:resolver:${this.name}`);
1787 this.loader = loader;
1788 }
1789 /**
1790 * Manually add a member to this resolver. Manually registered members take
1791 * precedence over any retrieved from the filesystem. This same pattern
1792 * exists at the container level, but having it here allows an addon to
1793 * specify a manual override _for it's own scope_, but not necessarily force
1794 * it onto the consuming application
1795 *
1796 * @since 0.1.0
1797 */
1798 register(specifier, value) {
1799 assert(specifier.includes(':'), 'Container specifiers must be in "type:entry" format');
1800 this.registry.set(specifier, value);
1801 }
1802 /**
1803 * Fetch the member matching the given parsedName. First checks for any
1804 * manually registered members, then falls back to type specific retrieve
1805 * methods that typically find the matching file on the filesystem.
1806 *
1807 * @since 0.1.0
1808 */
1809 retrieve(specifier) {
1810 assert(specifier.includes(':'), 'Container specifiers must be in "type:entry" format');
1811 this.debug(`retrieving ${specifier}`);
1812 let [type, entry] = specifier.split(':');
1813 if (this.registry.has(specifier)) {
1814 this.debug(`cache hit, returning cached value`);
1815 return this.registry.get(specifier);
1816 }
1817 let retrieveMethod = this[`retrieve${lodash_1.upperFirst(lodash_1.camelCase(type))}`];
1818 if (!retrieveMethod) {
1819 retrieveMethod = this.retrieveOther;
1820 }
1821 this.debug(`retrieving via retrieve${lodash_1.upperFirst(lodash_1.camelCase(type))}`);
1822 let result = retrieveMethod.call(this, type, entry);
1823 result = result && result.default || result;
1824 this.debug('retrieved %o', result);
1825 return result;
1826 }
1827 _retrieve(type, entry, relativepath) {
1828 this.debug(`attempting to retrieve ${type}:${entry} at ${relativepath} from ${this.name}`);
1829 return this.loader.loadRelative('/', relativepath, lodash_1.constant(false));
1830 }
1831 /**
1832 * Unknown types are assumed to exist underneath the `app/` folder
1833 */
1834 retrieveOther(type, entry) {
1835 return this._retrieve(type, entry, path.join('/app', inflection_1.pluralize(type), entry));
1836 }
1837 /**
1838 * App files are found in `app/*`
1839 */
1840 retrieveApp(type, entry) {
1841 return this._retrieve(type, entry, path.join('/app', entry));
1842 }
1843 /**
1844 * Config files are found in `config/`
1845 */
1846 retrieveConfig(type, entry) {
1847 return this._retrieve(type, entry, path.join('/config', entry));
1848 }
1849 /**
1850 * Initializer files are found in `config/initializers/`
1851 */
1852 retrieveInitializer(type, entry) {
1853 return this._retrieve(type, entry, path.join('/config', 'initializers', entry));
1854 }
1855 /**
1856 * Returns an array of entry names that are available from this resolver for
1857 * the given type.
1858 *
1859 * @since 0.1.0
1860 */
1861 availableForType(type) {
1862 let registeredForType = [];
1863 this.registry.forEach((entry, specifier) => {
1864 if (specifier.split(':')[0] === type) {
1865 registeredForType.push(specifier);
1866 }
1867 });
1868 let availableMethod = this[`availableFor${lodash_1.upperFirst(lodash_1.camelCase(type))}`];
1869 if (!availableMethod) {
1870 availableMethod = this.availableForOther;
1871 }
1872 let entries = availableMethod.call(this, type);
1873 let resolvedEntries = entries.map((entry) => `${type}:${entry}`);
1874 return lodash_1.uniq(registeredForType.sort().concat(resolvedEntries.sort()));
1875 }
1876 _availableForType(prefix) {
1877 let matchingFactories = Array.from(this.loader.factories.keys()).filter((moduleName) => {
1878 return moduleName.startsWith(prefix);
1879 });
1880 return matchingFactories.map((factoryPath) => factoryPath.substring(prefix.length + 1));
1881 }
1882 /**
1883 * Unknown types are assumed to exist in the `app/` folder
1884 */
1885 availableForOther(type) {
1886 return this._availableForType(path.join('/app', inflection_1.pluralize(type)));
1887 }
1888 /**
1889 * App files are found in `app/*`
1890 */
1891 availableForApp(type, entry) {
1892 return this._availableForType('/app');
1893 }
1894 /**
1895 * Config files are found in the `config/` folder. Initializers are _not_ included in this group
1896 */
1897 availableForConfig(type) {
1898 return this._availableForType('/config');
1899 }
1900 /**
1901 * Initializers files are found in the `config/initializers/` folder
1902 */
1903 availableForInitializer(type) {
1904 return this._availableForType(path.join('/config', 'initializers'));
1905 }
1906}
1907exports.default = Resolver;
1908
1909});
1910
1911
1912
1913// lib/parse/json-api.js
1914//========================================
1915
1916loader.add('/lib/parse/json-api.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
1917
1918"use strict";
1919Object.defineProperty(exports, "__esModule", { value: true });
1920const lodash_1 = require("lodash");
1921const assert = require("assert");
1922const json_1 = require("./json");
1923const errors_1 = require("../runtime/errors");
1924const set_if_not_empty_1 = require("../utils/set-if-not-empty");
1925const inflection_1 = require("inflection");
1926/**
1927 * Parses incoming request bodies according to the JSON-API specification. For
1928 * incoming payloads with `included` arrays, the primary `data` is returned
1929 * under the `body` key, and `included` is moved to it's own property.
1930 *
1931 * @package parse
1932 * @since 0.1.0
1933 */
1934class JSONAPIParser extends json_1.default {
1935 constructor() {
1936 super(...arguments);
1937 /**
1938 * The media type for JSON-API requests. If the incoming request doesn't have
1939 * this Content Type, the parser will immediately render a 400 Bad Request response
1940 */
1941 this.type = 'application/vnd.api+json';
1942 }
1943 async parse(request) {
1944 let body = await this.bufferAndParseBody(request);
1945 let result = {
1946 query: request.query,
1947 headers: request.headers,
1948 params: request.params
1949 };
1950 if (!request.hasBody) {
1951 return result;
1952 }
1953 try {
1954 assert(request.getHeader('content-type') === 'application/vnd.api+json', 'Invalid content type - must have `application/vnd.api+json` as the request content type');
1955 assert(body.data, 'Invalid JSON-API document (missing top level `data` object - see http://jsonapi.org/format/#document-top-level)');
1956 let parseResource = this.parseResource.bind(this);
1957 if (body.data) {
1958 if (!lodash_1.isArray(body.data)) {
1959 result.body = parseResource(body.data);
1960 }
1961 else {
1962 result.body = body.data.map(parseResource);
1963 }
1964 }
1965 if (body.included) {
1966 result.included = body.included.map(parseResource);
1967 }
1968 return result;
1969 }
1970 catch (e) {
1971 if (e.name === 'AssertionError') {
1972 throw new errors_1.default.BadRequest(e.message);
1973 }
1974 throw e;
1975 }
1976 }
1977 /**
1978 * Parse a single resource object from a JSONAPI document. The resource
1979 * object could come from the top level `data` payload, or from the
1980 * sideloaded `included` records.
1981 *
1982 * @since 0.1.0
1983 */
1984 parseResource(resource) {
1985 let parsedResource = {};
1986 set_if_not_empty_1.default(parsedResource, 'id', this.parseId(resource.id));
1987 Object.assign(parsedResource, this.parseAttributes(resource.attributes));
1988 Object.assign(parsedResource, this.parseRelationships(resource.relationships));
1989 return parsedResource;
1990 }
1991 /**
1992 * Parse a resource object id
1993 *
1994 * @since 0.1.0
1995 */
1996 parseId(id) {
1997 return id;
1998 }
1999 /**
2000 * Parse a resource object's type string
2001 *
2002 * @since 0.1.0
2003 */
2004 parseType(type) {
2005 return inflection_1.singularize(type);
2006 }
2007 /**
2008 * Parse a resource object's attributes. By default, this converts from the
2009 * JSONAPI recommended dasheried keys to camelCase.
2010 *
2011 * @since 0.1.0
2012 */
2013 parseAttributes(attrs) {
2014 return lodash_1.mapKeys(attrs, (value, key) => {
2015 return lodash_1.camelCase(key);
2016 });
2017 }
2018 /**
2019 * Parse a resource object's relationships. By default, this converts from
2020 * the JSONAPI recommended dasheried keys to camelCase.
2021 *
2022 * @since 0.1.0
2023 */
2024 parseRelationships(relationships) {
2025 return lodash_1.mapKeys(relationships, (value, key) => {
2026 return lodash_1.camelCase(key);
2027 });
2028 }
2029}
2030exports.default = JSONAPIParser;
2031
2032});
2033
2034
2035
2036// lib/parse/json.js
2037//========================================
2038
2039loader.add('/lib/parse/json.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
2040
2041"use strict";
2042Object.defineProperty(exports, "__esModule", { value: true });
2043const parser_1 = require("./parser");
2044const body_parser_1 = require("body-parser");
2045const bluebird_1 = require("bluebird");
2046/**
2047 * Parses incoming request bodies as JSON payloads.
2048 *
2049 * @package parse
2050 * @since 0.1.0
2051 */
2052class JSONParser extends parser_1.default {
2053 constructor() {
2054 super(...arguments);
2055 /**
2056 * When set to true, then deflated (compressed) bodies will be inflated; when
2057 * false, deflated bodies are rejected. Defaults to true.
2058 *
2059 * @since 0.1.0
2060 */
2061 this.inflate = true;
2062 /**
2063 * Controls the maximum request body size. If this is a number, then the
2064 * value specifies the number of bytes; if it is a string, the value is
2065 * passed to the bytes library for parsing. Defaults to '100kb'.
2066 *
2067 * @since 0.1.0
2068 */
2069 this.limit = '100kb';
2070 /**
2071 * When set to true, will only accept arrays and objects; when false will
2072 * accept anything JSON.parse accepts. Defaults to true.
2073 *
2074 * @since 0.1.0
2075 */
2076 this.strict = true;
2077 /**
2078 * The type option is used to determine what media type the middleware will
2079 * parse. This option can be a function or a string. If a string, type option
2080 * is passed directly to the type-is library and this can be an extension
2081 * name (like json), a mime type (like application/json), or a mime type with
2082 * a wildcard. If a function, the type option is called as fn(req) and the
2083 * request is parsed if it returns a truthy value. Defaults to
2084 * application/json.
2085 *
2086 * @since 0.1.0
2087 */
2088 this.type = 'application/json';
2089 }
2090 async bufferAndParseBody(request) {
2091 await bluebird_1.fromNode((cb) => {
2092 if (!this.jsonParserMiddleware) {
2093 this.jsonParserMiddleware = body_parser_1.json({
2094 inflate: this.inflate,
2095 limit: this.limit,
2096 reviver: this.reviver,
2097 strict: this.strict,
2098 type: this.type,
2099 verify: this.verify
2100 });
2101 }
2102 this.jsonParserMiddleware(request.incomingMessage, {}, cb);
2103 });
2104 return request.incomingMessage.body;
2105 }
2106 async parse(request) {
2107 let body = await this.bufferAndParseBody(request);
2108 return {
2109 body,
2110 query: request.query,
2111 headers: request.headers,
2112 params: request.params
2113 };
2114 }
2115}
2116exports.default = JSONParser;
2117
2118});
2119
2120
2121
2122// lib/parse/parser.js
2123//========================================
2124
2125loader.add('/lib/parse/parser.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
2126
2127"use strict";
2128Object.defineProperty(exports, "__esModule", { value: true });
2129const object_1 = require("../metal/object");
2130/**
2131 * Denali's Parsers are responsible for taking the incoming request body of an
2132 * HTTP request and transforming it into a consistent object structure that can
2133 * be used by Actions.
2134 *
2135 * For example, if your app uses JSON-API, you can use the JSON-API parser to
2136 * transform the incoming JSON-API request body structure into something easier
2137 * to work with in your Action layer.
2138 *
2139 * Other examples of common tasks include parsing and type casting query
2140 * params, and transforming keys (i.e. kebab-case to camelCase).
2141 *
2142 * @package parse
2143 * @since 0.1.0
2144 */
2145class Parser extends object_1.default {
2146}
2147exports.default = Parser;
2148
2149});
2150
2151
2152
2153// lib/render/json-api.js
2154//========================================
2155
2156loader.add('/lib/render/json-api.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
2157
2158"use strict";
2159Object.defineProperty(exports, "__esModule", { value: true });
2160const lodash_1 = require("lodash");
2161const assert = require("assert");
2162const path = require("path");
2163const inflection_1 = require("inflection");
2164const serializer_1 = require("./serializer");
2165const container_1 = require("../metal/container");
2166const bluebird_1 = require("bluebird");
2167const set_if_not_empty_1 = require("../utils/set-if-not-empty");
2168/**
2169 * Renders the payload according to the JSONAPI 1.0 spec, including related
2170 * resources, included records, and support for meta and links.
2171 *
2172 * @package data
2173 * @since 0.1.0
2174 */
2175class JSONAPISerializer extends serializer_1.default {
2176 constructor() {
2177 super(...arguments);
2178 /**
2179 * The default content type to use for any responses rendered by this serializer.
2180 *
2181 * @since 0.1.0
2182 */
2183 this.contentType = 'application/vnd.api+json';
2184 }
2185 /**
2186 * Take a response body (a model, an array of models, or an Error) and render
2187 * it as a JSONAPI compliant document
2188 *
2189 * @since 0.1.0
2190 */
2191 async serialize(body, action, options) {
2192 let context = {
2193 action,
2194 body,
2195 options,
2196 document: {}
2197 };
2198 await this.renderPrimary(context);
2199 await this.renderIncluded(context);
2200 this.renderMeta(context);
2201 this.renderLinks(context);
2202 this.renderVersion(context);
2203 this.dedupeIncluded(context);
2204 return context.document;
2205 }
2206 /**
2207 * Render the primary payload for a JSONAPI document (either a model or array
2208 * of models).
2209 *
2210 * @since 0.1.0
2211 */
2212 async renderPrimary(context) {
2213 let payload = context.body;
2214 if (lodash_1.isArray(payload)) {
2215 await this.renderPrimaryArray(context, payload);
2216 }
2217 else {
2218 await this.renderPrimaryObject(context, payload);
2219 }
2220 }
2221 /**
2222 * Render the primary data for the document, either a single Model or a
2223 * single Error.
2224 *
2225 * @since 0.1.0
2226 */
2227 async renderPrimaryObject(context, payload) {
2228 if (payload instanceof Error) {
2229 context.document.errors = [await this.renderError(context, payload)];
2230 }
2231 else {
2232 context.document.data = await this.renderRecord(context, payload);
2233 }
2234 }
2235 /**
2236 * Render the primary data for the document, either an array of Models or
2237 * Errors
2238 *
2239 * @since 0.1.0
2240 */
2241 async renderPrimaryArray(context, payload) {
2242 if (payload[0] instanceof Error) {
2243 context.document.errors = await bluebird_1.map(payload, async (error) => {
2244 assert(error instanceof Error, 'You passed a mixed array of errors and models to the JSON-API serializer. The JSON-API spec does not allow for both `data` and `errors` top level objects in a response');
2245 return await this.renderError(context, error);
2246 });
2247 }
2248 else {
2249 context.document.data = await bluebird_1.map(payload, async (record) => {
2250 assert(!(record instanceof Error), 'You passed a mixed array of errors and models to the JSON-API serializer. The JSON-API spec does not allow for both `data` and `errors` top level objects in a response');
2251 return await this.renderRecord(context, record);
2252 });
2253 }
2254 }
2255 /**
2256 * Render any included records supplied by the options into the top level of
2257 * the document
2258 *
2259 * @since 0.1.0
2260 */
2261 async renderIncluded(context) {
2262 if (context.options.included) {
2263 assert(lodash_1.isArray(context.options.included), 'included records must be passed in as an array');
2264 context.document.included = await bluebird_1.map(context.options.included, async (includedRecord) => {
2265 return await this.renderRecord(context, includedRecord);
2266 });
2267 }
2268 }
2269 /**
2270 * Render top level meta object for a document. Default uses meta supplied in
2271 * options call to res.render().
2272 *
2273 * @since 0.1.0
2274 */
2275 renderMeta(context) {
2276 if (context.options.meta) {
2277 context.document.meta = context.options.meta;
2278 }
2279 }
2280 /**
2281 * Render top level links object for a document. Defaults to the links
2282 * supplied in options.
2283 *
2284 * @since 0.1.0
2285 */
2286 renderLinks(context) {
2287 if (context.options.links) {
2288 context.document.links = context.options.links;
2289 }
2290 }
2291 /**
2292 * Render the version of JSONAPI supported.
2293 *
2294 * @since 0.1.0
2295 */
2296 renderVersion(context) {
2297 context.document.jsonapi = {
2298 version: '1.0'
2299 };
2300 }
2301 /**
2302 * Render the supplied record as a resource object.
2303 *
2304 * @since 0.1.0
2305 */
2306 async renderRecord(context, record) {
2307 assert(record, `Cannot serialize ${record}. You supplied ${record} instead of a Model instance.`);
2308 let serializedRecord = {
2309 type: inflection_1.pluralize(record.modelName),
2310 id: record.id
2311 };
2312 assert(serializedRecord.id != null, `Attempted to serialize a record (${record}) without an id, but the JSON-API spec requires all resources to have an id.`);
2313 set_if_not_empty_1.default(serializedRecord, 'attributes', this.attributesForRecord(context, record));
2314 set_if_not_empty_1.default(serializedRecord, 'relationships', await this.relationshipsForRecord(context, record));
2315 set_if_not_empty_1.default(serializedRecord, 'links', this.linksForRecord(context, record));
2316 set_if_not_empty_1.default(serializedRecord, 'meta', this.metaForRecord(context, record));
2317 return serializedRecord;
2318 }
2319 /**
2320 * Returns the JSONAPI attributes object representing this record's
2321 * relationships
2322 *
2323 * @since 0.1.0
2324 */
2325 attributesForRecord(context, record) {
2326 let serializedAttributes = {};
2327 let attributes = this.attributesToSerialize(context.action, context.options);
2328 attributes.forEach((attributeName) => {
2329 let key = this.serializeAttributeName(context, attributeName);
2330 let rawValue = record[attributeName];
2331 if (!lodash_1.isUndefined(rawValue)) {
2332 let value = this.serializeAttributeValue(context, rawValue, key, record);
2333 serializedAttributes[key] = value;
2334 }
2335 });
2336 return serializedAttributes;
2337 }
2338 /**
2339 * The JSONAPI spec recommends (but does not require) that property names be
2340 * dasherized. The default implementation of this serializer therefore does
2341 * that, but you can override this method to use a different approach.
2342 *
2343 * @since 0.1.0
2344 */
2345 serializeAttributeName(context, name) {
2346 return lodash_1.kebabCase(name);
2347 }
2348 /**
2349 * Take an attribute value and return the serialized value. Useful for
2350 * changing how certain types of values are serialized, i.e. Date objects.
2351 *
2352 * The default implementation returns the attribute's value unchanged.
2353 *
2354 * @since 0.1.0
2355 */
2356 serializeAttributeValue(context, value, key, record) {
2357 return value;
2358 }
2359 /**
2360 * Returns the JSONAPI relationships object representing this record's
2361 * relationships
2362 *
2363 * @since 0.1.0
2364 */
2365 async relationshipsForRecord(context, record) {
2366 let serializedRelationships = {};
2367 let relationships = this.relationshipsToSerialize(context.action, context.options);
2368 // The result of this.relationships is a whitelist of which relationships should be serialized,
2369 // and the configuration for their serialization
2370 let relationshipNames = Object.keys(relationships);
2371 for (let name of relationshipNames) {
2372 let config = relationships[name];
2373 let key = config.key || this.serializeRelationshipName(context, name);
2374 let descriptor = record.constructor.schema[name];
2375 assert(descriptor, `You specified a '${name}' relationship in your ${record.modelName} serializer, but no such relationship is defined on the ${record.modelName} model`);
2376 serializedRelationships[key] = await this.serializeRelationship(context, name, config, descriptor, record);
2377 }
2378 return serializedRelationships;
2379 }
2380 /**
2381 * Convert the relationship name to it's "over-the-wire" format. Defaults to
2382 * dasherizing it.
2383 *
2384 * @since 0.1.0
2385 */
2386 serializeRelationshipName(context, name) {
2387 return lodash_1.kebabCase(name);
2388 }
2389 /**
2390 * Takes the serializer config and the model's descriptor for a relationship,
2391 * and returns the serialized relationship object. Also sideloads any full
2392 * records if the relationship is so configured.
2393 *
2394 * @since 0.1.0
2395 */
2396 async serializeRelationship(context, name, config, descriptor, record) {
2397 let relationship = {};
2398 set_if_not_empty_1.default(relationship, 'links', this.linksForRelationship(context, name, config, descriptor, record));
2399 set_if_not_empty_1.default(relationship, 'meta', this.metaForRelationship(context, name, config, descriptor, record));
2400 set_if_not_empty_1.default(relationship, 'data', await this.dataForRelationship(context, name, config, descriptor, record));
2401 return relationship;
2402 }
2403 /**
2404 * Returns the serialized form of the related Models for the given record and
2405 * relationship.
2406 *
2407 * @since 0.1.0
2408 */
2409 async dataForRelationship(context, name, config, descriptor, record) {
2410 let relatedData = await record.getRelated(name);
2411 if (descriptor.mode === 'hasMany') {
2412 return await bluebird_1.map(relatedData, async (relatedRecord) => {
2413 return await this.dataForRelatedRecord(context, name, relatedRecord, config, descriptor, record);
2414 });
2415 }
2416 return await this.dataForRelatedRecord(context, name, relatedData, config, descriptor, record);
2417 }
2418 /**
2419 * Given a related record, return the resource object for that record, and
2420 * sideload the record as well.
2421 *
2422 * @since 0.1.0
2423 */
2424 async dataForRelatedRecord(context, name, relatedRecord, config, descriptor, record) {
2425 await this.includeRecord(context, name, relatedRecord, config, descriptor);
2426 return {
2427 type: inflection_1.pluralize(relatedRecord.modelName),
2428 id: relatedRecord.id
2429 };
2430 }
2431 /**
2432 * Takes a relationship descriptor and the record it's for, and returns any
2433 * links for that relationship for that record. I.e. '/books/1/author'
2434 *
2435 * @since 0.1.0
2436 */
2437 linksForRelationship(context, name, config, descriptor, record) {
2438 let recordLinks = this.linksForRecord(context, record);
2439 let recordURL;
2440 if (recordLinks) {
2441 if (typeof recordLinks.self === 'string') {
2442 recordURL = recordLinks.self;
2443 }
2444 else {
2445 recordURL = recordLinks.self.href;
2446 }
2447 return {
2448 self: path.join(recordURL, `relationships/${name}`),
2449 related: path.join(recordURL, name)
2450 };
2451 }
2452 return null;
2453 }
2454 /**
2455 * Returns any meta for a given relationship and record. No meta included by
2456 * default.
2457 *
2458 * @since 0.1.0
2459 */
2460 metaForRelationship(context, name, config, descriptor, record) {
2461 // defaults to no meta content
2462 }
2463 /**
2464 * Returns links for a particular record, i.e. self: "/books/1". Default
2465 * implementation assumes the URL for a particular record maps to that type's
2466 * `show` action, i.e. `books/show`.
2467 *
2468 * @since 0.1.0
2469 */
2470 linksForRecord(context, record) {
2471 let router = container_1.lookup('app:router');
2472 let url = router.urlFor(`${inflection_1.pluralize(record.modelName)}/show`, record);
2473 return typeof url === 'string' ? { self: url } : null;
2474 }
2475 /**
2476 * Returns meta for a particular record.
2477 *
2478 * @since 0.1.0
2479 */
2480 metaForRecord(context, record) {
2481 // defaults to no meta
2482 }
2483 /**
2484 * Sideloads a record into the top level "included" array
2485 *
2486 * @since 0.1.0
2487 */
2488 async includeRecord(context, name, relatedRecord, config, descriptor) {
2489 assert(relatedRecord, 'You tried to sideload an included record, but the record itself was not provided.');
2490 if (!lodash_1.isArray(context.document.included)) {
2491 context.document.included = [];
2492 }
2493 let relatedOptions = (context.options.relationships && context.options.relationships[name]) || context.options;
2494 let relatedSerializer = container_1.lookup(`serializer:${config.serializer || relatedRecord.modelName}`);
2495 let relatedContext = lodash_1.assign({}, context, { options: relatedOptions });
2496 context.document.included.push(await relatedSerializer.renderRecord(relatedContext, relatedRecord));
2497 }
2498 /**
2499 * Render the supplied error
2500 *
2501 * @since 0.1.0
2502 */
2503 renderError(context, error) {
2504 let renderedError = {
2505 status: String(error.status) || '500',
2506 code: error.code || error.name || 'InternalServerError',
2507 detail: error.message
2508 };
2509 set_if_not_empty_1.default(renderedError, 'id', this.idForError(context, error));
2510 set_if_not_empty_1.default(renderedError, 'title', this.titleForError(context, error));
2511 set_if_not_empty_1.default(renderedError, 'source', this.sourceForError(context, error));
2512 set_if_not_empty_1.default(renderedError, 'meta', this.metaForError(context, error));
2513 set_if_not_empty_1.default(renderedError, 'links', this.linksForError(context, error));
2514 return renderedError;
2515 }
2516 /**
2517 * Given an error, return a unique id for this particular occurence of the
2518 * problem.
2519 *
2520 * @since 0.1.0
2521 */
2522 idForError(context, error) {
2523 return error.id;
2524 }
2525 /**
2526 * A short, human-readable summary of the problem that SHOULD NOT change from
2527 * occurrence to occurrence of the problem, except for purposes of
2528 * localization.
2529 *
2530 * @since 0.1.0
2531 */
2532 titleForError(context, error) {
2533 return error.title;
2534 }
2535 /**
2536 * Given an error, return a JSON Pointer, a URL query param name, or other
2537 * info indicating the source of the error.
2538 *
2539 * @since 0.1.0
2540 */
2541 sourceForError(context, error) {
2542 return error.source;
2543 }
2544 /**
2545 * Return the meta for a given error object. You could use this for example,
2546 * to return debug information in development environments.
2547 *
2548 * @since 0.1.0
2549 */
2550 metaForError(context, error) {
2551 return error.meta;
2552 }
2553 /**
2554 * Return a links object for an error. You could use this to link to a bug
2555 * tracker report of the error, for example.
2556 *
2557 * @since 0.1.0
2558 */
2559 linksForError(context, error) {
2560 // defaults to no links
2561 }
2562 /**
2563 * Remove duplicate entries from the sideloaded data.
2564 *
2565 * @since 0.1.0
2566 */
2567 dedupeIncluded(context) {
2568 if (lodash_1.isArray(context.document.included)) {
2569 context.document.included = lodash_1.uniqBy(context.document.included, (resource) => {
2570 return `${resource.type}/${resource.id}`;
2571 });
2572 }
2573 }
2574}
2575exports.default = JSONAPISerializer;
2576
2577});
2578
2579
2580
2581// lib/render/json.js
2582//========================================
2583
2584loader.add('/lib/render/json.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
2585
2586"use strict";
2587Object.defineProperty(exports, "__esModule", { value: true });
2588const lodash_1 = require("lodash");
2589const assert = require("assert");
2590const bluebird_1 = require("bluebird");
2591const serializer_1 = require("./serializer");
2592const model_1 = require("../data/model");
2593const container_1 = require("../metal/container");
2594/**
2595 * Renders the payload as a flat JSON object or array at the top level. Related
2596 * models are embedded.
2597 *
2598 * @package data
2599 * @since 0.1.0
2600 */
2601class JSONSerializer extends serializer_1.default {
2602 constructor() {
2603 super(...arguments);
2604 /**
2605 * The default content type to apply to responses formatted by this
2606 * serializer
2607 *
2608 * @since 0.1.0
2609 */
2610 this.contentType = 'application/json';
2611 }
2612 /**
2613 * Renders the payload, either a primary data model(s) or an error payload.
2614 *
2615 * @since 0.1.0
2616 */
2617 async serialize(body, action, options = {}) {
2618 if (body instanceof Error) {
2619 return this.renderError(body, action, options);
2620 }
2621 return this.renderPrimary(body, action, options);
2622 }
2623 /**
2624 * Renders a primary data payload (a model or array of models).
2625 *
2626 * @since 0.1.0
2627 */
2628 async renderPrimary(payload, action, options) {
2629 if (lodash_1.isArray(payload)) {
2630 return await bluebird_1.all(payload.map(async (item) => {
2631 return await this.renderItem(item, action, options);
2632 }));
2633 }
2634 return await this.renderItem(payload, action, options);
2635 }
2636 /**
2637 * If the primary data isn't a model, just render whatever it is directly
2638 *
2639 * @since 0.1.0
2640 */
2641 async renderItem(item, action, options) {
2642 if (item instanceof model_1.default) {
2643 return await this.renderModel(item, action, options);
2644 }
2645 return item;
2646 }
2647 /**
2648 * Renders an individual model
2649 *
2650 * @since 0.1.0
2651 */
2652 async renderModel(model, action, options) {
2653 let id = model.id;
2654 let attributes = this.serializeAttributes(model, action, options);
2655 let relationships = await this.serializeRelationships(model, action, options);
2656 return lodash_1.assign({ id }, attributes, relationships);
2657 }
2658 /**
2659 * Serialize the attributes for a given model
2660 *
2661 * @since 0.1.0
2662 */
2663 serializeAttributes(model, action, options) {
2664 let serializedAttributes = {};
2665 let attributes = this.attributesToSerialize(action, options);
2666 attributes.forEach((attributeName) => {
2667 let key = this.serializeAttributeName(attributeName);
2668 let rawValue = model[attributeName];
2669 if (!lodash_1.isUndefined(rawValue)) {
2670 let value = this.serializeAttributeValue(rawValue, key, model);
2671 serializedAttributes[key] = value;
2672 }
2673 });
2674 return serializedAttributes;
2675 }
2676 /**
2677 * Transform attribute names into their over-the-wire representation. Default
2678 * behavior uses the attribute name as-is.
2679 *
2680 * @since 0.1.0
2681 */
2682 serializeAttributeName(attributeName) {
2683 return attributeName;
2684 }
2685 /**
2686 * Take an attribute value and return the serialized value. Useful for
2687 * changing how certain types of values are serialized, i.e. Date objects.
2688 *
2689 * The default implementation returns the attribute's value unchanged.
2690 *
2691 * @since 0.1.0
2692 */
2693 serializeAttributeValue(value, key, model) {
2694 return value;
2695 }
2696 /**
2697 * Serialize the relationships for a given model
2698 *
2699 * @since 0.1.0
2700 */
2701 async serializeRelationships(model, action, options) {
2702 let serializedRelationships = {};
2703 let relationships = this.relationshipsToSerialize(action, options);
2704 // The result of this.relationships is a whitelist of which relationships
2705 // should be serialized, and the configuration for their serialization
2706 for (let relationshipName in this.relationships) {
2707 let config = relationships[relationshipName];
2708 let key = config.key || this.serializeRelationshipName(relationshipName);
2709 let descriptor = model.constructor.schema[relationshipName];
2710 assert(descriptor, `You specified a '${relationshipName}' relationship in your ${this.constructor.name} serializer, but no such relationship is defined on the ${model.modelName} model`);
2711 serializedRelationships[key] = await this.serializeRelationship(relationshipName, config, descriptor, model, action, options);
2712 }
2713 return serializedRelationships;
2714 }
2715 /**
2716 * Serializes a relationship
2717 *
2718 * @since 0.1.0
2719 */
2720 async serializeRelationship(relationship, config, descriptor, model, action, options) {
2721 let relatedSerializer = container_1.lookup(`serializer:${descriptor.relatedModelName}`, { loose: true }) || container_1.lookup(`serializer:application`, { loose: true });
2722 if (typeof relatedSerializer === 'boolean') {
2723 throw new Error(`No serializer found for ${descriptor.relatedModelName}, and no fallback application serializer found either`);
2724 }
2725 if (descriptor.mode === 'hasMany') {
2726 let relatedModels = await model.getRelated(relationship);
2727 return await bluebird_1.all(relatedModels.map(async (relatedModel) => {
2728 if (config.strategy === 'embed') {
2729 return await relatedSerializer.renderModel(relatedModel, action, options);
2730 }
2731 else if (config.strategy === 'id') {
2732 return relatedModel.id;
2733 }
2734 }));
2735 }
2736 else {
2737 let relatedModel = await model.getRelated(relationship);
2738 if (config.strategy === 'embed') {
2739 return await relatedSerializer.renderModel(relatedModel, action, options);
2740 }
2741 else if (config.strategy === 'id') {
2742 return relatedModel.id;
2743 }
2744 }
2745 }
2746 /**
2747 * Transform relationship names into their over-the-wire representation.
2748 * Default behavior uses the relationship name as-is.
2749 *
2750 * @since 0.1.0
2751 */
2752 serializeRelationshipName(name) {
2753 return name;
2754 }
2755 /**
2756 * Render an error payload
2757 *
2758 * @since 0.1.0
2759 */
2760 renderError(error, action, options) {
2761 return {
2762 status: error.status || 500,
2763 code: error.code || 'InternalServerError',
2764 message: error.message
2765 };
2766 }
2767}
2768exports.default = JSONSerializer;
2769
2770});
2771
2772
2773
2774// lib/render/serializer.js
2775//========================================
2776
2777loader.add('/lib/render/serializer.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
2778
2779"use strict";
2780Object.defineProperty(exports, "__esModule", { value: true });
2781const view_1 = require("./view");
2782const errors_1 = require("../runtime/errors");
2783const result_1 = require("../utils/result");
2784const container_1 = require("../metal/container");
2785/**
2786 * Serializers allow you to customize what data is returned in the response and
2787 * apply simple transformations to it. They allow you to decouple what data is
2788 * sent from how that data is structured / rendered.
2789 *
2790 * @package data
2791 * @since 0.1.0
2792 */
2793class Serializer extends view_1.default {
2794 constructor() {
2795 super(...arguments);
2796 /**
2797 * The content type header to send back with the response
2798 *
2799 * @since 0.1.0
2800 */
2801 this.contentType = 'application/json';
2802 }
2803 /**
2804 * Convenience method to encapsulate standard attribute whitelist behavior -
2805 * render options take precedence, then allow this.attributes to be a
2806 * function or straight definition
2807 *
2808 * @since 0.1.0
2809 */
2810 attributesToSerialize(action, options) {
2811 return options.attributes || result_1.default(this.attributes, action);
2812 }
2813 /**
2814 * Convenience method to encapsulate standard relationship whitelist behavior
2815 * - render options take precedence, then allow this.relationships to be a
2816 * function or straight definition
2817 *
2818 * @since 0.1.0
2819 */
2820 relationshipsToSerialize(action, options) {
2821 return options.relationships || result_1.default(this.relationships, action);
2822 }
2823 async render(action, response, body, options) {
2824 response.setHeader('Content-type', this.contentType);
2825 if (body instanceof errors_1.default) {
2826 response.statusCode = body.status;
2827 }
2828 body = await this.serialize(body, action, options);
2829 let isProduction = container_1.lookup('config:environment').environment === 'production';
2830 response.write(JSON.stringify(body, null, isProduction ? 0 : 2) || '');
2831 response.end();
2832 }
2833}
2834exports.default = Serializer;
2835
2836});
2837
2838
2839
2840// lib/render/view.js
2841//========================================
2842
2843loader.add('/lib/render/view.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
2844
2845"use strict";
2846Object.defineProperty(exports, "__esModule", { value: true });
2847const object_1 = require("../metal/object");
2848class View extends object_1.default {
2849}
2850exports.default = View;
2851
2852});
2853
2854
2855
2856// lib/runtime/action.js
2857//========================================
2858
2859loader.add('/lib/runtime/action.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
2860
2861"use strict";
2862Object.defineProperty(exports, "__esModule", { value: true });
2863const lodash_1 = require("lodash");
2864const protochain = require("protochain");
2865const instrumentation_1 = require("../metal/instrumentation");
2866const model_1 = require("../data/model");
2867const createDebug = require("debug");
2868const assert = require("assert");
2869const object_1 = require("../metal/object");
2870const errors_1 = require("./errors");
2871const container_1 = require("../metal/container");
2872const debug = createDebug('denali:action');
2873const beforeFiltersCache = new Map();
2874const afterFiltersCache = new Map();
2875/**
2876 * Actions form the core of interacting with a Denali application. They are the
2877 * controller layer in the MVC architecture, taking in incoming requests,
2878 * performing business logic, and handing off to the renderer to send the
2879 * response.
2880 *
2881 * When a request comes in, Denali will invoke the `respond` method on the
2882 * matching Action class. The return value (or resolved return value) of this
2883 * method is used to render the response.
2884 *
2885 * Actions also support filters. Simply define a method on your action, and add
2886 * the method name to the `before` or `after` array. Filters behave similar to
2887 * responders in that they receive the request params and can return a promise
2888 * which will be waited on before continuing. Filters are inheritable, so child
2889 * classes will run filters added by parent classes.
2890 *
2891 * @package runtime
2892 * @since 0.1.0
2893 */
2894class Action extends object_1.default {
2895 constructor() {
2896 super(...arguments);
2897 /**
2898 * Application config
2899 *
2900 * @since 0.1.0
2901 */
2902 this.config = container_1.lookup('service:config');
2903 /**
2904 * Force which parser should be used for parsing the incoming request.
2905 *
2906 * By default it uses the application parser, but you can override with the
2907 * name of the parser you'd rather use instead.
2908 *
2909 * @since 0.1.0
2910 */
2911 this.parser = container_1.lookup('parser:application');
2912 /**
2913 * Automatically inject the logger into all actions
2914 *
2915 * @since 0.1.0
2916 */
2917 this.logger = container_1.lookup('app:logger');
2918 /**
2919 * Track whether or not we have rendered yet
2920 */
2921 this.hasRendered = false;
2922 }
2923 async render(status, body, options) {
2924 if (typeof status !== 'number') {
2925 options = body;
2926 body = status;
2927 status = 200;
2928 }
2929 if (!options) {
2930 options = {};
2931 }
2932 this.hasRendered = true;
2933 debug(`[${this.request.id}]: rendering`);
2934 this.response.setHeader('X-Request-Id', this.request.id);
2935 debug(`[${this.request.id}]: setting response status code to ${status}`);
2936 this.response.statusCode = status;
2937 if (!body) {
2938 debug(`[${this.request.id}]: no response body to render, response finished`);
2939 this.response.end();
2940 return;
2941 }
2942 // Render with a custom view if requested
2943 if (options.view) {
2944 let view = container_1.lookup(`view:${options.view}`);
2945 assert(view, `No such view: ${options.view}`);
2946 debug(`[${this.request.id}]: rendering response body with the ${options.view} view`);
2947 return await view.render(this, this.response, body, options);
2948 }
2949 // Pick the serializer to use
2950 let serializerLookup = 'application';
2951 if (options.serializer) {
2952 serializerLookup = options.serializer;
2953 }
2954 else {
2955 let sample = lodash_1.isArray(body) ? body[0] : body;
2956 if (sample instanceof model_1.default) {
2957 serializerLookup = sample.modelName;
2958 }
2959 }
2960 // Render with the serializer
2961 let serializer = container_1.lookup(`serializer:${serializerLookup}`);
2962 debug(`[${this.request.id}]: rendering response body with the ${serializerLookup} serializer`);
2963 return await serializer.render(this, this.response, body, options);
2964 }
2965 /**
2966 * Invokes the action. Determines the best responder method for content
2967 * negotiation, then executes the filter/responder chain in sequence,
2968 * handling errors and rendering the response.
2969 *
2970 * You shouldn't invoke this directly - Denali will automatically wire up
2971 * your routes to this method.
2972 *
2973 * @since 0.1.0
2974 */
2975 async run(request, response) {
2976 this.request = request;
2977 this.response = response;
2978 // Parse the incoming request based on the action's chosen parser
2979 debug(`[${request.id}]: parsing request`);
2980 assert(typeof this.parser.parse === 'function', 'The parser you supply must define a `parse(request)` method. See the parser docs for details');
2981 let parsedRequest = await this.parser.parse(request);
2982 // Build the before and after filter chains
2983 let { beforeChain, afterChain } = this._buildFilterChains();
2984 let instrumentation = instrumentation_1.default.instrument('action.run', {
2985 action: this.actionPath,
2986 parsed: parsedRequest
2987 });
2988 // Before filters
2989 debug(`[${this.request.id}]: running before filters`);
2990 await this._invokeFilters(beforeChain, parsedRequest);
2991 // Responder
2992 if (!this.hasRendered) {
2993 debug(`[${this.request.id}]: running responder`);
2994 let result = await this.respond(parsedRequest);
2995 // Autorender if render has not been manually called and a value was returned
2996 if (!this.hasRendered) {
2997 debug(`[${this.request.id}]: autorendering`);
2998 await this.render(result);
2999 }
3000 }
3001 // After filters
3002 debug(`[${this.request.id}]: running after filters`);
3003 await this._invokeFilters(afterChain, parsedRequest);
3004 // If no one has rendered, bail
3005 if (!this.hasRendered) {
3006 throw new errors_1.default.InternalServerError(`${this.actionPath} did not render anything`);
3007 }
3008 instrumentation.finish();
3009 }
3010 /**
3011 * Invokes the filters in the supplied chain in sequence.
3012 */
3013 async _invokeFilters(chain, parsedRequest) {
3014 chain = lodash_1.clone(chain);
3015 while (chain.length > 0) {
3016 let filter = chain.shift();
3017 let instrumentation = instrumentation_1.default.instrument('action.filter', {
3018 action: this.actionPath,
3019 request: parsedRequest,
3020 filter: filter.name
3021 });
3022 debug(`[${this.request.id}]: running '${filter.name}' filter`);
3023 let filterResult = await filter.call(this, parsedRequest);
3024 instrumentation.finish();
3025 if (!this.hasRendered && filterResult) {
3026 return this.render(200, filterResult);
3027 }
3028 }
3029 }
3030 /**
3031 * Walk the prototype chain of this Action instance to find all the `before`
3032 * and `after` arrays to build the complete filter chains.
3033 *
3034 * Caches the result on the child Action class to avoid the potentially
3035 * expensive prototype walk on each request.
3036 *
3037 * Throws if it encounters the name of a filter method that doesn't exist.
3038 */
3039 _buildFilterChains() {
3040 let ActionClass = this.constructor;
3041 if (!beforeFiltersCache.has(ActionClass)) {
3042 let prototypeChain = protochain(ActionClass).reverse().concat(ActionClass);
3043 this._buildFilterChain('before', beforeFiltersCache, prototypeChain);
3044 this._buildFilterChain('after', afterFiltersCache, prototypeChain);
3045 }
3046 return {
3047 beforeChain: beforeFiltersCache.get(ActionClass),
3048 afterChain: afterFiltersCache.get(ActionClass)
3049 };
3050 }
3051 _buildFilterChain(stageName, cache, prototypes) {
3052 let ActionClass = this.constructor;
3053 let compiledFilterList = lodash_1.flatMap(prototypes, (prototype) => {
3054 let filters = lodash_1.get(prototype, stageName, []);
3055 filters = lodash_1.castArray(filters);
3056 return filters.map((filter) => {
3057 if (typeof filter === 'string') {
3058 assert(typeof lodash_1.get(this, filter) === 'function', `${filter} method not found on the ${this.actionPath} action.`);
3059 return this[filter];
3060 }
3061 return filter;
3062 });
3063 });
3064 cache.set(ActionClass, compiledFilterList);
3065 }
3066}
3067/**
3068 * Invoked before the `respond()` method. The framework will invoke filters
3069 * from parent classes and mixins in the same order the mixins were applied.
3070 *
3071 * Filters can be synchronous, or return a promise (which will pause the
3072 * before/respond/after chain until it resolves).
3073 *
3074 * If a before filter returns any value (or returns a promise which resolves
3075 * to any value) other than null or undefined, Denali will attempt to render
3076 * that response and halt further processing of the request (including
3077 * remaining before filters).
3078 *
3079 * Filters must be defined as static properties to allow Denali to extract
3080 * the values. Instance fields are not visible until instantiation, so
3081 * there's no way to build an "accumulated" value from each step in the
3082 * inheritance chain.
3083 *
3084 * @since 0.1.0
3085 */
3086Action.before = [];
3087/**
3088 * Invoked after the `respond()` method. The framework will invoke filters
3089 * from parent classes and mixins in the same order the mixins were applied.
3090 *
3091 * Filters can be synchronous, or return a promise (which will pause the
3092 * before/respond/after chain until it resolves).
3093 *
3094 * Filters must be defined as static properties to allow Denali to extract
3095 * the values. Instance fields are not visible until instantiation, so
3096 * there's no way to build an "accumulated" value from each step in the
3097 * inheritance chain.
3098 *
3099 * @since 0.1.0
3100 */
3101Action.after = [];
3102exports.default = Action;
3103
3104});
3105
3106
3107
3108// lib/runtime/addon.js
3109//========================================
3110
3111loader.add('/lib/runtime/addon.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
3112
3113"use strict";
3114Object.defineProperty(exports, "__esModule", { value: true });
3115const container_1 = require("../metal/container");
3116/**
3117 * Addons are the fundamental unit of organization for Denali apps. The
3118 * Application class is just a specialized Addon, and each Addon can contain
3119 * any amount of functionality - each one is essentially a mini Denali app.
3120 *
3121 * @package runtime
3122 * @since 0.1.0
3123 */
3124class Addon {
3125 /**
3126 * The resolver for this addon's loader scope
3127 */
3128 get resolver() {
3129 return this.loader.resolver;
3130 }
3131 constructor(loader, options) {
3132 this.loader = loader;
3133 this.environment = options.environment;
3134 container_1.default.register(`addon:${this.name}`, this);
3135 }
3136 /**
3137 * The name of the addon. Override this to use a different name than the
3138 * package name for your addon.
3139 *
3140 * @since 0.1.0
3141 */
3142 get name() {
3143 return `${this.loader.pkgName}@${this.loader.version}`;
3144 }
3145 /**
3146 * A hook to perform any shutdown actions necessary to gracefully exit the
3147 * application, i.e. close database/socket connections.
3148 *
3149 * @since 0.1.0
3150 */
3151 async shutdown(application) {
3152 // defaults to noop
3153 }
3154}
3155exports.default = Addon;
3156
3157});
3158
3159
3160
3161// lib/runtime/application.js
3162//========================================
3163
3164loader.add('/lib/runtime/application.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
3165
3166"use strict";
3167Object.defineProperty(exports, "__esModule", { value: true });
3168const lodash_1 = require("lodash");
3169const http = require("http");
3170const https = require("https");
3171const bluebird_1 = require("bluebird");
3172const addon_1 = require("./addon");
3173const topsort_1 = require("../utils/topsort");
3174const container_1 = require("../metal/container");
3175/**
3176 * Application instances are specialized Addons, designed to kick off the
3177 * loading, mounting, and launching stages of booting up.
3178 *
3179 * @package runtime
3180 */
3181class Application extends addon_1.default {
3182 constructor(loader, options) {
3183 super(loader, lodash_1.defaults(options, { environment: 'development' }));
3184 /**
3185 * List of child addons for this app (one-level deep only, i.e. no
3186 * addons-of-addons are included)
3187 *
3188 * @since 0.1.0
3189 */
3190 this.addons = [];
3191 this.loader.children.forEach((addonLoader, addonName) => {
3192 let AddonClass = addonLoader.resolver.retrieve('app:addon') || addon_1.default;
3193 this.addons.push(new AddonClass(addonLoader, options));
3194 });
3195 this.drainers = [];
3196 // Setup some helpful container shortcuts
3197 container_1.default.register('app:main', this);
3198 this.router = container_1.default.lookup('app:router');
3199 this.logger = container_1.default.lookup('app:logger');
3200 // Generate config first, since the loading process may need it
3201 this.generateConfig();
3202 this.config = container_1.default.lookup('service:config');
3203 this.compileRouter();
3204 }
3205 /**
3206 * Take the loaded environment config functions, and execute them.
3207 * Application config is executed first, and the returned config object is
3208 * handed off to the addon config files, which add their configuration by
3209 * mutating that same object.
3210 *
3211 * The resulting final config is stored at `application.config`, and is
3212 * registered in the container under `config:environment`.
3213 *
3214 * This is invoked before the rest of the addons are loaded for 2 reasons:
3215 *
3216 * - The config values for the application could theoretically impact the
3217 * addon loading process
3218 * - Addons are given a chance to modify the application config, so it must
3219 * be loaded before they are.
3220 */
3221 generateConfig() {
3222 let appConfig = this.resolver.retrieve('config:environment') || lodash_1.constant({
3223 environment: 'development',
3224 server: {
3225 port: 3000
3226 }
3227 });
3228 let config = appConfig(this.environment, container_1.default);
3229 config.environment = this.environment;
3230 container_1.default.register('config:environment', config);
3231 this.addons.forEach((addon) => {
3232 let addonConfig = addon.resolver.retrieve('config:environment');
3233 if (addonConfig) {
3234 addonConfig(this.environment, container_1.default, config);
3235 }
3236 });
3237 return config;
3238 }
3239 /**
3240 * Assemble middleware and routes
3241 */
3242 compileRouter() {
3243 // Load addon middleware first
3244 this.addons.forEach((addon) => {
3245 let addonMiddleware = addon.resolver.retrieve('config:middleware') || lodash_1.noop;
3246 addonMiddleware(this.router, this);
3247 });
3248 // Then load app middleware
3249 let appMiddleware = this.resolver.retrieve('config:middleware') || lodash_1.noop;
3250 appMiddleware(this.router, this);
3251 // Load app routes first so they have precedence
3252 let appRoutes = this.resolver.retrieve('config:routes') || lodash_1.noop;
3253 appRoutes(this.router, this);
3254 // Load addon routes in reverse order so routing precedence matches addon load order
3255 this.addons.reverse().forEach((addon) => {
3256 let addonRoutes = addon.resolver.retrieve('config:routes') || lodash_1.noop;
3257 addonRoutes(this.router, this);
3258 });
3259 }
3260 /**
3261 * Start the Denali server. Runs all initializers, creates an HTTP server,
3262 * and binds to the port to handle incoming HTTP requests.
3263 *
3264 * @since 0.1.0
3265 */
3266 async start() {
3267 let port = this.config.getWithDefault('server', 'port', 3000);
3268 await this.runInitializers();
3269 if (!this.config.get('server', 'detached')) {
3270 await this.createServer(port);
3271 this.logger.info(`${this.name} server up on port ${port}`);
3272 }
3273 }
3274 /**
3275 * Creates an HTTP or HTTPS server, depending on whether or not SSL
3276 * configuration is present in config/environment.js
3277 */
3278 async createServer(port) {
3279 await new Promise((resolve) => {
3280 let handler = this.router.handle.bind(this.router);
3281 let server;
3282 if (this.config.get('server', 'ssl')) {
3283 server = https.createServer(this.config.get('server', 'ssl'), handler).listen(port, resolve);
3284 }
3285 else {
3286 server = http.createServer(handler).listen(port, resolve);
3287 }
3288 this.drainers.push(async function drainHttp() {
3289 await new Promise((resolveDrainer) => {
3290 server.close(resolveDrainer);
3291 setTimeout(resolveDrainer, 60 * 1000);
3292 });
3293 });
3294 });
3295 }
3296 /**
3297 * Lookup all initializers and run them in sequence. Initializers can
3298 * override the default load order by including `before` or `after`
3299 * properties on the exported class (the name or array of names of the other
3300 * initializers it should run before/after).
3301 *
3302 * @since 0.1.0
3303 */
3304 async runInitializers() {
3305 let initializers = lodash_1.values(container_1.default.lookupAll('initializer'));
3306 initializers = topsort_1.default(initializers);
3307 for (let initializer of initializers) {
3308 await initializer.initialize(this);
3309 }
3310 }
3311 /**
3312 * Shutdown the application gracefully (i.e. close external database
3313 * connections, drain in-flight requests, etc)
3314 *
3315 * @since 0.1.0
3316 */
3317 async shutdown() {
3318 await bluebird_1.all(this.drainers.map((drainer) => drainer()));
3319 await bluebird_1.all(this.addons.map(async (addon) => {
3320 await addon.shutdown(this);
3321 }));
3322 container_1.default.teardown();
3323 }
3324}
3325exports.default = Application;
3326
3327});
3328
3329
3330
3331// lib/runtime/config.js
3332//========================================
3333
3334loader.add('/lib/runtime/config.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
3335
3336"use strict";
3337Object.defineProperty(exports, "__esModule", { value: true });
3338const service_1 = require("./service");
3339const lodash_1 = require("lodash");
3340const container_1 = require("../metal/container");
3341class ConfigService extends service_1.default {
3342 constructor() {
3343 super(...arguments);
3344 this._config = container_1.lookup('config:environment');
3345 }
3346 get environment() {
3347 return this._config.environment;
3348 }
3349 get(...path) {
3350 // Split on dots in path segments, allowing for `get('foo.bar')` as well as `get('foo', 'bar')`
3351 path = path.reduce((segments, nextSegment) => segments.concat(nextSegment.split('.')), []);
3352 return lodash_1.get(this._config, path);
3353 }
3354 getWithDefault(...args) {
3355 let defaultValue = args.pop();
3356 let path = args;
3357 // Split on dots in path segments, allowing for `get('foo.bar')` as well as `get('foo', 'bar')`
3358 path = path.reduce((segments, nextSegment) => segments.concat(nextSegment.split('.')), []);
3359 return lodash_1.get(this._config, path, defaultValue);
3360 }
3361}
3362exports.default = ConfigService;
3363
3364});
3365
3366
3367
3368// lib/runtime/errors.js
3369//========================================
3370
3371loader.add('/lib/runtime/errors.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
3372
3373"use strict";
3374Object.defineProperty(exports, "__esModule", { value: true });
3375const Errors = require("http-errors");
3376/**
3377 * Denali uses the **http-errors** package for handling HTTP errors. Check
3378 * [it's documentation](https://github.com/jshttp/http-errors) for how to use
3379 * it.
3380 *
3381 * @package runtime
3382 * @since 0.1.0
3383 */
3384exports.default = Errors;
3385
3386});
3387
3388
3389
3390// lib/runtime/logger.js
3391//========================================
3392
3393loader.add('/lib/runtime/logger.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
3394
3395"use strict";
3396Object.defineProperty(exports, "__esModule", { value: true });
3397const lodash_1 = require("lodash");
3398const chalk_1 = require("chalk");
3399const object_1 = require("../metal/object");
3400/**
3401 * A simple Logger class that adds timestamps and supports multiple levels of
3402 * logging, colorized output, and control over verbosity.
3403 *
3404 * @package runtime
3405 * @since 0.1.0
3406 */
3407class Logger extends object_1.default {
3408 constructor() {
3409 super(...arguments);
3410 /**
3411 * Default log level if none specified.
3412 *
3413 * @since 0.1.0
3414 */
3415 this.loglevel = 'info';
3416 /**
3417 * Specify if logs should be colorized.
3418 *
3419 * @since 0.1.0
3420 */
3421 this.colorize = true;
3422 /**
3423 * Available log levels that can be used.
3424 */
3425 this.levels = [
3426 'info',
3427 'warn',
3428 'error'
3429 ];
3430 /**
3431 * Color map for the available levels.
3432 */
3433 this.colors = {
3434 info: chalk_1.default.white,
3435 warn: chalk_1.default.yellow,
3436 error: chalk_1.default.red
3437 };
3438 }
3439 /**
3440 * Log at the 'info' level.
3441 *
3442 * @since 0.1.0
3443 */
3444 info(msg) {
3445 this.log('info', msg);
3446 }
3447 /**
3448 * Log at the 'warn' level.
3449 *
3450 * @since 0.1.0
3451 */
3452 warn(msg) {
3453 this.log('warn', msg);
3454 }
3455 /**
3456 * Log at the 'error' level.
3457 *
3458 * @since 0.1.0
3459 */
3460 error(msg) {
3461 this.log('error', msg);
3462 }
3463 /**
3464 * Log a message to the logger at a specific log level.
3465 */
3466 log(level, msg) {
3467 if (this.levels.indexOf(level) === -1) {
3468 level = this.loglevel;
3469 }
3470 let timestamp = (new Date()).toISOString();
3471 let padLength = this.levels.reduce((n, label) => Math.max(n, label.length), null);
3472 let levelLabel = lodash_1.padStart(level.toUpperCase(), padLength);
3473 if (this.colorize) {
3474 let colorizer = this.colors[level] || lodash_1.identity;
3475 msg = colorizer(msg);
3476 levelLabel = colorizer(levelLabel);
3477 }
3478 /* tslint:disable:no-console no-debugger */
3479 console.log(`[${timestamp}] ${levelLabel} - ${msg}`);
3480 if (level === 'error') {
3481 debugger;
3482 }
3483 /* tslint:enable:no-console no-debugger*/
3484 }
3485}
3486exports.default = Logger;
3487
3488});
3489
3490
3491
3492// lib/runtime/request.js
3493//========================================
3494
3495loader.add('/lib/runtime/request.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
3496
3497"use strict";
3498Object.defineProperty(exports, "__esModule", { value: true });
3499const lodash_1 = require("lodash");
3500const accepts = require("accepts");
3501const net_1 = require("net");
3502const typeis = require("type-is");
3503const parseRange = require("range-parser");
3504const parse = require("parseurl");
3505const proxyaddr = require("proxy-addr");
3506const uuid = require("uuid");
3507const url = require("url");
3508/**
3509 * The Request class represents an incoming HTTP request (specifically, Node's
3510 * IncomingMessage).
3511 *
3512 * @package runtime
3513 * @since 0.1.0
3514 */
3515class Request {
3516 constructor(incomingMessage, serverConfig) {
3517 /**
3518 * A UUID generated unqiue to this request. Useful for tracing a request
3519 * through the application.
3520 *
3521 * @since 0.1.0
3522 */
3523 this.id = uuid.v4();
3524 this.incomingMessage = incomingMessage;
3525 this.config = serverConfig || {};
3526 }
3527 /**
3528 * The uppercase method name for the request, i.e. GET, POST, HEAD
3529 *
3530 * @since 0.1.0
3531 */
3532 get method() {
3533 return this.incomingMessage.method.toUpperCase();
3534 }
3535 /**
3536 * The requested path name
3537 *
3538 * @since 0.1.0
3539 */
3540 get path() {
3541 return parse(this.incomingMessage).pathname;
3542 }
3543 /**
3544 * The query string, parsed into an object
3545 *
3546 * @since 0.1.0
3547 */
3548 get query() {
3549 return url.parse(this.incomingMessage.url, true).query;
3550 }
3551 /**
3552 * The headers for the incoming request
3553 *
3554 * @since 0.1.0
3555 */
3556 get headers() {
3557 return this.incomingMessage.headers;
3558 }
3559 /**
3560 * Return subdomains as an array.
3561 *
3562 * Subdomains are the dot-separated parts of the host before the main domain
3563 * of the app. By default, the domain of the app is assumed to be the last
3564 * two parts of the host. This can be changed by setting
3565 * config.server.subdomainOffset
3566 *
3567 * For example, if the domain is "tobi.ferrets.example.com": If the subdomain
3568 * offset is not set, req.subdomains is `["ferrets", "tobi"]`. If the
3569 * subdomain offset is 3, req.subdomains is `["tobi"]`.
3570 *
3571 * @since 0.1.0
3572 */
3573 get subdomains() {
3574 let hostname = this.hostname;
3575 if (!hostname) {
3576 return [];
3577 }
3578 let offset = this.config.subdomainOffset;
3579 let subdomains = !net_1.isIP(hostname) ? hostname.split('.').reverse() : [hostname];
3580 return subdomains.slice(offset == null ? 2 : offset);
3581 }
3582 /**
3583 * Return the protocol string "http" or "https" when requested with TLS. When
3584 * the "server.trustProxy" setting trusts the socket address, the
3585 * "X-Forwarded-Proto" header field will be trusted and used if present.
3586 *
3587 * If you're running behind a reverse proxy that supplies https for you this
3588 * may be enabled.
3589 *
3590 * @since 0.1.0
3591 */
3592 get protocol() {
3593 let rawProtocol = this.incomingMessage.connection.encrypted ? 'https' : 'http';
3594 let ip = this.incomingMessage.connection.remoteAddress;
3595 let trustProxyConfig = this.config.trustProxy || lodash_1.constant(false);
3596 if (trustProxyConfig) {
3597 let trustProxy;
3598 if (typeof trustProxyConfig !== 'function') {
3599 trustProxy = proxyaddr.compile(trustProxyConfig);
3600 }
3601 else {
3602 trustProxy = trustProxyConfig;
3603 }
3604 if (trustProxy(ip, 0)) {
3605 let proxyClaimedProtocol = this.getHeader('X-Forwarded-Proto') || rawProtocol;
3606 return proxyClaimedProtocol.split(/\s*,\s*/)[0];
3607 }
3608 }
3609 return rawProtocol;
3610 }
3611 /**
3612 * Check if the request was an _XMLHttpRequest_.
3613 *
3614 * @since 0.1.0
3615 */
3616 get xhr() {
3617 let val = this.getHeader('X-Requested-With') || '';
3618 return val.toLowerCase() === 'xmlhttprequest';
3619 }
3620 /**
3621 * Parse the "Host" header field to a hostname.
3622 *
3623 * When the "trust proxy" setting trusts the socket address, the
3624 * "X-Forwarded-Host" header field will be trusted.
3625 *
3626 * @since 0.1.0
3627 */
3628 get hostname() {
3629 let host = this.getHeader('X-Forwarded-Host');
3630 let ip = this.incomingMessage.socket.remoteAddress;
3631 let trustProxyConfig = this.config.trustProxy || lodash_1.constant(false);
3632 let trustProxy;
3633 if (typeof trustProxyConfig !== 'function') {
3634 trustProxy = proxyaddr.compile(trustProxyConfig);
3635 }
3636 else {
3637 trustProxy = trustProxyConfig;
3638 }
3639 if (!host || !trustProxy(ip, 0)) {
3640 host = this.getHeader('Host');
3641 }
3642 if (!host) {
3643 return;
3644 }
3645 // IPv6 literal support
3646 let offset = host[0] === '[' ? host.indexOf(']') + 1 : 0;
3647 let index = host.indexOf(':', offset);
3648 return index !== -1 ? host.substring(0, index) : host;
3649 }
3650 /**
3651 * Return the remote address from the trusted proxy.
3652 *
3653 * The is the remote address on the socket unless "trust proxy" is set.
3654 *
3655 * @since 0.1.0
3656 */
3657 get ip() {
3658 let trustProxyConfig = this.config.trustProxy || lodash_1.constant(false);
3659 return proxyaddr(this.incomingMessage, trustProxyConfig);
3660 }
3661 /**
3662 * When "trust proxy" is set, trusted proxy addresses + client.
3663 *
3664 * For example if the value were "client, proxy1, proxy2" you would receive
3665 * the array `["client", "proxy1", "proxy2"]` where "proxy2" is the furthest
3666 * down-stream and "proxy1" and "proxy2" were trusted.
3667 *
3668 * @since 0.1.0
3669 */
3670 get ips() {
3671 let trustProxyConfig = this.config.trustProxy || lodash_1.constant(false);
3672 let ips = proxyaddr.all(this.incomingMessage, trustProxyConfig);
3673 ips.reverse().pop();
3674 return ips;
3675 }
3676 /**
3677 * Does this request have a request body?
3678 */
3679 get hasBody() {
3680 return typeis.hasBody(this.incomingMessage);
3681 }
3682 getHeader(name) {
3683 return this.incomingMessage.headers[name.toLowerCase()];
3684 }
3685 accepts(...type) {
3686 let accept = accepts(this.incomingMessage);
3687 return accept.types(...type);
3688 }
3689 acceptsEncodings(...encoding) {
3690 let accept = accepts(this.incomingMessage);
3691 // <any> is needed here because of incorrect types
3692 // see https://github.com/DefinitelyTyped/DefinitelyTyped/pull/23395
3693 return accept.encodings(...encoding);
3694 }
3695 acceptsCharsets(...charset) {
3696 let accept = accepts(this.incomingMessage);
3697 // <any> is needed here because of incorrect types
3698 // see https://github.com/DefinitelyTyped/DefinitelyTyped/pull/23395
3699 return accept.charsets(...charset);
3700 }
3701 acceptsLanguages(...lang) {
3702 let accept = accepts(this.incomingMessage);
3703 // <any> is needed here because of incorrect types
3704 // see https://github.com/DefinitelyTyped/DefinitelyTyped/pull/23395
3705 return accept.languages(...lang);
3706 }
3707 /**
3708 * Parse Range header field, capping to the given `size`.
3709 *
3710 * Unspecified ranges such as "0-" require knowledge of your resource length.
3711 * In the case of a byte range this is of course the total number of bytes. If
3712 * the Range header field is not given `undefined` is returned, `-1` when
3713 * unsatisfiable, and `-2` when syntactically invalid.
3714 *
3715 * When ranges are returned, the array has a "type" property which is the type
3716 * of range that is required (most commonly, "bytes"). Each array element is
3717 * an object with a "start" and "end" property for the portion of the range.
3718 *
3719 * The "combine" option can be set to `true` and overlapping & adjacent ranges
3720 * will be combined into a single range.
3721 *
3722 * NOTE: remember that ranges are inclusive, so for example "Range: users=0-3"
3723 * should respond with 4 users when available, not 3.
3724 *
3725 * @since 0.1.0
3726 */
3727 range(size, options) {
3728 let range = this.getHeader('Range');
3729 if (!range) {
3730 return;
3731 }
3732 return parseRange(size, range, options);
3733 }
3734 /**
3735 * Check if the incoming request contains the "Content-Type" header field,
3736 * and it contains the give mime `type`.
3737 *
3738 * Examples:
3739 *
3740 * // With Content-Type: text/html; charset=utf-8
3741 * req.is('html');
3742 * req.is('text/html');
3743 * req.is('text/*');
3744 * // => true
3745 *
3746 * // When Content-Type is application/json
3747 * req.is('json');
3748 * req.is('application/json');
3749 * req.is('application/*');
3750 * // => true
3751 *
3752 * req.is('html');
3753 * // => false
3754 *
3755 * @since 0.1.0
3756 */
3757 is(...types) {
3758 if (Array.isArray(types[0])) {
3759 types = types[0];
3760 }
3761 return typeis(this.incomingMessage, types);
3762 }
3763}
3764exports.default = Request;
3765
3766});
3767
3768
3769
3770// lib/runtime/route.js
3771//========================================
3772
3773loader.add('/lib/runtime/route.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
3774
3775"use strict";
3776Object.defineProperty(exports, "__esModule", { value: true });
3777const RouteParser = require("route-parser");
3778/**
3779 * Extends the base RouteParser Route class with some additional properties
3780 * that Denali tacks on.
3781 *
3782 * @package runtime
3783 */
3784class Route extends RouteParser {
3785}
3786exports.default = Route;
3787
3788});
3789
3790
3791
3792// lib/runtime/router.js
3793//========================================
3794
3795loader.add('/lib/runtime/router.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
3796
3797"use strict";
3798Object.defineProperty(exports, "__esModule", { value: true });
3799const ware = require("ware");
3800const inflection_1 = require("inflection");
3801const bluebird_1 = require("bluebird");
3802const createDebug = require("debug");
3803const errors_1 = require("./errors");
3804const route_1 = require("./route");
3805const request_1 = require("./request");
3806const object_1 = require("../metal/object");
3807const container_1 = require("../metal/container");
3808const lodash_1 = require("lodash");
3809const debug = createDebug('denali:router');
3810/**
3811 * The Router handles incoming requests, sending them to the appropriate
3812 * action. It's also responsible for defining routes in the first place - it's
3813 * passed into the `config/routes.js` file's exported function as the first
3814 * argument.
3815 *
3816 * @package runtime
3817 * @since 0.1.0
3818 */
3819class Router extends object_1.default {
3820 constructor() {
3821 super(...arguments);
3822 /**
3823 * The cache of available routes.
3824 */
3825 this.routes = {
3826 GET: [],
3827 POST: [],
3828 PUT: [],
3829 PATCH: [],
3830 DELETE: [],
3831 HEAD: [],
3832 OPTIONS: []
3833 };
3834 /**
3835 * The internal generic middleware handler, responsible for building and
3836 * executing the middleware chain.
3837 */
3838 this.middleware = ware();
3839 }
3840 get config() {
3841 return container_1.lookup('service:config');
3842 }
3843 /**
3844 * Helper method to invoke the function exported by `config/routes.js` in the
3845 * context of the current router instance.
3846 *
3847 * @since 0.1.0
3848 */
3849 map(fn) {
3850 debug('mapping routes');
3851 fn(this);
3852 }
3853 /**
3854 * Takes an incoming request and it's response from an HTTP server, prepares
3855 * them, runs the generic middleware first, hands them off to the appropriate
3856 * action given the incoming URL, and finally renders the response.
3857 */
3858 async handle(req, res) {
3859 let serverConfig = this.config.get('server');
3860 let request = new request_1.default(req, serverConfig);
3861 try {
3862 debug(`[${request.id}]: ${request.method.toUpperCase()} ${request.path}`);
3863 // Middleware
3864 await bluebird_1.fromNode((cb) => this.middleware.run(request, res, cb));
3865 // Find the matching route
3866 debug(`[${request.id}]: routing request`);
3867 let routes = this.routes[request.method];
3868 if (routes) {
3869 /* tslint:disable-next-line prefer-for-of */
3870 for (let i = 0; i < routes.length; i += 1) {
3871 request.params = routes[i].match(request.path);
3872 if (request.params) {
3873 request.route = routes[i];
3874 break;
3875 }
3876 }
3877 }
3878 // Handle 404s
3879 if (!request.route) {
3880 let availableRoutes = routes && routes.map((r) => r.spec);
3881 debug(`[${request.id}]: ${request.method} ${request.path} did match any route. Available ${request.method} routes:\n${availableRoutes.join(',\n') || 'none'}`);
3882 let error = new errors_1.default.NotFound('Route not recognized');
3883 error.meta = { availableRoutesForMethod: routes || [] };
3884 throw error;
3885 }
3886 // Create our action to handle the response
3887 let action = new request.route.action();
3888 // Run the action
3889 debug(`[${request.id}]: running action`);
3890 await action.run(request, res);
3891 }
3892 catch (error) {
3893 await this.handleError(request, res, error);
3894 }
3895 }
3896 /**
3897 * Takes a request, response, and an error and hands off to the generic
3898 * application level error action.
3899 */
3900 async handleError(request, res, error) {
3901 request.params = request.params || {};
3902 request.params.error = error;
3903 let ErrorAction = container_1.lookup('action:error');
3904 let errorAction = new ErrorAction();
3905 return errorAction.run(request, res);
3906 }
3907 /**
3908 * Add the supplied middleware function to the generic middleware stack that
3909 * runs prior to action handling.
3910 *
3911 * @since 0.1.0
3912 */
3913 use(middleware) {
3914 this.middleware.use(middleware);
3915 }
3916 /**
3917 * Add a route to the application. Maps a method and URL pattern to an
3918 * action, with optional additional parameters.
3919 *
3920 * URL patterns can use:
3921 *
3922 * * Dynamic segments, i.e. `'foo/:bar'` * Wildcard segments, i.e.
3923 * `'foo/*bar'`, captures the rest of the URL up to the querystring
3924 * * Optional groups, i.e. `'foo(/:bar)'`
3925 *
3926 * @since 0.1.0
3927 */
3928 route(method, rawPattern, actionPath, params) {
3929 method = method.toUpperCase();
3930 // Ensure leading slashes
3931 let normalizedPattern = rawPattern.replace(/^([^/])/, '/$1');
3932 // Remove hardcoded trailing slashes
3933 normalizedPattern = normalizedPattern.replace(/\/$/, '');
3934 // Ensure optional trailing slashes
3935 normalizedPattern = `${normalizedPattern}(/)`;
3936 // Add route
3937 let ActionClass = container_1.lookup(`action:${actionPath}`);
3938 let route = new route_1.default(normalizedPattern);
3939 route.actionPath = actionPath;
3940 route.action = ActionClass;
3941 route.additionalParams = params;
3942 if (!route.action) {
3943 throw new Error(`No action found at ${actionPath}`);
3944 }
3945 this.routes[method].push(route);
3946 }
3947 /**
3948 * Returns the URL for a given action. You can supply a params object which
3949 * will be used to fill in the dynamic segements of the action's route (if
3950 * any).
3951 */
3952 urlFor(actionPath, data) {
3953 let actionEntry = container_1.lookup(`action:${actionPath}`, { loose: true });
3954 if (actionEntry === false) {
3955 return false;
3956 }
3957 let action = actionEntry; // because TS won't narrow the type in the forEach below
3958 let route;
3959 lodash_1.forEach(this.routes, (routes) => {
3960 route = lodash_1.find(routes, { action });
3961 return !route; // kill the iterator if we found the match
3962 });
3963 return route && route.reverse(data);
3964 }
3965 /**
3966 * Shorthand for `this.route('get', ...arguments)`
3967 *
3968 * @since 0.1.0
3969 */
3970 get(rawPattern, actionPath, params) {
3971 this.route('get', rawPattern, actionPath, params);
3972 }
3973 /**
3974 * Shorthand for `this.route('post', ...arguments)`
3975 *
3976 * @since 0.1.0
3977 */
3978 post(rawPattern, actionPath, params) {
3979 this.route('post', rawPattern, actionPath, params);
3980 }
3981 /**
3982 * Shorthand for `this.route('put', ...arguments)`
3983 *
3984 * @since 0.1.0
3985 */
3986 put(rawPattern, actionPath, params) {
3987 this.route('put', rawPattern, actionPath, params);
3988 }
3989 /**
3990 * Shorthand for `this.route('patch', ...arguments)`
3991 *
3992 * @since 0.1.0
3993 */
3994 patch(rawPattern, actionPath, params) {
3995 this.route('patch', rawPattern, actionPath, params);
3996 }
3997 /**
3998 * Shorthand for `this.route('delete', ...arguments)`
3999 *
4000 * @since 0.1.0
4001 */
4002 delete(rawPattern, actionPath, params) {
4003 this.route('delete', rawPattern, actionPath, params);
4004 }
4005 /**
4006 * Shorthand for `this.route('head', ...arguments)`
4007 *
4008 * @since 0.1.0
4009 */
4010 head(rawPattern, actionPath, params) {
4011 this.route('head', rawPattern, actionPath, params);
4012 }
4013 /**
4014 * Shorthand for `this.route('options', ...arguments)`
4015 *
4016 * @since 0.1.0
4017 */
4018 options(rawPattern, actionPath, params) {
4019 this.route('options', rawPattern, actionPath, params);
4020 }
4021 /**
4022 * Create all the CRUD routes for a given resource and it's relationships.
4023 * Based on the JSON-API recommendations for URL design.
4024 *
4025 * The `options` argument lets you pass in `only` or `except` arrays to
4026 * define exceptions. Action names included in `only` will be the only ones
4027 * generated, while names included in `except` will be omitted.
4028 *
4029 * Set `options.related = false` to disable relationship routes.
4030 *
4031 * If no options are supplied, the following routes are generated (assuming a
4032 * 'books' resource as an example):
4033 *
4034 * * `GET /books`
4035 * * `POST /books`
4036 * * `GET /books/:id`
4037 * * `PATCH /books/:id`
4038 * * `DELETE /books/:id`
4039 * * `GET /books/:id/:relation`
4040 * * `GET /books/:id/relationships/:relation`
4041 * * `PATCH /books/:id/relationships/:relation`
4042 * * `POST /books/:id/relationships/:relation`
4043 * * `DELETE /books/:id/relationships/:relation`
4044 *
4045 * See http://jsonapi.org/recommendations/#urls for details.
4046 *
4047 * @since 0.1.0
4048 */
4049 resource(resourceName, options = {}) {
4050 let plural = inflection_1.pluralize(resourceName);
4051 let collection = `/${plural}`;
4052 let resource = `${collection}/:id`;
4053 let relationship = `${resource}/relationships/:relation`;
4054 let related = `${resource}/:relation`;
4055 if (!options.related) {
4056 options.except = ['related', 'fetch-related', 'replace-related', 'add-related', 'remove-related'].concat(options.except);
4057 }
4058 let hasWhitelist = Boolean(options.only);
4059 options.only = lodash_1.castArray(options.only);
4060 options.except = lodash_1.castArray(options.except);
4061 /**
4062 * Check if the given action should be generated based on the
4063 * whitelist/blacklist options
4064 */
4065 function include(action) {
4066 let whitelisted = options.only.includes(action);
4067 let blacklisted = options.except.includes(action);
4068 return !blacklisted && ((hasWhitelist && whitelisted) ||
4069 !hasWhitelist);
4070 }
4071 [
4072 ['list', 'get', collection],
4073 ['create', 'post', collection],
4074 ['show', 'get', resource],
4075 ['update', 'patch', resource],
4076 ['destroy', 'delete', resource],
4077 ['related', 'get', related],
4078 ['fetch-related', 'get', relationship],
4079 ['replace-related', 'patch', relationship],
4080 ['add-related', 'post', relationship],
4081 ['remove-related', 'delete', relationship]
4082 ].forEach((routeTemplate) => {
4083 let [action, method, url] = routeTemplate;
4084 if (include(action)) {
4085 let routeMethod = this[method];
4086 routeMethod.call(this, url, `${plural}/${action}`);
4087 }
4088 });
4089 }
4090 /**
4091 * Enables easy route namespacing. You can supply a method which takes a
4092 * single argument that works just like the `router` argument in your
4093 * `config/routes.js`, or you can use the return value just like the router.
4094 *
4095 * router.namespace('users', (namespace) => {
4096 * namespace.get('sign-in');
4097 * });
4098 * // or ...
4099 * let namespace = router.namespace('users');
4100 * namespace.get('sign-in');
4101 */
4102 namespace(namespace, fn) {
4103 let router = this;
4104 if (namespace.endsWith('/')) {
4105 namespace = namespace.slice(0, namespace.length - 1);
4106 }
4107 // tslint:disable:completed-docs
4108 let wrapper = {
4109 get(pattern, actionPath, params) {
4110 router.route('get', `${namespace}/${pattern.replace(/^\//, '')}`, actionPath, params);
4111 },
4112 post(pattern, actionPath, params) {
4113 router.route('post', `${namespace}/${pattern.replace(/^\//, '')}`, actionPath, params);
4114 },
4115 put(pattern, actionPath, params) {
4116 router.route('put', `${namespace}/${pattern.replace(/^\//, '')}`, actionPath, params);
4117 },
4118 patch(pattern, actionPath, params) {
4119 router.route('patch', `${namespace}/${pattern.replace(/^\//, '')}`, actionPath, params);
4120 },
4121 delete(pattern, actionPath, params) {
4122 router.route('delete', `${namespace}/${pattern.replace(/^\//, '')}`, actionPath, params);
4123 },
4124 head(pattern, actionPath, params) {
4125 router.route('head', `${namespace}/${pattern.replace(/^\//, '')}`, actionPath, params);
4126 },
4127 options(pattern, actionPath, params) {
4128 router.route('options', `${namespace}/${pattern.replace(/^\//, '')}`, actionPath, params);
4129 },
4130 resource(resourceName, options) {
4131 router.resource.call(this, resourceName, options);
4132 }
4133 };
4134 // tslint:enable:completed-docs
4135 if (fn) {
4136 fn(wrapper);
4137 }
4138 }
4139}
4140exports.default = Router;
4141
4142});
4143
4144
4145
4146// lib/runtime/service.js
4147//========================================
4148
4149loader.add('/lib/runtime/service.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
4150
4151"use strict";
4152Object.defineProperty(exports, "__esModule", { value: true });
4153const object_1 = require("../metal/object");
4154/**
4155 * Services are typically used to represent either external systems (i.e. a
4156 * caching service) or a cross-cutting, reusable piece of application logic
4157 * (i.e. an authorization / roles service).
4158 *
4159 * Services are mostly conventional - they are just singletons with no
4160 * special behavior. The common base class ensures they are
4161 * singletons, makes user intent clear, and paves the way for introducing
4162 * additional common functionality in future versions of Denali.
4163 *
4164 * @package runtime
4165 */
4166class Service extends object_1.default {
4167}
4168exports.default = Service;
4169
4170});
4171
4172
4173
4174// lib/test/acceptance-test.js
4175//========================================
4176
4177loader.add('/lib/test/acceptance-test.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
4178
4179"use strict";
4180Object.defineProperty(exports, "__esModule", { value: true });
4181const path = require("path");
4182const glob_1 = require("glob");
4183const bluebird_1 = require("bluebird");
4184const lodash_1 = require("lodash");
4185const mock_request_1 = require("./mock-request");
4186const mock_response_1 = require("./mock-response");
4187/**
4188 * The AppAcceptance class represents an app acceptance test. It spins up an
4189 * in-memory instance of the application under test, and exposes methods to
4190 * submit simulated requests to the application, and get the response. This
4191 * helps keep acceptance tests lightweight and easily parallelizable, since
4192 * they don't need to bind to an actual port.
4193 *
4194 * @package test
4195 * @since 0.1.0
4196 */
4197class AcceptanceTest {
4198 constructor() {
4199 /**
4200 * Default headers that are applied to each request. Useful for handling
4201 * API-wide content-types, sessions, etc.
4202 *
4203 * @since 0.1.0
4204 */
4205 this.headers = {
4206 accept: 'application/json',
4207 'content-type': 'application/json'
4208 };
4209 /**
4210 * An internal registry of container injections.
4211 */
4212 this._injections = {};
4213 let compiledPath = path.join(process.cwd(), process.env.DENALI_TEST_BUILD_DIR);
4214 let bundleFile = glob_1.sync(path.join(compiledPath, '*.bundle.js'))[0];
4215 let bundle = require(bundleFile);
4216 this.container = bundle();
4217 let Application = this.container.lookup('app:application');
4218 this.application = new Application(this.container.loader, { environment: process.env.NODE_ENV || 'test' });
4219 }
4220 /**
4221 * A helper method for setting up an app acceptance test. Adds
4222 * beforeEach/afterEach hooks to the current ava test suite which will setup
4223 * and teardown the acceptance test. They also setup a test transaction and
4224 * roll it back once the test is finished (for the ORM adapters that support
4225 * it), so your test data won't pollute the database.
4226 *
4227 * @package test
4228 * @since 0.1.0
4229 */
4230 static setupTest() {
4231 let ava = require('ava');
4232 ava.beforeEach(async (t) => {
4233 let acceptanceTest = new AcceptanceTest();
4234 await acceptanceTest.setup(t.context);
4235 });
4236 ava.afterEach.always(async (t) => {
4237 await t.context.app.teardown();
4238 });
4239 return ava;
4240 }
4241 async setup(context) {
4242 context.app = this;
4243 await this.start();
4244 let adapters = this.container.lookupAll('orm-adapter');
4245 let transactionInitializers = [];
4246 lodash_1.forEach(adapters, (Adapter) => {
4247 if (typeof Adapter.startTestTransaction === 'function') {
4248 transactionInitializers.push(Adapter.startTestTransaction());
4249 }
4250 });
4251 await bluebird_1.all(transactionInitializers);
4252 }
4253 async teardown() {
4254 let transactionRollbacks = [];
4255 let adapters = this.container.lookupAll('orm-adapter');
4256 lodash_1.forEach(adapters, (Adapter) => {
4257 if (typeof Adapter.rollbackTestTransaction === 'function') {
4258 transactionRollbacks.push(Adapter.rollbackTestTransaction());
4259 }
4260 });
4261 await bluebird_1.all(transactionRollbacks);
4262 await this.shutdown();
4263 }
4264 /**
4265 * Start the application (note: this won't actually start the HTTP server, but performs all the
4266 * other startup work for you).
4267 *
4268 * @since 0.1.0
4269 */
4270 async start() {
4271 await this.application.runInitializers();
4272 }
4273 /**
4274 * Submit a simulated HTTP request to the application.
4275 *
4276 * @since 0.1.0
4277 */
4278 async request(options) {
4279 let body = null;
4280 options.headers = lodash_1.mapKeys(options.headers, (value, key) => key.toLowerCase()) || {};
4281 if (options.body) {
4282 body = typeof options.body === 'string' ? options.body : JSON.stringify(options.body);
4283 options.headers['transfer-encoding'] = 'chunked';
4284 }
4285 let req = new mock_request_1.default({
4286 method: options.method.toUpperCase(),
4287 url: options.url,
4288 headers: lodash_1.assign({}, this.headers, options.headers)
4289 });
4290 return new Promise((resolve, reject) => {
4291 let res = new mock_response_1.default(({ status, body, json }) => {
4292 if (status < 500) {
4293 resolve({ status: res.statusCode, body: json || body });
4294 }
4295 else {
4296 reject({ response: res, status, body, json });
4297 }
4298 });
4299 // tslint:disable-next-line:no-floating-promises
4300 this.application.router.handle(req, res);
4301 let SIMULATED_WRITE_DELAY = 10;
4302 setTimeout(() => {
4303 if (body) {
4304 req.write(body);
4305 }
4306 req.end();
4307 }, SIMULATED_WRITE_DELAY);
4308 });
4309 }
4310 /**
4311 * Send a simulated GET request
4312 *
4313 * @since 0.1.0
4314 */
4315 async get(url, options = {}) {
4316 return this.request(Object.assign(options, { url, method: 'GET' }));
4317 }
4318 /**
4319 * Send a simulated HEAD request
4320 *
4321 * @since 0.1.0
4322 */
4323 async head(url, options = {}) {
4324 return this.request(Object.assign(options, { url, method: 'HEAD' }));
4325 }
4326 /**
4327 * Send a simulated DELETE request
4328 *
4329 * @since 0.1.0
4330 */
4331 async delete(url, options = {}) {
4332 return this.request(Object.assign(options, { url, method: 'DELETE' }));
4333 }
4334 /**
4335 * Send a simulated POST request
4336 *
4337 * @since 0.1.0
4338 */
4339 async post(url, body, options = {}) {
4340 return this.request(Object.assign(options, { url, body, method: 'POST' }));
4341 }
4342 /**
4343 * Send a simulated PUT request
4344 *
4345 * @since 0.1.0
4346 */
4347 async put(url, body, options = {}) {
4348 return this.request(Object.assign(options, { url, body, method: 'PUT' }));
4349 }
4350 /**
4351 * Send a simulated PATCH request
4352 *
4353 * @since 0.1.0
4354 */
4355 async patch(url, body, options = {}) {
4356 return this.request(Object.assign(options, { url, body, method: 'PATCH' }));
4357 }
4358 /**
4359 * Get the current value of a default header
4360 *
4361 * @since 0.1.0
4362 */
4363 getHeader(name) {
4364 name = name.toLowerCase();
4365 return this.headers[name];
4366 }
4367 /**
4368 * Set a default header value
4369 *
4370 * @since 0.1.0
4371 */
4372 setHeader(name, value) {
4373 this.headers[name.toLowerCase()] = value;
4374 }
4375 /**
4376 * Remove a default header value
4377 *
4378 * @since 0.1.0
4379 */
4380 removeHeader(name) {
4381 delete this.headers[name.toLowerCase()];
4382 }
4383 /**
4384 * Lookup an entry in the test application container
4385 *
4386 * @since 0.1.0
4387 */
4388 lookup(name) {
4389 return this.container.lookup(name);
4390 }
4391 /**
4392 * Overwrite an entry in the test application container. Use `restore()` to
4393 * restore the original container entry later.
4394 *
4395 * @since 0.1.0
4396 */
4397 inject(name, value, options) {
4398 this._injections[name] = this.container.lookup(name);
4399 this.container.register(name, value, options);
4400 this.container.clearCache(name);
4401 }
4402 /**
4403 * Restore the original container entry for an entry that was previously
4404 * overwritten by `inject()`
4405 *
4406 * @since 0.1.0
4407 */
4408 restore(name) {
4409 this.container.register(name, this._injections[name]);
4410 delete this._injections[name];
4411 }
4412 /**
4413 * Shut down the test application, cleaning up any resources in use
4414 *
4415 * @since 0.1.0
4416 */
4417 async shutdown() {
4418 await this.application.shutdown();
4419 }
4420}
4421exports.AcceptanceTest = AcceptanceTest;
4422exports.default = AcceptanceTest.setupTest.bind(AcceptanceTest);
4423
4424});
4425
4426
4427
4428// lib/test/mock-request.js
4429//========================================
4430
4431loader.add('/lib/test/mock-request.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
4432
4433"use strict";
4434Object.defineProperty(exports, "__esModule", { value: true });
4435const stream_1 = require("stream");
4436const url = require("url");
4437const net_1 = require("net");
4438const lodash_1 = require("lodash");
4439/**
4440 * A mock request used to simluate an HTTP request to the application during
4441 * tests. You shouldn't need to instantiate these directly - instead, use an
4442 * AppAcceptance test.
4443 *
4444 * @package test
4445 */
4446class MockRequest extends stream_1.PassThrough {
4447 constructor(options = {}) {
4448 super();
4449 this.httpVersion = '1.1';
4450 this.headers = {};
4451 this.method = 'GET';
4452 this.url = '/';
4453 this.trailers = {};
4454 this.readable = true;
4455 this.method = options.method || this.method;
4456 let parsedUrl = url.parse(options.url || this.url);
4457 this.url = parsedUrl.path;
4458 if (options.headers) {
4459 this.headers = lodash_1.mapKeys(options.headers, (value, key) => key.toLowerCase());
4460 }
4461 if (options.trailers) {
4462 this.trailers = lodash_1.mapKeys(options.trailers, (value, key) => key.toLowerCase());
4463 }
4464 this.httpVersion = options.httpVersion || this.httpVersion;
4465 this.connection = new net_1.Socket();
4466 Object.defineProperty(this.connection, 'remoteAddress', {
4467 value: '192.168.1.1'
4468 });
4469 Object.defineProperty(this.connection, 'encrypted', {
4470 get: () => {
4471 return parsedUrl.protocol === 'https:';
4472 }
4473 });
4474 let json = options.json;
4475 if (json) {
4476 options.body = JSON.stringify(options.json);
4477 this.headers['content-type'] = this.headers['content-type'] || 'application/json';
4478 }
4479 let body = options.body;
4480 if (body) {
4481 if (isReadableStream(body)) {
4482 body.pipe(this);
4483 }
4484 else {
4485 if (!this.headers['content-length']) {
4486 this.headers['content-length'] = String(body.length);
4487 }
4488 this.write(body);
4489 this.end();
4490 }
4491 }
4492 }
4493 get httpVersionMajor() {
4494 return Number(this.httpVersion.split('.')[0]);
4495 }
4496 get httpVersionMinor() {
4497 return Number(this.httpVersion.split('.')[1]);
4498 }
4499 get socket() {
4500 return this.connection;
4501 }
4502 get rawHeaders() {
4503 return lodash_1.flatMapDeep(this.headers, (value, name) => {
4504 if (Array.isArray(value)) {
4505 return value.map((v) => [name, v]);
4506 }
4507 return [name, value];
4508 });
4509 }
4510 get rawTrailers() {
4511 return lodash_1.flatten(lodash_1.toPairs(this.trailers));
4512 }
4513 setTimeout(msecs, callback) {
4514 return this;
4515 }
4516 destroy() {
4517 // noop
4518 }
4519}
4520exports.default = MockRequest;
4521function isReadableStream(stream) {
4522 return typeof stream.pipe === 'function';
4523}
4524
4525});
4526
4527
4528
4529// lib/test/mock-response.js
4530//========================================
4531
4532loader.add('/lib/test/mock-response.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
4533
4534"use strict";
4535Object.defineProperty(exports, "__esModule", { value: true });
4536const stream_1 = require("stream");
4537const net_1 = require("net");
4538const http_1 = require("http");
4539/**
4540 * A mock response used to simluate the server response to mock requests during
4541 * tests. You shouldn't need to instantiate these directly - instead, use an
4542 * AppAcceptance test.
4543 *
4544 * @package test
4545 */
4546// tslint:disable:completed-docs member-access
4547class MockResponse extends stream_1.Writable {
4548 constructor(callback) {
4549 super();
4550 this._headers = {};
4551 // Settings
4552 this.upgrading = false;
4553 this.chunkedEncoding = false;
4554 this.shouldKeepAlive = false;
4555 this.useChunkedEncodingByDefault = false;
4556 this.sendDate = true;
4557 // Response state
4558 this.finished = false;
4559 this.headersSent = false;
4560 this.connection = new net_1.Socket();
4561 this._body = '';
4562 this.on('finish', () => {
4563 try {
4564 this._json = JSON.parse(this._body);
4565 }
4566 catch (e) { }
4567 if (callback) {
4568 callback({ status: this.statusCode, body: this._body, json: this._json });
4569 }
4570 });
4571 }
4572 get statusMessage() {
4573 return this._customStatusMessage || http_1.STATUS_CODES[this.statusCode];
4574 }
4575 write(chunk, encoding, cb) {
4576 if (Buffer.isBuffer(chunk)) {
4577 chunk = chunk.toString();
4578 }
4579 this._body += chunk;
4580 if (cb) {
4581 setImmediate(cb);
4582 }
4583 return true;
4584 }
4585 // Outgoing Message interface
4586 _implicitHeader() { }
4587 setHeader(name, value) {
4588 this._headers[name] = value;
4589 }
4590 getHeader(name) {
4591 return this._headers[name];
4592 }
4593 getHeaders() {
4594 return this._headers;
4595 }
4596 getHeaderNames() {
4597 return Object.keys(this._headers);
4598 }
4599 hasHeader(name) {
4600 return Boolean(this._headers[name]);
4601 }
4602 removeHeader(name) {
4603 delete this._headers[name];
4604 }
4605 addTrailers(headers) {
4606 throw new Error('Trailing headers are not supported on requests without chunked encoding, and MockResponse does not support chunked encoding yet.');
4607 }
4608 flushHeaders() { }
4609 writeHead(statusCode, statusMessage, headers) {
4610 this.statusCode = statusCode;
4611 if (typeof statusMessage === 'string') {
4612 this._customStatusMessage = statusMessage;
4613 Object.assign(this._headers, headers);
4614 }
4615 else {
4616 Object.assign(this._headers, statusMessage);
4617 }
4618 }
4619 writeContinue() { }
4620 assignSocket(socket) { }
4621 detachSocket(socket) { }
4622 setTimeout(msecs, callback) {
4623 this.connection.setTimeout(msecs, callback);
4624 return this;
4625 }
4626}
4627exports.default = MockResponse;
4628
4629});
4630
4631
4632
4633// lib/test/unit-test.js
4634//========================================
4635
4636loader.add('/lib/test/unit-test.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
4637
4638"use strict";
4639Object.defineProperty(exports, "__esModule", { value: true });
4640const bluebird_1 = require("bluebird");
4641const lodash_1 = require("lodash");
4642/**
4643 * The AppUnitTest class represents an app unit test. Loads up the bundle and
4644 * lookups up the module under test. The bundle allows for multiple concurrent
4645 * tests while ensuring state is not shared across them.
4646 *
4647 * @package test
4648 * @since 0.1.0
4649 */
4650class UnitTest {
4651 constructor(_subject, overrideConfig, options = {}) {
4652 this._subject = _subject;
4653 this.overrideConfig = overrideConfig;
4654 this.originalContainerValues = {};
4655 // Fetch the container for this unit test file (added during the build process)
4656 this.container = global.unitTestBundleContainer;
4657 // If the subject is pulled from the container, make sure we preserve it's value
4658 if (typeof _subject === 'string') {
4659 overrideConfig[_subject] = true;
4660 }
4661 this.applyContainerOverrideConfig(overrideConfig, options.clearContainer);
4662 }
4663 /**
4664 * A helper method for setting up an app unit test. Adds beforeEach/afterEach
4665 * hooks to the ava test suite which will setup and teardown the unit test.
4666 * They also setup a test transaction and roll it back once the test is
4667 * finished (for the ORM adapters that support it), so your test data won't
4668 * pollute the database.
4669 *
4670 * It returns the Ava test interface, but it enforces serial execution. For
4671 * more details, check out
4672 * https://gist.github.com/davewasmer/cd8ac4fad5502e9ce5c8055b283f08cb
4673 *
4674 * @since 0.1.0
4675 */
4676 static setupTest(subject = () => null, overrides = {}, options = {}) {
4677 let ava = require('ava');
4678 let unitTest = new this(subject, overrides, options);
4679 ava.beforeEach(async (t) => await unitTest.setup(t.context));
4680 ava.afterEach.always(async (t) => await unitTest.teardown());
4681 return ava.serial;
4682 }
4683 async setup(context) {
4684 // Setup the container with the "allowed world" for this test suite
4685 lodash_1.forEach(this.startingContainerValues, (value, specifier) => {
4686 this.container.register(specifier, value);
4687 });
4688 // Some shortcuts on the context object
4689 context.unit = this;
4690 context.container = this.container;
4691 context.subject = this.subject.bind(this);
4692 context.inject = this.inject.bind(this);
4693 context.restore = this.restore.bind(this);
4694 context.lookup = this.container.lookup.bind(this.container);
4695 // Start any database transactions we can
4696 await this.startTestTransactions();
4697 }
4698 /**
4699 * Takes the supplied override config, and updates the bundle container to
4700 * match it.
4701 *
4702 * @param overrideConfig Container overrides that should be used for this
4703 * test (see ContainerOverrideConfig)
4704 */
4705 applyContainerOverrideConfig(overrideConfig, clearContainer) {
4706 // Before we potentially wipe the container, grab any "passthrough" entries
4707 // allowed by the config
4708 this.startingContainerValues = lodash_1.mapValues(overrideConfig, (config, key) => {
4709 return config === true ? this.container.lookup(key, { raw: true, loose: true }) : config;
4710 });
4711 if (clearContainer !== false) {
4712 this.container.clear();
4713 }
4714 }
4715 /**
4716 * Returns the subject of the test. Follows container lookup rules, i.e. if
4717 * the entry under test is a singleton, will return the singleton instance,
4718 * not the class.
4719 *
4720 * @since 0.1.0
4721 */
4722 subject() {
4723 if (typeof this._subject === 'string') {
4724 return this.container.lookup(this._subject);
4725 }
4726 return this._subject();
4727 }
4728 inject(nameOrInjections, value, options) {
4729 let injections;
4730 if (typeof nameOrInjections === 'string') {
4731 let name = nameOrInjections;
4732 injections = { [name]: { value, options } };
4733 }
4734 else {
4735 injections = lodash_1.mapValues(nameOrInjections, (value) => ({ value }));
4736 }
4737 lodash_1.forEach(injections, ({ value, options }, specifier) => {
4738 this.originalContainerValues[specifier] = this.container.lookup(specifier, { raw: true, loose: true });
4739 this.container.register(specifier, value, options);
4740 this.container.clearCache(specifier);
4741 });
4742 }
4743 /**
4744 * Restore the original container entry for an entry that was previously
4745 * overwritten by `inject()`. If no arguments are supplied, all injections
4746 * are restored.
4747 *
4748 * @since 0.1.0
4749 */
4750 restore(...specifiers) {
4751 if (specifiers.length === 0) {
4752 specifiers = Object.keys(this.originalContainerValues);
4753 }
4754 specifiers.forEach((specifier) => {
4755 this.container.clearCache(specifier);
4756 let originalValue = this.originalContainerValues[specifier];
4757 if (originalValue != null) {
4758 this.container.register(specifier, originalValue);
4759 }
4760 delete this.originalContainerValues[specifier];
4761 });
4762 }
4763 /**
4764 * Lookup all the ORM adapters, and give each one a chance to start a
4765 * transaction that will wrap a test, and be rolled back after
4766 */
4767 async startTestTransactions() {
4768 let adapters = this.container.lookupAll('orm-adapter');
4769 let transactionInitializers = [];
4770 lodash_1.forEach(adapters, (Adapter) => {
4771 if (typeof Adapter.startTestTransaction === 'function') {
4772 transactionInitializers.push(Adapter.startTestTransaction());
4773 }
4774 });
4775 await bluebird_1.all(transactionInitializers);
4776 }
4777 /**
4778 * Roll back any test transactions started at the beginning of the test
4779 */
4780 async rollbackTestTransactions() {
4781 let transactionRollbacks = [];
4782 let adapters = this.container.lookupAll('orm-adapter');
4783 lodash_1.forEach(adapters, (Adapter) => {
4784 if (typeof Adapter.rollbackTestTransaction === 'function') {
4785 transactionRollbacks.push(Adapter.rollbackTestTransaction());
4786 }
4787 });
4788 await bluebird_1.all(transactionRollbacks);
4789 }
4790 async teardown() {
4791 this.container.clear();
4792 await this.rollbackTestTransactions();
4793 }
4794}
4795exports.UnitTest = UnitTest;
4796exports.default = UnitTest.setupTest.bind(UnitTest);
4797
4798});
4799
4800
4801
4802// lib/utils/json-api.js
4803//========================================
4804
4805loader.add('/lib/utils/json-api.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
4806
4807"use strict";
4808Object.defineProperty(exports, "__esModule", { value: true });
4809
4810});
4811
4812
4813
4814// lib/utils/result.js
4815//========================================
4816
4817loader.add('/lib/utils/result.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
4818
4819"use strict";
4820Object.defineProperty(exports, "__esModule", { value: true });
4821function result(valueOrFn, ...args) {
4822 if (typeof valueOrFn === 'function') {
4823 return valueOrFn(...args);
4824 }
4825 else {
4826 return valueOrFn;
4827 }
4828}
4829exports.default = result;
4830
4831});
4832
4833
4834
4835// lib/utils/rewrap.js
4836//========================================
4837
4838loader.add('/lib/utils/rewrap.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
4839
4840"use strict";
4841Object.defineProperty(exports, "__esModule", { value: true });
4842const denali_cli_1 = require("denali-cli");
4843const wrap = require("wordwrap");
4844/**
4845 * Take the tagged string and change the word wrapping to 100 columns (or the width of the terminal
4846 * window, if smaller). Useful for writing paragraphs of text that should wrap in the source code,
4847 * but may need to wrap to a different width when printed out to the terminal.
4848 *
4849 * @package util
4850 */
4851function rewrap(strings, ...expressions) {
4852 let text = denali_cli_1.unwrap(strings, ...expressions);
4853 text = wrap(text, Math.min(100, process.stdout.columns));
4854 return text;
4855}
4856exports.default = rewrap;
4857
4858});
4859
4860
4861
4862// lib/utils/set-if-not-empty.js
4863//========================================
4864
4865loader.add('/lib/utils/set-if-not-empty.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
4866
4867"use strict";
4868Object.defineProperty(exports, "__esModule", { value: true });
4869const lodash_1 = require("lodash");
4870function setIfNotEmpty(obj, key, value) {
4871 if (lodash_1.isArray(value) || !lodash_1.isEmpty(value)) {
4872 lodash_1.set(obj, key, value);
4873 }
4874}
4875exports.default = setIfNotEmpty;
4876
4877});
4878
4879
4880
4881// lib/utils/strings.js
4882//========================================
4883
4884loader.add('/lib/utils/strings.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
4885
4886"use strict";
4887Object.defineProperty(exports, "__esModule", { value: true });
4888const dedent = require("dedent-js");
4889const util_1 = require("util");
4890exports.default = {
4891 ////////////
4892 // Errors //
4893 ////////////
4894 ContainerEntryNotFound(specifier, registryEntries, resolverNames) {
4895 let registrationsOverview;
4896 if (registryEntries.length > 0) {
4897 registrationsOverview = dedent `
4898 Available manual registrations (via container.register(...)):
4899 - ${registryEntries.join('\n - ')}
4900 `;
4901 }
4902 else {
4903 registrationsOverview = dedent `
4904 There were no manually registered entries in the container.
4905 `;
4906 }
4907 let resolversOverview;
4908 if (resolverNames.length > 0) {
4909 resolversOverview = dedent `
4910 Available resolvers:
4911 - ${resolverNames.join('\n - ')}
4912 `;
4913 }
4914 else {
4915 resolversOverview = dedent `
4916 There were no resolvers available in the container.
4917 `;
4918 }
4919 return dedent `
4920 You tried to lookup a container entry under ${specifier}, but the
4921 container has no such entry.
4922
4923 ${registrationsOverview}
4924
4925 ${resolversOverview}
4926
4927 Run with DEBUG=verbose-denali:resolver:<resolver name> to trace a specific
4928 resolver's resolution
4929 `;
4930 },
4931 ContainerEntryNotAConstructor(specifier, value) {
4932 let str = dedent `
4933 You flagged ${specifier} as a singleton, so the container expected to
4934 a constructor function under that entry.
4935 `;
4936 if (value === undefined) {
4937 str += dedent `
4938 Instead it found 'undefined'. Did you forget to add 'export default'
4939 to a file?
4940 `;
4941 }
4942 else {
4943 str += dedent `
4944 Instead it found:
4945
4946 ${util_1.inspect(value)}
4947 `;
4948 }
4949 return str;
4950 }
4951};
4952
4953});
4954
4955
4956
4957// lib/utils/topsort.js
4958//========================================
4959
4960loader.add('/lib/utils/topsort.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
4961
4962"use strict";
4963Object.defineProperty(exports, "__esModule", { value: true });
4964const dag_map_1 = require("dag-map");
4965/**
4966 * Take an array of vertices (objects with a name, value, and optional before / after), create a
4967 * directed acyclic graph of them, and return the vertex values in a sorted array.
4968 *
4969 * @package util
4970 */
4971function topsort(items, options = {}) {
4972 let graph = new dag_map_1.default();
4973 items.forEach((item) => {
4974 let value = options.valueKey ? item[options.valueKey] : item;
4975 graph.add(item.name, value, item.before, item.after);
4976 });
4977 let sorted = [];
4978 graph.topsort((key, value) => {
4979 sorted.push(value);
4980 });
4981 return sorted;
4982}
4983exports.default = topsort;
4984
4985});
4986
4987
4988
4989// lib/utils/types.js
4990//========================================
4991
4992loader.add('/lib/utils/types.js', {"isMain":false}, function(module, exports, require, __dirname, __filename) {
4993
4994"use strict";
4995Object.defineProperty(exports, "__esModule", { value: true });
4996
4997});
4998
4999
5000
5001 });
5002})();
5003
5004//# sourceMappingURL=denali.fragment.map