alchemymvc 1.1.9 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1780 +1,1794 @@
1
- 'use strict';
2
-
3
- var shared_objects = {},
4
- plugModules = null,
5
- usedModules = {},
6
- useErrors = {},
7
- usePaths = {},
8
- ac_entries = {},
9
- parseArgs = require('minimist'),
10
- libpath = require('path'),
11
- colors = require('ansi-256-colors'),
12
- fs = require('fs');
13
-
14
- /**
15
- * The Alchemy class
16
- *
17
- * @constructor
18
- *
19
- * @author Jelle De Loecker <jelle@develry.be>
20
- * @since 0.0.1
21
- * @version 1.1.0
22
- */
23
- global.Alchemy = Function.inherits('Informer', 'Alchemy', function Alchemy() {
24
-
25
- var that = this,
26
- package_json;
27
-
28
- // Only allow a single instance of the Alchemy class
29
- if (global.alchemy) {
30
- return global.alchemy;
31
- }
32
-
33
- // Timestamp when alchemy started
34
- this.start_time = Date.now();
35
-
36
- // Current working directory
37
- this.cwd = process.cwd();
38
-
39
- // Parsed arguments
40
- this.argv = parseArgs(process.argv.slice(2));
41
-
42
- // The id of this server instance
43
- this.discovery_id = Crypto.pseudoHex();
44
-
45
- // Link to the colors module
46
- this.colors = colors;
47
-
48
- // The session count
49
- this.session_count = 0;
50
-
51
- // Plugins to be loaded will be stored in here, with their options
52
- this.plugins = {};
53
-
54
- // Certain required modules can be registered under a name
55
- this.modules = {};
56
-
57
- // Link to all used modules
58
- this.modules_loaded = usedModules;
59
-
60
- // Link to failed modules
61
- this.modules_error = useErrors;
62
-
63
- // Try getting the app package.json file
64
- try {
65
- package_json = require(libpath.resolve(PATH_ROOT, 'package.json'));
66
- } catch (err) {
67
- package_json = {};
68
- }
69
-
70
- // The app package.json as an object
71
- this.package = package_json;
72
-
73
- // Now get the alchemymvc package.json file
74
- try {
75
- package_json = require(libpath.resolve(PATH_CORE, '..', 'package.json'));
76
- } catch (err) {
77
- package_json = {};
78
- }
79
-
80
- // Get the alchemy core version
81
- this.version = package_json.version;
82
-
83
- // Keep status
84
- this.status = {};
85
-
86
- // All caches
87
- this.caches = {};
88
-
89
- // Also store the version in the process versions object
90
- process.versions.alchemy = this.version;
91
-
92
- // Also store the version of the app
93
- process.versions.alchemy_app = this.package.version;
94
-
95
- // Load the settings
96
- this.loadSettings();
97
-
98
- // Listen to messages from parent processes
99
- process.on('message', function gotMessage(message) {
100
- if (typeof message == 'string') {
101
- return that.emit(message);
102
- }
103
-
104
- if (message && message.type) {
105
- return that.emit(message.type, message.data);
106
- }
107
- });
108
-
109
- // Get Janeway
110
- this.Janeway = this.use('janeway');
111
-
112
- // Asign the Janeway levels
113
- Object.assign(this, this.Janeway.LEVELS);
114
-
115
- try {
116
- if (this.argv['stream-janeway']) {
117
- this.startJaneway({stream: true});
118
- } else if (this.allow_janeway) {
119
- this.startJaneway();
120
- }
121
- } catch (err) {
122
- log.warn('Failed to start Janeway:', err);
123
- }
124
- });
125
-
126
- /**
127
- * See if running janeway is allowed
128
- *
129
- * @author Jelle De Loecker <jelle@develry.be>
130
- * @since 0.5.0
131
- * @version 0.5.0
132
- *
133
- * @type {Boolean}
134
- */
135
- Alchemy.prepareProperty(function allow_janeway() {
136
-
137
- // Setting the --disable-janeway flag explicitly disabled ALL forms of janeway
138
- if (this.argv['disable-janeway'] || process.env.DISABLE_JANEWAY) {
139
- return false;
140
- }
141
-
142
- // You can also disable janeway in the settings
143
- if (this.settings.janeway === false) {
144
- return false;
145
- }
146
-
147
- if (Blast.isNW || !process.stdout.isTTY) {
148
- return false;
149
- }
150
-
151
- return true;
152
- });
153
-
154
- /**
155
- * Expirable object where sessions are stored
156
- *
157
- * @author Jelle De Loecker <jelle@develry.be>
158
- * @since 0.5.0
159
- * @version 1.0.4
160
- *
161
- * @type {Develry.Cache}
162
- */
163
- Alchemy.prepareProperty(function sessions() {
164
-
165
- var cache = this.getCache('sessions', {
166
- max_idle : alchemy.settings.session_length,
167
- max_length : Infinity
168
- });
169
-
170
- cache.on('removed', function onRemoved(value, key) {
171
- // @TODO: check if expired?
172
- value.removed();
173
- });
174
-
175
- return cache;
176
- });
177
-
178
- /**
179
- * Expirable object where sessions are temporarily stored
180
- * based on the browser fingerprints
181
- *
182
- * @author Jelle De Loecker <jelle@develry.be>
183
- * @since 1.1.0
184
- * @version 1.1.0
185
- *
186
- * @type {Develry.Cache}
187
- */
188
- Alchemy.prepareProperty(function fingerprints() {
189
-
190
- var cache = this.getCache('fingerprints', {
191
- max_idle : '3 minutes',
192
- max_length : 3000
193
- });
194
-
195
- return cache;
196
- });
197
-
198
- /**
199
- * Get or set the environment
200
- *
201
- * @author Jelle De Loecker <jelle@develry.be>
202
- * @since 0.4.0
203
- * @version 0.4.0
204
- *
205
- * @type {String}
206
- */
207
- Alchemy.setProperty(function environment() {
208
- return alchemy.settings.environment;
209
- }, function set_environment(value) {
210
- alchemy.settings.environment = String(value);
211
- return alchemy.settings.environment;
212
- });
213
-
214
- /**
215
- * Start janeway
216
- *
217
- * @author Jelle De Loecker <jelle@develry.be>
218
- * @since 0.5.0
219
- * @version 1.1.0
220
- *
221
- * @param {Object} options
222
- */
223
- Alchemy.setMethod(function startJaneway(options) {
224
-
225
- if (this.Janeway.started) {
226
- return;
227
- }
228
-
229
- if (!options) {
230
- options = {};
231
- }
232
-
233
- if (options.stream) {
234
- let that = this,
235
- screen,
236
- out;
237
-
238
- out = new require('net').Socket({fd: 4, writable: true});
239
-
240
- out.columns = 80;
241
- out.rows = 24;
242
-
243
- screen = this.Janeway.createScreen({
244
- input : process.stdin,
245
- terminal : 'xterm-256color',
246
- output : out
247
- });
248
-
249
- options.screen = screen;
250
-
251
- // Also output to stdout
252
- options.output_to_stdout = true;
253
-
254
- // Keep regular stdout color
255
- options.keep_color = true;
256
-
257
- // Don't mess with the indentation
258
- options.change_indent = false;
259
-
260
- this.on('janeway_propose_geometry', function onProposeGeometry(data) {
261
- out.columns = data.cols || data.width;
262
- out.rows = data.rows || data.height;
263
- out.emit('resize');
264
- });
265
-
266
- screen.on('resize', function onResize(a, b) {
267
- that.Janeway.redraw();
268
- });
269
-
270
- this.on('janeway_redraw', function onRedrawRequest() {
271
- that.Janeway.redraw();
272
- });
273
- }
274
-
275
- this.Janeway.started = true;
276
- this.Janeway.start(options);
277
-
278
- if (this.settings.title) {
279
- let title = this.settings.title;
280
-
281
- if (this.settings.titleized) {
282
- title = 'Alchemy: ' + title;
283
- }
284
-
285
- this.Janeway.setTitle(title);
286
- }
287
-
288
- if (this.settings.session_menu) {
289
-
290
- let session_menu = this.Janeway.addIndicator('⌨ ');
291
-
292
- if (!session_menu.addItem) {
293
- return session_menu.remove();
294
- }
295
-
296
- this.Janeway.session_menu = session_menu;
297
- }
298
-
299
- });
300
-
301
- /**
302
- * Log messages of level 5 (info)
303
- *
304
- * @author Jelle De Loecker <jelle@develry.be>
305
- * @since 0.0.1
306
- * @version 0.4.0
307
- */
308
- Alchemy.setMethod(function log(...args) {
309
- return alchemy.printLog(5, args, {level: 3});
310
- });
311
-
312
- /**
313
- * Actually print a log message
314
- *
315
- * @author Jelle De Loecker <jelle@develry.be>
316
- * @since 0.4.0
317
- * @version 1.1.0
318
- *
319
- * @param {Number} level
320
- * @param {Array} args
321
- * @param {Object} options
322
- */
323
- Alchemy.setMethod(function printLog(level, args, options) {
324
-
325
- var type,
326
- line;
327
-
328
- if (this.settings.silent) {
329
- return;
330
- }
331
-
332
- if (!Array.isArray(args)) {
333
- args = [args];
334
- }
335
-
336
- if (typeof level == 'string') {
337
- type = level;
338
- } else {
339
- if (level < 3) {
340
- type = 'error';
341
- } else if (level < 5) {
342
- type = 'warn';
343
- } else {
344
- type = 'info';
345
- }
346
- }
347
-
348
- if (this.Janeway != null) {
349
- line = this.Janeway.print(type, args, options);
350
-
351
- if (options && options.gutter && line) {
352
- line.setGutter(options.gutter)
353
- }
354
-
355
- return line;
356
- } else {
357
- console[type](...args);
358
- }
359
- });
360
-
361
- /**
362
- * Load the settings
363
- *
364
- * @author Jelle De Loecker <jelle@develry.be>
365
- * @since 0.4.0
366
- * @version 1.1.6
367
- */
368
- Alchemy.setMethod(function loadSettings() {
369
-
370
- var default_path,
371
- port_error,
372
- local_path,
373
- env_config,
374
- env_path,
375
- settings,
376
- local,
377
- env;
378
-
379
- if (this.settings) {
380
- return;
381
- }
382
-
383
- // Create the settings object
384
- this.settings = settings = {};
385
-
386
- // Generate the path to the default settings file
387
- default_path = libpath.resolve(PATH_ROOT, 'app', 'config', 'default');
388
-
389
- // Get default settings
390
- try {
391
- Object.assign(settings, require(default_path));
392
- } catch (err) {
393
- settings.no_default_file = default_path;
394
- }
395
-
396
- // Generate the path to the local settings file
397
- local_path = libpath.resolve(PATH_ROOT, 'app', 'config', 'local');
398
-
399
- // Get the local settings
400
- try {
401
- local = require(local_path);
402
- } catch(err) {
403
- local = {};
404
- settings.no_local_file = local_path;
405
- }
406
-
407
- // Default to the 'dev' environment
408
- if (!local.environment) {
409
-
410
- if (process.env.ENV) {
411
- local.environment = process.env.ENV;
412
- } else {
413
- local.environment = 'dev';
414
- }
415
- }
416
-
417
- env = this.argv.env || this.argv.environment;
418
-
419
- if (env) {
420
- local.environment = env;
421
- this.printLog(this.INFO, ['Switching to environment', env]);
422
- }
423
-
424
- // Generate the path to the environment settings file
425
- env_path = libpath.resolve(PATH_APP, 'config', local.environment, 'config');
426
-
427
- // Get the config
428
- try {
429
- env_config = require(env_path);
430
- } catch(err) {
431
- env_config = {};
432
- settings.no_env_file = env_path;
433
- }
434
-
435
- // Merge all the settings in order: default - environment - local
436
- Object.merge(settings, env_config, local);
437
-
438
- if (!settings.name) {
439
- settings.name = this.package.name;
440
- }
441
-
442
- if (settings.title == null) {
443
- if (this.package.title) {
444
- // Allow users to set the title in their package file
445
- settings.title = this.package.title;
446
- } else if (settings.name) {
447
- settings.title = settings.name.replace(/-/g, ' ').titleize();
448
- settings.titleized = true;
449
- }
450
- }
451
-
452
- if (this.argv.port) {
453
- let port = parseInt(this.argv.port);
454
-
455
- if (port) {
456
- this.printLog(this.INFO, ['Using port setting from argument:', port]);
457
- settings.port = port;
458
- } else {
459
- this.argv.socket = this.argv.port;
460
- this.argv.port = null;
461
- }
462
- }
463
-
464
- if (this.argv.socket) {
465
- settings.port = false;
466
- settings.socket = this.argv.socket;
467
-
468
- let stat;
469
-
470
- try {
471
- stat = fs.statSync(settings.socket);
472
- } catch (err) {
473
- // Ignore if it doesn't exist yet
474
- }
475
-
476
- if (stat && stat.isDirectory()) {
477
- settings.socket = libpath.resolve(settings.socket, settings.name + '.alchemy.sock');
478
- }
479
-
480
- this.printLog(this.INFO, ['Using socket setting from argument:', settings.socket]);
481
- }
482
-
483
- if (!settings.port && settings.port !== false) {
484
- settings.port = 3000;
485
- }
486
-
487
- if (settings.port > 49151) {
488
- port_error = 'Could not use port number ' + String(port).bold.red + ' because ';
489
-
490
- // Make sure the port is valid
491
- if (settings.port > 65535) {
492
- this.printLog(this.FATAL, [port_error + 'there is no port higher than 65535. Please use ports below 49151.']);
493
- } else {
494
- this.printLog(this.FATAL, [port_error + 'it\'s an ephemeral port. Please use ports below 49151.']);
495
- }
496
-
497
- process.exit();
498
- }
499
-
500
- if (this.argv.url) {
501
- settings.url = this.argv.url;
502
- }
503
-
504
- if (settings.url && settings.url.indexOf('{') > -1) {
505
- settings.url = settings.url.assign(settings);
506
- }
507
-
508
- if (this.argv.preload) {
509
- settings.preload = this.argv.preload;
510
-
511
- if (Array.isArray(settings.preload)) {
512
- settings.preload = settings.preload.last();
513
- }
514
- }
515
-
516
- if (settings.preload == 'false') {
517
- settings.preload = false;
518
- }
519
-
520
- let key,
521
- val;
522
-
523
- for (key in this.argv) {
524
-
525
- if (!key.startsWith('override-')) {
526
- continue;
527
- }
528
-
529
- val = this.argv[key];
530
- key = key.after('override-');
531
-
532
- if (!settings[key] || typeof settings[key] != 'object') {
533
- settings[key] = val;
534
- } else {
535
- Object.merge(settings[key], val);
536
- }
537
- }
538
-
539
- if (settings.preload) {
540
- this.doPreload();
541
- }
542
-
543
- // Set the debug value
544
- global.DEBUG = settings.debug;
545
- });
546
-
547
- /**
548
- * Set status
549
- *
550
- * @author Jelle De Loecker <jelle@develry.be>
551
- * @since 0.4.1
552
- * @version 0.4.1
553
- */
554
- Alchemy.setMethod(function setStatus(name, value) {
555
- this.status[name] = value;
556
- });
557
-
558
- /**
559
- * Execute the function when alchemy is ready
560
- *
561
- * @author Jelle De Loecker <jelle@develry.be>
562
- * @since 0.0.1
563
- * @version 1.1.2
564
- *
565
- * @param {Function} callback The function to execute
566
- *
567
- * @return {Pledge}
568
- */
569
- Alchemy.setMethod(function ready(callback) {
570
-
571
- var that = this,
572
- pledge = new Pledge();
573
-
574
- pledge.done(callback);
575
-
576
- if (!this.sputnik) {
577
- Blast.loaded(function hasLoaded() {
578
- pledge.resolve(that.ready());
579
- });
580
- } else {
581
- this.sputnik.after(['start_server', 'datasources', 'listening'], function afterReady() {
582
- pledge.resolve();
583
- });
584
- }
585
-
586
- return pledge;
587
- });
588
-
589
- /**
590
- * Preload the client-side stuff
591
- *
592
- * @author Jelle De Loecker <jelle@elevenways.be>
593
- * @since 1.1.2
594
- * @version 1.1.2
595
- */
596
- Alchemy.setMethod(async function doPreload() {
597
-
598
- await this.ready();
599
-
600
- let url;
601
-
602
- if (this.settings.url) {
603
- url = this.settings.url;
604
- } else if (this.settings.port) {
605
- url = 'http://localhost:' + this.settings.port;
606
- }
607
-
608
- if (url) {
609
- log.info('Preloading url', url);
610
- Blast.fetch(url);
611
-
612
- url += '/hawkejs/hawkejs-client.js';
613
-
614
- log.info('Preloading client file via HTTP', url);
615
- Blast.fetch(url);
616
- } else {
617
- log.info('Preloading client file');
618
-
619
- Blast.getClientPath({
620
- modify_prototypes : true,
621
- create_source_map : alchemy.settings.debug,
622
- enable_coverage : !!global.__coverage__
623
- });
624
- }
625
- });
626
-
627
- /**
628
- * Resolve the provided arguments to a useable path string.
629
- * Only used strings, discards objects.
630
- *
631
- * @author Jelle De Loecker <jelle@develry.be>
632
- * @since 0.0.1
633
- * @version 0.4.0
634
- *
635
- * @param {String} path_to_dirs The path containing the dirs to load
636
- */
637
- Alchemy.setMethod(function pathResolve(...path_to_dirs) {
638
-
639
- var path_arguments,
640
- i;
641
-
642
- if (path_to_dirs.length == 1) {
643
- return path_to_dirs[0];
644
- }
645
-
646
- path_arguments = [];
647
-
648
- for (i = 0; i < path_to_dirs.length; i++) {
649
- if (typeof path_to_dirs[i] == 'string') {
650
- path_arguments.push(path_to_dirs[i]);
651
- }
652
- }
653
-
654
- if (path_arguments.length > 1) {
655
- return libpath.resolve(...path_arguments);
656
- } else {
657
- return path_arguments[0];
658
- }
659
- });
660
-
661
- /**
662
- * A wrapper function for requiring modules
663
- *
664
- * @author Jelle De Loecker <jelle@develry.be>
665
- * @since 0.0.1
666
- * @version 1.0.0
667
- *
668
- * @param {String} module_name The name/path of the module to load
669
- * @param {String} register_as Cache the module under this name
670
- * @param {Object} options Extra options
671
- * @param {Boolean} options.force Force a new requirement and do not cache
672
- *
673
- * @return {Object} The required module
674
- */
675
- Alchemy.setMethod(function use(module_name, register_as, options) {
676
-
677
- var module,
678
- result;
679
-
680
- if (typeof register_as == 'object') {
681
- options = register_as;
682
- register_as = false;
683
- }
684
-
685
- // Certain modules can be disabled by registering them as null
686
- if (module_name == null && register_as) {
687
- this.modules[register_as] = null;
688
- return null;
689
- }
690
-
691
- // If the module has explicitly been set to null, return that
692
- if (this.modules[module_name] === null) {
693
- return null;
694
- }
695
-
696
- if (typeof options == 'undefined') options = {};
697
- if (typeof options.force == 'undefined') options.force = false;
698
-
699
- // If a module has already been registered under this name, return that
700
- if (this.modules[module_name] && !options.force) {
701
- return this.modules[module_name];
702
- }
703
-
704
- if (this.argv['debug-requirements']) {
705
- this.printLog(this.DEBUG, ['Going to load module', module_name], {level: 2});
706
- }
707
-
708
- try {
709
- result = this.findModule(module_name, options);
710
- module = result.module;
711
- } catch (err) {
712
-
713
- if (!useErrors[module_name]) {
714
- useErrors[module_name] = 0;
715
- }
716
-
717
- useErrors[module_name]++;
718
-
719
- if (!options.silent || this.argv['debug-requirements']) {
720
- this.printLog(this.SEVERE, ['Failed to load module "' + module_name + '":', err.message], {level: 6, err: err});
721
- }
722
- return;
723
- }
724
-
725
- if (!usedModules[module_name]) {
726
-
727
- let entry = {
728
- internal : result.internal,
729
- loaded : 0
730
- };
731
-
732
- if (result.package) {
733
- entry.version = result.package.version;
734
- }
735
-
736
- usedModules[module_name] = result;
737
- }
738
-
739
- usedModules[module_name].loaded++;
740
-
741
- if (register_as) {
742
- this.modules[register_as] = module;
743
- }
744
-
745
- // If a new requirement needs to be forced, clear the cache
746
- if (options.force) {
747
- delete require.cache[result.module_path];
748
- return require(result.module_path);
749
- }
750
-
751
- return module;
752
- });
753
-
754
-
755
- /**
756
- * Look for a module by traversing the filesystem
757
- *
758
- * @author Jelle De Loecker <jelle@develry.be>
759
- * @since 0.0.1
760
- * @version 0.4.0
761
- *
762
- * @param {String} startPath The path to originate the search from
763
- * @param {String} moduleName
764
- * @param {Number} recurse
765
- *
766
- * @return {String}
767
- */
768
- Alchemy.setMethod(function searchModule(startPath, moduleName, recurse) {
769
-
770
- var moduledirs,
771
- module_path,
772
- entries,
773
- nmPath,
774
- temp,
775
- path,
776
- key,
777
- i;
778
-
779
- // Don't do this search if it hasn't been enabled
780
- // The new npm flat structure makes this an expensive thing to do
781
- if (!this.settings.search_for_modules) {
782
-
783
- // Set recurse to 3, so this is the first and last call
784
- recurse = 3;
785
-
786
- // Only add 2 folder to look through,
787
- // the alchemymvc node_modules folder
788
- // and the base node_modules folder
789
- moduledirs = ['..', libpath.resolve(startPath, 'node_modules', 'alchemymvc')];
790
-
791
- // Add plugin folders
792
- if (!plugModules) {
793
- path = libpath.resolve(PATH_ROOT, 'node_modules');
794
-
795
- if (fs.existsSync(path)) {
796
-
797
- // Get all the entries in the main modules folder
798
- entries = fs.readdirSync(libpath.resolve(PATH_ROOT, 'node_modules'));
799
-
800
- // Initiate the plugin modules variables
801
- plugModules = [];
802
-
803
-
804
- for (i = 0; i < entries.length; i++) {
805
- temp = entries[i];
806
-
807
- if (temp.startsWith('alchemy-')) {
808
- plugModules.push(libpath.resolve(PATH_ROOT, 'node_modules', temp));
809
- }
810
- }
811
- } else {
812
- plugModules = [];
813
- }
814
- }
815
-
816
- for (i = 0; i < plugModules.length; i++) {
817
- moduledirs.push(plugModules[i]);
818
- }
819
-
820
- } else if (!searchModule.have_warned) {
821
- searchModule.have_warned = true;
822
- log.warn('The "search_for_modules" config has been enabled!');
823
- }
824
-
825
- if (!recurse) {
826
- recurse = 1;
827
- }
828
-
829
- nmPath = libpath.resolve(startPath, 'node_modules');
830
-
831
- if (!moduledirs) {
832
- // Get all the entries inside the given path
833
- try {
834
- moduledirs = fs.readdirSync(nmPath);
835
- } catch(err) {
836
- return;
837
- }
838
- }
839
-
840
- // Look in the base node_modules directory first
841
- if (recurse == 1) {
842
- moduledirs.unshift('..');
843
- }
844
-
845
- // Go over every directory in the main node_modules folder
846
- for (i = 0; i < moduledirs.length; i++) {
847
-
848
- key = moduledirs[i];
849
-
850
- try {
851
- // Let require find the specific file to get
852
- module_path = require.resolve(libpath.resolve(nmPath, key, 'node_modules', moduleName));
853
-
854
- // If no errors have popped up now, we can break the for loop
855
- break;
856
-
857
- } catch(e) {
858
- // Do nothing
859
- }
860
- }
861
-
862
- if (!module_path && recurse < 3) {
863
- for (i = 0; i < moduledirs.length; i++) {
864
-
865
- module_path = this.searchModule(libpath.resolve(nmPath, moduledirs[i]), moduleName, recurse+1);
866
-
867
- if (module_path) {
868
- break;
869
- }
870
- }
871
- }
872
-
873
- return module_path;
874
- });
875
-
876
- /**
877
- * Find a module in our customized file structure
878
- *
879
- * @author Jelle De Loecker <jelle@develry.be>
880
- * @since 0.0.1
881
- * @version 1.1.0
882
- *
883
- * @param {String} moduleName
884
- * @param {Object} options
885
- *
886
- * @return {Object}
887
- */
888
- Alchemy.setMethod(function findModule(moduleName, options) {
889
-
890
- var package_json,
891
- module_path,
892
- internal,
893
- module,
894
- result,
895
- time,
896
- key,
897
- i;
898
-
899
- if (!options) {
900
- options = {};
901
- }
902
-
903
- if (options.require == null) {
904
- options.require = true;
905
- }
906
-
907
- // If we've required this once before, return it
908
- if (result = usePaths[moduleName]) {
909
- if (result.err) {
910
- throw result.err;
911
- }
912
-
913
- // Only return the cached module if it was required then,
914
- // or if require is now false
915
- if (result.module || !options.require) {
916
- return result;
917
- }
918
- }
919
-
920
- result = {
921
- err : null,
922
- module : null,
923
- module_dir : null,
924
- module_path : null,
925
- package : null,
926
- internal : null,
927
- search_time : null,
928
- };
929
-
930
- time = Date.now();
931
-
932
- // Simply try to resolve the module by name
933
- try {
934
- module_path = require.resolve(moduleName);
935
- } catch (err) {
936
- result.err = err;
937
- }
938
-
939
- // If that path wasn't found, look through the root node_modules
940
- if (result.err) {
941
- try {
942
- module_path = this.searchModule(PATH_ROOT, moduleName);
943
- } catch (err) {
944
- console.log(err);
945
- return;
946
- }
947
- }
948
-
949
- // If the module_path was found, actually require the module
950
- if (module_path) {
951
-
952
- // Modules are required by default
953
- if (options.require) {
954
- module = require(module_path);
955
- }
956
-
957
- // Get the package.json file
958
- if (~module_path.indexOf(libpath.sep)) {
959
- internal = false;
960
-
961
- let module_dir = libpath.dirname(module_path);
962
- result.module_dir = module_dir;
963
-
964
- try {
965
- package_json = require(libpath.resolve(module_dir, 'package.json'));
966
- } catch (err) {
967
- package_json = false;
968
- }
969
- } else {
970
- internal = true;
971
- package_json = {
972
- version: process.versions.node
973
- };
974
- }
975
- }
976
-
977
- if (!options.require || module) {
978
- result.err = null;
979
- }
980
-
981
- if (module) {
982
- result.module = module;
983
- }
984
-
985
- result.module_path = module_path;
986
- result.package = package_json;
987
- result.internal = internal;
988
-
989
- // Save the result
990
- usePaths[moduleName] = result;
991
-
992
- // If there was an error, throw it now
993
- if (result.err) {
994
- throw result.err;
995
- }
996
-
997
- result.search_time = Date.now() - time;
998
-
999
- // Else return the result
1000
- return result;
1001
- });
1002
-
1003
- /**
1004
- * Create a shared object
1005
- *
1006
- * @author Jelle De Loecker <jelle@develry.be>
1007
- * @since 0.0.1
1008
- * @version 0.4.0
1009
- *
1010
- * @param {String} name The name of the object to get
1011
- * @param {String} type The type to create (array or object)
1012
- *
1013
- * @return {Object|Array}
1014
- */
1015
- Alchemy.setMethod(function shared(name, type, value) {
1016
-
1017
- if (typeof type !== 'string') {
1018
- value = type;
1019
- type = 'object';
1020
- }
1021
-
1022
- // Create it if it doesn't exist
1023
- if (!shared_objects[name]) {
1024
- if (type === 'array' || type === 'Array') {
1025
- shared_objects[name] = value || [];
1026
- } else {
1027
- shared_objects[name] = value || {};
1028
- }
1029
- }
1030
-
1031
- return shared_objects[name];
1032
- });
1033
-
1034
- /**
1035
- * Get an object id,
1036
- * return undefined if no valid data was given (instead of throwing an error)
1037
- *
1038
- * @author Jelle De Loecker <jelle@develry.be>
1039
- * @since 0.0.1
1040
- * @version 0.4.0
1041
- *
1042
- * @param {String|ObjectID} obj
1043
- *
1044
- * @return {ObjectID|undefined}
1045
- */
1046
- Alchemy.setMethod(function castObjectId(obj) {
1047
-
1048
- var type = typeof obj;
1049
-
1050
- if (obj && type === 'object' && obj.constructor && obj.constructor.name === 'ObjectID') {
1051
- return obj;
1052
- } else if (type === 'string' && obj.isObjectId()) {
1053
- return alchemy.ObjectId(obj);
1054
- }
1055
-
1056
- return undefined;
1057
- });
1058
-
1059
- /**
1060
- * See if the given object is a stream
1061
- *
1062
- * @author Jelle De Loecker <jelle@develry.be>
1063
- * @since 0.2.0
1064
- * @version 1.0.5
1065
- *
1066
- * @return {Boolean}
1067
- */
1068
- Alchemy.setMethod(function isStream(obj) {
1069
- return obj && (typeof obj._read == 'function' || typeof obj._write == 'function') && typeof obj.on === 'function';
1070
- });
1071
-
1072
- /**
1073
- * Get or create a new cache instance
1074
- *
1075
- * @author Jelle De Loecker <jelle@develry.be>
1076
- * @since 1.0.0
1077
- * @version 1.0.4
1078
- *
1079
- * @param {String} name
1080
- * @param {Number|Object} options
1081
- *
1082
- * @return {Develry.Cache}
1083
- */
1084
- Alchemy.setMethod(function getCache(name, options) {
1085
-
1086
- var instance,
1087
- duration,
1088
- config,
1089
- type;
1090
-
1091
- if (this.caches[name]) {
1092
- return this.caches[name];
1093
- }
1094
-
1095
- if (!options) {
1096
- options = {};
1097
- }
1098
-
1099
- type = typeof options;
1100
-
1101
- if (type == 'number' || type == 'string') {
1102
- options = {
1103
- max_age : options,
1104
- };
1105
- }
1106
-
1107
- config = Object.assign({
1108
- max_length : 5000,
1109
- }, options);
1110
-
1111
- // @TODO: Fixed in 0.6.1
1112
- instance = new Blast.Classes.Develry.Cache();
1113
- Object.assign(instance, config);
1114
-
1115
- this.caches[name] = instance;
1116
-
1117
- return instance;
1118
- });
1119
-
1120
- /**
1121
- * Get a route
1122
- *
1123
- * @author Jelle De Loecker <jelle@elevenways.be>
1124
- * @since 1.1.3
1125
- * @version 1.1.3
1126
- *
1127
- * @param {String} href The href or route name
1128
- * @param {Object} parameters Route parameters
1129
- *
1130
- * @return {String}
1131
- */
1132
- Alchemy.setMethod(function routeUrl(href, parameters) {
1133
-
1134
- let temp = Router.getUrl(href, parameters);
1135
-
1136
- if (temp && temp.href) {
1137
- temp = String(temp);
1138
-
1139
- if (temp) {
1140
- return temp;
1141
- }
1142
- }
1143
-
1144
- return href;
1145
- });
1146
-
1147
- /**
1148
- * Get paths that should be cached by the client
1149
- *
1150
- * @author Jelle De Loecker <jelle@develry.be>
1151
- * @since 1.0.7
1152
- * @version 1.0.7
1153
- *
1154
- * @return {Pledge}
1155
- */
1156
- Alchemy.decorateMethod(Blast.Decorators.memoize(), function getAppcachePaths() {
1157
-
1158
- var paths = [];
1159
-
1160
- return Function.parallel(function getHawkejsTemplates(next) {
1161
-
1162
- var templates = [],
1163
- directories = alchemy.hawkejs.directories.getSorted(),
1164
- tasks = [],
1165
- i;
1166
-
1167
- function checkDirectory(dir_path, mount_path, next) {
1168
- fs.readdir(dir_path, function gotDir(err, files) {
1169
-
1170
- if (err) {
1171
-
1172
- if (err.code == 'ENOENT') {
1173
- return next();
1174
- }
1175
-
1176
- return next(err);
1177
- }
1178
-
1179
- let tasks = [],
1180
- i;
1181
-
1182
- for (i = 0; i < files.length; i++) {
1183
- let file = files[i],
1184
- full_path = libpath.resolve(dir_path, file),
1185
- full_mount_path = mount_path + '/' + file;
1186
-
1187
- tasks.push(function checkPath(next) {
1188
- fs.stat(full_path, function gotStat(err, stat) {
1189
-
1190
- if (err) {
1191
-
1192
- if (err.code == 'ENOENT') {
1193
- return next();
1194
- }
1195
-
1196
- return next(err);
1197
- }
1198
-
1199
- if (stat.isDirectory()) {
1200
- return checkDirectory(full_path, full_mount_path, next);
1201
- }
1202
-
1203
- if (stat.isFile()) {
1204
- if (file.endsWith('.ejs') || file.endsWith('.hwk')) {
1205
-
1206
- if (full_mount_path[0] == '/') {
1207
- full_mount_path = full_mount_path.slice(1);
1208
- }
1209
-
1210
- templates.push(full_mount_path);
1211
- }
1212
- }
1213
-
1214
- next();
1215
- });
1216
- });
1217
- }
1218
-
1219
- Function.parallel(tasks, next);
1220
- });
1221
- }
1222
-
1223
- for (i = 0; i < directories.length; i++) {
1224
- let directory = directories[i];
1225
-
1226
- tasks.push(function readDir(next) {
1227
- checkDirectory(directory, '', next);
1228
- });
1229
- }
1230
-
1231
- return Function.parallel(tasks, function done(err) {
1232
-
1233
- if (err) {
1234
- return next(err);
1235
- }
1236
-
1237
- let path,
1238
- url,
1239
- i;
1240
-
1241
- for (i = 0; i < templates.length; i++) {
1242
- path = templates[i];
1243
- url = '/hawkejs/templates?name[0]=' + encodeURIComponent(path.beforeLast('.ejs')) + '&v=' + alchemy.package.version;
1244
- paths.push(url);
1245
- }
1246
-
1247
- next();
1248
- });
1249
- }, function done(err) {
1250
-
1251
- if (err) {
1252
- return;
1253
- }
1254
-
1255
- return paths;
1256
- });
1257
- });
1258
-
1259
- /**
1260
- * Get the appcache manifest text
1261
- *
1262
- * @author Jelle De Loecker <jelle@develry.be>
1263
- * @since 1.0.7
1264
- * @version 1.0.7
1265
- *
1266
- * @return {Pledge}
1267
- */
1268
- Alchemy.decorateMethod(Blast.Decorators.memoize(), function getAppcacheManifest() {
1269
-
1270
- return Function.series(function gotPaths(next) {
1271
- alchemy.getAppcachePaths().done(next);
1272
- }, function createText(err, result) {
1273
-
1274
- if (err) {
1275
- return;
1276
- }
1277
-
1278
- let manifest = 'CACHE MANIFEST\n\n',
1279
- entry,
1280
- url,
1281
- key,
1282
- i;
1283
-
1284
- manifest += 'CACHE:\n';
1285
-
1286
- // Allways add the client script
1287
- manifest += '/hawkejs/hawkejs-client.js?v=' + alchemy.package.version + '\n';
1288
-
1289
- if (ac_entries.cache && ac_entries.cache.length) {
1290
- for (key in ac_entries.cache) {
1291
- manifest += ac_entries.cache[key].url + '\n';
1292
- }
1293
- }
1294
-
1295
- for (i = 0; i < result[0].length; i++) {
1296
- url = result[0][i];
1297
-
1298
- manifest += url + '\n';
1299
- }
1300
-
1301
- manifest += '\n';
1302
- manifest += 'NETWORK:\n*\n\n';
1303
-
1304
- // This will cause a cache update each time the server is reset
1305
- manifest += '#' + alchemy.package.version + '-' + alchemy.discovery_id;
1306
-
1307
- return manifest;
1308
- });
1309
- });
1310
-
1311
- /**
1312
- * Add an appcache entry
1313
- *
1314
- * @author Jelle De Loecker <jelle@develry.be>
1315
- * @since 1.0.7
1316
- * @version 1.0.7
1317
- *
1318
- * @param {String|Object}
1319
- */
1320
- Alchemy.setMethod(function addAppcacheEntry(entry) {
1321
-
1322
- if (typeof entry == 'string') {
1323
- entry = {
1324
- url : entry
1325
- };
1326
- }
1327
-
1328
- if (!entry.type) {
1329
- entry.type = 'cache';
1330
- } else {
1331
- entry.type = entry.type.toLowerCase();
1332
- }
1333
-
1334
- if (!ac_entries[entry.type]) {
1335
- ac_entries[entry.type] = [];
1336
- }
1337
-
1338
- ac_entries[entry.type].push(entry);
1339
- });
1340
-
1341
- /**
1342
- * Get the body of an IncomingMessage
1343
- *
1344
- * @author Jelle De Loecker <jelle@develry.be>
1345
- * @since 1.1.0
1346
- * @version 1.1.0
1347
- *
1348
- * @param {IncomingMessage} req
1349
- * @param {OutgoingMessage} res Optional
1350
- * @param {Function} callback
1351
- */
1352
- Alchemy.setMethod(function parseRequestBody(req, res, callback) {
1353
-
1354
- const conduit = req.conduit;
1355
-
1356
- if (typeof res == 'function') {
1357
- callback = res;
1358
- res = null;
1359
- }
1360
-
1361
- if (req.original) {
1362
- req = req.original;
1363
- }
1364
-
1365
- if (req.body != null) {
1366
- return callback(null, req.body);
1367
- }
1368
-
1369
- // Multipart data is handled by "formidable"
1370
- if (req.headers['content-type'] && req.headers['content-type'].startsWith('multipart/form-data')) {
1371
-
1372
- let form = new formidable.IncomingForm();
1373
-
1374
- // md5 hash by default
1375
- form.hash = 'md5';
1376
-
1377
- form.parse(req, function parsedMultipart(err, form_fields, form_files) {
1378
-
1379
- var fields = {},
1380
- files = {},
1381
- key;
1382
-
1383
- if (err && req.conduit && req.conduit.aborted) {
1384
- return callback(null);
1385
- }
1386
-
1387
- // Fix the field names
1388
- for (key in form_fields) {
1389
- Object.setFormPath(fields, key, form_fields[key]);
1390
- }
1391
-
1392
- // Fix the file names
1393
- for (key in form_files) {
1394
- Object.setFormPath(files, key, form_files[key]);
1395
- }
1396
-
1397
- if (err) {
1398
- log.error('Error parsing multipart POST', {err: err});
1399
- req.body = {};
1400
- req.files = {};
1401
- } else {
1402
- req.body = fields;
1403
- req.files = files;
1404
-
1405
- if (conduit) {
1406
- conduit.setRequestBody(fields);
1407
- conduit.setRequestFiles(files);
1408
- }
1409
- }
1410
-
1411
- callback(null, fields);
1412
- });
1413
-
1414
- return;
1415
- }
1416
-
1417
- // Regular form-encoded data
1418
- if (req.headers['content-type'] && req.headers['content-type'].indexOf('form-urlencoded') > -1) {
1419
-
1420
- urlFormBody(req, res, function parsedBody(err) {
1421
-
1422
- if (err && req.conduit && req.conduit.aborted) {
1423
- return callback(null);
1424
- }
1425
-
1426
- // You can't send files using a regular post
1427
- req.files = {};
1428
-
1429
- if (err) {
1430
- log.error('Error parsing x-www-form-urlencoded body data', {err: err});
1431
- req.body = {};
1432
- } else {
1433
- req.body = req.body;
1434
-
1435
- if (conduit) {
1436
- conduit.setRequestBody(req.body);
1437
- }
1438
- }
1439
-
1440
- callback(null, req.body);
1441
- });
1442
-
1443
- return;
1444
- }
1445
-
1446
- // Any other encoded data (like JSON)
1447
- anyBody(req, function parsedBody(err, body) {
1448
-
1449
- if (err && req.conduit && req.conduit.aborted) {
1450
- return callback(null);
1451
- }
1452
-
1453
- // You can't send files using a regular post
1454
- req.files = {};
1455
-
1456
- if (err) {
1457
- log.error('Error parsing body data', {err: err});
1458
- req.body = {};
1459
- } else {
1460
- req.body = body;
1461
-
1462
- if (conduit) {
1463
- conduit.setRequestBody(body);
1464
- }
1465
- }
1466
-
1467
- callback(null, req.body);
1468
- });
1469
- });
1470
-
1471
- /**
1472
- * Export all data
1473
- *
1474
- * @author Jelle De Loecker <jelle@develry.be>
1475
- * @since 1.0.5
1476
- * @version 1.0.5
1477
- *
1478
- * @return {Stream}
1479
- */
1480
- Alchemy.setMethod(function createExportStream(options) {
1481
-
1482
- var stream = new require('stream').PassThrough();
1483
-
1484
- this.exportToStream(stream, options);
1485
-
1486
- return stream;
1487
- });
1488
-
1489
- /**
1490
- * Export all data to stream
1491
- *
1492
- * @author Jelle De Loecker <jelle@develry.be>
1493
- * @since 1.0.5
1494
- * @version 1.0.5
1495
- *
1496
- * @param {Stream} output
1497
- * @param {Object} options
1498
- *
1499
- * @return {Pledge}
1500
- */
1501
- Alchemy.setMethod(function exportToStream(output, options) {
1502
-
1503
- if (!alchemy.isStream(output)) {
1504
- if (!options) {
1505
- options = output;
1506
- output = null;
1507
- }
1508
-
1509
- output = options.output;
1510
- }
1511
-
1512
- if (!output) {
1513
- return Pledge.reject(new Error('No target output stream has been given'));
1514
- }
1515
-
1516
- if (!options) {
1517
- options = {};
1518
- }
1519
-
1520
- let tasks = [],
1521
- i;
1522
-
1523
- for (i = 0; i < Model.children.length; i++) {
1524
- let model = Model.children[i];
1525
-
1526
- tasks.push(async function exportModel(next) {
1527
- await (new model).exportToStream(output);
1528
- next();
1529
- });
1530
- }
1531
-
1532
- return Function.series(tasks, function done(err) {
1533
-
1534
- if (err) {
1535
- return output.emit('error', err);
1536
- }
1537
-
1538
- output.end();
1539
- });
1540
- });
1541
-
1542
- /**
1543
- * Import from a stream
1544
- *
1545
- * @author Jelle De Loecker <jelle@develry.be>
1546
- * @since 1.0.5
1547
- * @version 1.0.5
1548
- *
1549
- * @param {Stream} input
1550
- * @param {Object} options
1551
- *
1552
- * @return {Pledge}
1553
- */
1554
- Alchemy.setMethod(function importFromStream(input, options) {
1555
-
1556
- if (!alchemy.isStream(input)) {
1557
- if (!options) {
1558
- options = input;
1559
- input = null;
1560
- }
1561
-
1562
- input = options.input;
1563
- }
1564
-
1565
- if (!input) {
1566
- return Pledge.reject(new Error('No source input stream has been given'));
1567
- }
1568
-
1569
- if (!options) {
1570
- options = {};
1571
- }
1572
-
1573
- let that = this,
1574
- current_type = null,
1575
- extra_stream,
1576
- pledge = new Pledge(),
1577
- stopped,
1578
- paused,
1579
- buffer,
1580
- model,
1581
- value,
1582
- seen = 0,
1583
- left,
1584
- size,
1585
- doc;
1586
-
1587
- input.on('data', function onData(data) {
1588
-
1589
- if (stopped) {
1590
- return;
1591
- }
1592
-
1593
- if (buffer) {
1594
- buffer = Buffer.concat([buffer, data]);
1595
- } else {
1596
- buffer = data;
1597
- }
1598
-
1599
- handleBuffer();
1600
- });
1601
-
1602
- function handleBuffer() {
1603
-
1604
- if (paused) {
1605
- return;
1606
- }
1607
-
1608
- if (!current_type && buffer.length < 2) {
1609
- return;
1610
- }
1611
-
1612
- if (!current_type) {
1613
- current_type = buffer.readUInt8(0);
1614
-
1615
- if (current_type == 0x01) {
1616
- size = buffer.readUInt8(1);
1617
- buffer = buffer.slice(2);
1618
- } else if (current_type == 0x02 && buffer.length >= 5) {
1619
- size = buffer.readUInt32BE(1);
1620
- buffer = buffer.slice(5);
1621
- } else if (current_type == 0xFF) {
1622
- size = buffer.readUInt32BE(1);
1623
- buffer = buffer.slice(5);
1624
- seen = 0;
1625
-
1626
- if (!doc) {
1627
- stopped = true;
1628
- pledge.reject(new Error('Found extra import data, but no active document'));
1629
- } else {
1630
- extra_stream = new require('stream').PassThrough();
1631
- doc.extraImportFromStream(extra_stream);
1632
- }
1633
- } else {
1634
- // Not enough data? Wait
1635
- current_type = null;
1636
- return;
1637
- }
1638
- }
1639
-
1640
- handleRest();
1641
- }
1642
-
1643
- function handleRest() {
1644
-
1645
- if (current_type == 0xFF) {
1646
- left = size - seen;
1647
- value = buffer.slice(0, left);
1648
-
1649
- seen += value.length;
1650
-
1651
- if (value.length == buffer.length) {
1652
- buffer = null;
1653
- } else if (value.length < buffer.length) {
1654
- buffer = buffer.slice(left);
1655
- }
1656
-
1657
- extra_stream.write(value);
1658
-
1659
- if (value.length == left) {
1660
- extra_stream.end();
1661
- current_type = null;
1662
-
1663
- if (buffer) {
1664
- handleBuffer();
1665
- }
1666
- }
1667
-
1668
- return;
1669
- }
1670
-
1671
- if (buffer.length >= size) {
1672
- value = buffer.slice(0, size);
1673
- buffer = buffer.slice(size);
1674
- } else {
1675
- // Wait for next call
1676
- return;
1677
- }
1678
-
1679
- if (current_type == 0x01) {
1680
- value = value.toString();
1681
-
1682
- if (!model || model.model_name != value) {
1683
- model = Model.get(value);
1684
- doc = null;
1685
- }
1686
-
1687
- if (!model) {
1688
- stopped = true;
1689
- return pledge.reject(new Error('Could not find Model "' + value + '"'));
1690
- }
1691
-
1692
- current_type = null;
1693
- size = 0;
1694
- } else if (current_type == 0x02) {
1695
- doc = model.createDocument();
1696
- input.pause();
1697
- paused = true;
1698
-
1699
- doc.importFromBuffer(value).done(function done(err, result) {
1700
-
1701
- if (err) {
1702
- stopped = true;
1703
- return pledge.reject(err);
1704
- }
1705
-
1706
- current_type = null;
1707
- paused = false;
1708
- input.resume();
1709
-
1710
- handleBuffer();
1711
- });
1712
-
1713
- return;
1714
- }
1715
-
1716
- if (buffer && buffer.length) {
1717
- handleBuffer();
1718
- }
1719
- }
1720
-
1721
- return pledge;
1722
- });
1723
-
1724
- /**
1725
- * The alchemy global, where everything will be stored
1726
- *
1727
- * @author Jelle De Loecker <jelle@develry.be>
1728
- * @since 0.0.1
1729
- * @version 0.4.0
1730
- *
1731
- * @type {Alchemy}
1732
- */
1733
- DEFINE('alchemy', new Alchemy());
1734
-
1735
- /**
1736
- * Define the log function
1737
- *
1738
- * @author Jelle De Loecker <jelle@develry.be>
1739
- * @since 0.0.1
1740
- * @version 0.4.0
1741
- *
1742
- * @type {Function}
1743
- */
1744
- DEFINE('log', alchemy.log);
1745
-
1746
- for (let key in alchemy.Janeway.LEVELS) {
1747
- let name = key.toLowerCase();
1748
- let val = alchemy.Janeway.LEVELS[key];
1749
-
1750
- log[name] = function(...args) {
1751
- return alchemy.printLog(val, args, {level: 2});
1752
- };
1753
- }
1754
-
1755
- log.warn = log.warning;
1756
-
1757
- /**
1758
- * Define the todo log function
1759
- *
1760
- * @author Jelle De Loecker <jelle@develry.be>
1761
- * @since 0.2.0
1762
- * @version 0.4.0
1763
- *
1764
- * @type {Function}
1765
- */
1766
- log.todo = function todo(...args) {
1767
-
1768
- var options = {
1769
- gutter: alchemy.Janeway.esc(91) + '\u2620 Todo:' + alchemy.Janeway.esc(39),
1770
- level: 2
1771
- };
1772
-
1773
- return alchemy.printLog(alchemy.TODO, args, options);
1774
- };
1775
-
1776
- const anyBody = alchemy.use('body/any'),
1777
- formBody = alchemy.use('body/form'),
1778
- formidable = alchemy.use('formidable'),
1779
- bodyParser = alchemy.use('body-parser'),
1
+ 'use strict';
2
+
3
+ var shared_objects = {},
4
+ plugModules = null,
5
+ usedModules = {},
6
+ useErrors = {},
7
+ usePaths = {},
8
+ ac_entries = {},
9
+ parseArgs = require('minimist'),
10
+ libpath = require('path'),
11
+ colors = require('ansi-256-colors'),
12
+ fs = require('fs');
13
+
14
+ /**
15
+ * The Alchemy class
16
+ *
17
+ * @constructor
18
+ *
19
+ * @author Jelle De Loecker <jelle@develry.be>
20
+ * @since 0.0.1
21
+ * @version 1.1.0
22
+ */
23
+ global.Alchemy = Function.inherits('Informer', 'Alchemy', function Alchemy() {
24
+
25
+ var that = this,
26
+ package_json;
27
+
28
+ // Only allow a single instance of the Alchemy class
29
+ if (global.alchemy) {
30
+ return global.alchemy;
31
+ }
32
+
33
+ // Timestamp when alchemy started
34
+ this.start_time = Date.now();
35
+
36
+ // Current working directory
37
+ this.cwd = process.cwd();
38
+
39
+ // Parsed arguments
40
+ this.argv = parseArgs(process.argv.slice(2));
41
+
42
+ // The id of this server instance
43
+ this.discovery_id = Crypto.pseudoHex();
44
+
45
+ // Link to the colors module
46
+ this.colors = colors;
47
+
48
+ // The session count
49
+ this.session_count = 0;
50
+
51
+ // Plugins to be loaded will be stored in here, with their options
52
+ this.plugins = {};
53
+
54
+ // Certain required modules can be registered under a name
55
+ this.modules = {};
56
+
57
+ // Link to all used modules
58
+ this.modules_loaded = usedModules;
59
+
60
+ // Link to failed modules
61
+ this.modules_error = useErrors;
62
+
63
+ // Try getting the app package.json file
64
+ try {
65
+ package_json = require(libpath.resolve(PATH_ROOT, 'package.json'));
66
+ } catch (err) {
67
+ package_json = {};
68
+ }
69
+
70
+ // The app package.json as an object
71
+ this.package = package_json;
72
+
73
+ // Now get the alchemymvc package.json file
74
+ try {
75
+ package_json = require(libpath.resolve(PATH_CORE, '..', 'package.json'));
76
+ } catch (err) {
77
+ package_json = {};
78
+ }
79
+
80
+ // Get the alchemy core version
81
+ this.version = package_json.version;
82
+
83
+ // Keep status
84
+ this.status = {};
85
+
86
+ // All caches
87
+ this.caches = {};
88
+
89
+ // Also store the version in the process versions object
90
+ process.versions.alchemy = this.version;
91
+
92
+ // Also store the version of the app
93
+ process.versions.alchemy_app = this.package.version;
94
+
95
+ // Load the settings
96
+ this.loadSettings();
97
+
98
+ // Listen to messages from parent processes
99
+ process.on('message', function gotMessage(message) {
100
+ if (typeof message == 'string') {
101
+ return that.emit(message);
102
+ }
103
+
104
+ if (message && message.type) {
105
+ return that.emit(message.type, message.data);
106
+ }
107
+ });
108
+
109
+ // Get Janeway
110
+ this.Janeway = this.use('janeway');
111
+
112
+ // Asign the Janeway levels
113
+ Object.assign(this, this.Janeway.LEVELS);
114
+
115
+ try {
116
+ if (this.argv['stream-janeway']) {
117
+ this.startJaneway({stream: true});
118
+ } else if (this.allow_janeway) {
119
+ this.startJaneway();
120
+ }
121
+ } catch (err) {
122
+ log.warn('Failed to start Janeway:', err);
123
+ }
124
+ });
125
+
126
+ /**
127
+ * See if running janeway is allowed
128
+ *
129
+ * @author Jelle De Loecker <jelle@develry.be>
130
+ * @since 0.5.0
131
+ * @version 0.5.0
132
+ *
133
+ * @type {Boolean}
134
+ */
135
+ Alchemy.prepareProperty(function allow_janeway() {
136
+
137
+ // Setting the --disable-janeway flag explicitly disabled ALL forms of janeway
138
+ if (this.argv['disable-janeway'] || process.env.DISABLE_JANEWAY) {
139
+ return false;
140
+ }
141
+
142
+ // You can also disable janeway in the settings
143
+ if (this.settings.janeway === false) {
144
+ return false;
145
+ }
146
+
147
+ if (Blast.isNW || !process.stdout.isTTY) {
148
+ return false;
149
+ }
150
+
151
+ return true;
152
+ });
153
+
154
+ /**
155
+ * Expirable object where sessions are stored
156
+ *
157
+ * @author Jelle De Loecker <jelle@develry.be>
158
+ * @since 0.5.0
159
+ * @version 1.0.4
160
+ *
161
+ * @type {Develry.Cache}
162
+ */
163
+ Alchemy.prepareProperty(function sessions() {
164
+
165
+ var cache = this.getCache('sessions', {
166
+ max_idle : alchemy.settings.session_length,
167
+ max_length : Infinity
168
+ });
169
+
170
+ cache.on('removed', function onRemoved(value, key) {
171
+ // @TODO: check if expired?
172
+ value.removed();
173
+ });
174
+
175
+ return cache;
176
+ });
177
+
178
+ /**
179
+ * Expirable object where sessions are temporarily stored
180
+ * based on the browser fingerprints
181
+ *
182
+ * @author Jelle De Loecker <jelle@develry.be>
183
+ * @since 1.1.0
184
+ * @version 1.1.0
185
+ *
186
+ * @type {Develry.Cache}
187
+ */
188
+ Alchemy.prepareProperty(function fingerprints() {
189
+
190
+ var cache = this.getCache('fingerprints', {
191
+ max_idle : '3 minutes',
192
+ max_length : 3000
193
+ });
194
+
195
+ return cache;
196
+ });
197
+
198
+ /**
199
+ * Get or set the environment
200
+ *
201
+ * @author Jelle De Loecker <jelle@develry.be>
202
+ * @since 0.4.0
203
+ * @version 0.4.0
204
+ *
205
+ * @type {String}
206
+ */
207
+ Alchemy.setProperty(function environment() {
208
+ return alchemy.settings.environment;
209
+ }, function set_environment(value) {
210
+ alchemy.settings.environment = String(value);
211
+ return alchemy.settings.environment;
212
+ });
213
+
214
+ /**
215
+ * Start janeway
216
+ *
217
+ * @author Jelle De Loecker <jelle@develry.be>
218
+ * @since 0.5.0
219
+ * @version 1.1.0
220
+ *
221
+ * @param {Object} options
222
+ */
223
+ Alchemy.setMethod(function startJaneway(options) {
224
+
225
+ if (this.Janeway.started) {
226
+ return;
227
+ }
228
+
229
+ if (!options) {
230
+ options = {};
231
+ }
232
+
233
+ if (options.stream) {
234
+ let that = this,
235
+ screen,
236
+ out;
237
+
238
+ out = new require('net').Socket({fd: 4, writable: true});
239
+
240
+ out.columns = 80;
241
+ out.rows = 24;
242
+
243
+ screen = this.Janeway.createScreen({
244
+ input : process.stdin,
245
+ terminal : 'xterm-256color',
246
+ output : out
247
+ });
248
+
249
+ options.screen = screen;
250
+
251
+ // Also output to stdout
252
+ options.output_to_stdout = true;
253
+
254
+ // Keep regular stdout color
255
+ options.keep_color = true;
256
+
257
+ // Don't mess with the indentation
258
+ options.change_indent = false;
259
+
260
+ this.on('janeway_propose_geometry', function onProposeGeometry(data) {
261
+ out.columns = data.cols || data.width;
262
+ out.rows = data.rows || data.height;
263
+ out.emit('resize');
264
+ });
265
+
266
+ screen.on('resize', function onResize(a, b) {
267
+ that.Janeway.redraw();
268
+ });
269
+
270
+ this.on('janeway_redraw', function onRedrawRequest() {
271
+ that.Janeway.redraw();
272
+ });
273
+ }
274
+
275
+ this.Janeway.started = true;
276
+ this.Janeway.start(options);
277
+
278
+ if (this.settings.title) {
279
+ let title = this.settings.title;
280
+
281
+ if (this.settings.titleized) {
282
+ title = 'Alchemy: ' + title;
283
+ }
284
+
285
+ this.Janeway.setTitle(title);
286
+ }
287
+
288
+ if (this.settings.session_menu) {
289
+
290
+ let session_menu = this.Janeway.addIndicator('⌨ ');
291
+
292
+ if (!session_menu.addItem) {
293
+ return session_menu.remove();
294
+ }
295
+
296
+ this.Janeway.session_menu = session_menu;
297
+ }
298
+
299
+ });
300
+
301
+ /**
302
+ * Log messages of level 5 (info)
303
+ *
304
+ * @author Jelle De Loecker <jelle@develry.be>
305
+ * @since 0.0.1
306
+ * @version 0.4.0
307
+ */
308
+ Alchemy.setMethod(function log(...args) {
309
+ return alchemy.printLog(5, args, {level: 3});
310
+ });
311
+
312
+ /**
313
+ * Actually print a log message
314
+ *
315
+ * @author Jelle De Loecker <jelle@develry.be>
316
+ * @since 0.4.0
317
+ * @version 1.1.0
318
+ *
319
+ * @param {Number} level
320
+ * @param {Array} args
321
+ * @param {Object} options
322
+ */
323
+ Alchemy.setMethod(function printLog(level, args, options) {
324
+
325
+ var type,
326
+ line;
327
+
328
+ if (this.settings.silent) {
329
+ return;
330
+ }
331
+
332
+ if (!Array.isArray(args)) {
333
+ args = [args];
334
+ }
335
+
336
+ if (typeof level == 'string') {
337
+ type = level;
338
+ } else {
339
+ if (level < 3) {
340
+ type = 'error';
341
+ } else if (level < 5) {
342
+ type = 'warn';
343
+ } else {
344
+ type = 'info';
345
+ }
346
+ }
347
+
348
+ if (this.Janeway != null) {
349
+ line = this.Janeway.print(type, args, options);
350
+
351
+ if (options && options.gutter && line) {
352
+ line.setGutter(options.gutter)
353
+ }
354
+
355
+ return line;
356
+ } else {
357
+ console[type](...args);
358
+ }
359
+ });
360
+
361
+ /**
362
+ * Load the settings
363
+ *
364
+ * @author Jelle De Loecker <jelle@develry.be>
365
+ * @since 0.4.0
366
+ * @version 1.1.6
367
+ */
368
+ Alchemy.setMethod(function loadSettings() {
369
+
370
+ var default_path,
371
+ port_error,
372
+ local_path,
373
+ env_config,
374
+ env_path,
375
+ settings,
376
+ local,
377
+ env;
378
+
379
+ if (this.settings) {
380
+ return;
381
+ }
382
+
383
+ // Create the settings object
384
+ this.settings = settings = {};
385
+
386
+ // Generate the path to the default settings file
387
+ default_path = libpath.resolve(PATH_ROOT, 'app', 'config', 'default');
388
+
389
+ // Get default settings
390
+ try {
391
+ Object.assign(settings, require(default_path));
392
+ } catch (err) {
393
+ settings.no_default_file = default_path;
394
+ }
395
+
396
+ // Generate the path to the local settings file
397
+ local_path = libpath.resolve(PATH_ROOT, 'app', 'config', 'local');
398
+
399
+ // Get the local settings
400
+ try {
401
+ local = require(local_path);
402
+ } catch(err) {
403
+ local = {};
404
+ settings.no_local_file = local_path;
405
+ }
406
+
407
+ // Default to the 'dev' environment
408
+ if (!local.environment) {
409
+
410
+ if (process.env.ENV) {
411
+ local.environment = process.env.ENV;
412
+ } else {
413
+ local.environment = 'dev';
414
+ }
415
+ }
416
+
417
+ env = this.argv.env || this.argv.environment;
418
+
419
+ if (env) {
420
+ local.environment = env;
421
+ this.printLog(this.INFO, ['Switching to environment', env]);
422
+ }
423
+
424
+ // Generate the path to the environment settings file
425
+ env_path = libpath.resolve(PATH_APP, 'config', local.environment, 'config');
426
+
427
+ // Get the config
428
+ try {
429
+ env_config = require(env_path);
430
+ } catch(err) {
431
+ env_config = {};
432
+ settings.no_env_file = env_path;
433
+ }
434
+
435
+ // Merge all the settings in order: default - environment - local
436
+ Object.merge(settings, env_config, local);
437
+
438
+ if (!settings.name) {
439
+ settings.name = this.package.name;
440
+ }
441
+
442
+ if (settings.title == null) {
443
+ if (this.package.title) {
444
+ // Allow users to set the title in their package file
445
+ settings.title = this.package.title;
446
+ } else if (settings.name) {
447
+ settings.title = settings.name.replace(/-/g, ' ').titleize();
448
+ settings.titleized = true;
449
+ }
450
+ }
451
+
452
+ if (this.argv.port) {
453
+ let port = parseInt(this.argv.port);
454
+
455
+ if (port) {
456
+ this.printLog(this.INFO, ['Using port setting from argument:', port]);
457
+ settings.port = port;
458
+ } else {
459
+ this.argv.socket = this.argv.port;
460
+ this.argv.port = null;
461
+ }
462
+ }
463
+
464
+ if (this.argv.socket) {
465
+ settings.port = false;
466
+ settings.socket = this.argv.socket;
467
+
468
+ let stat;
469
+
470
+ try {
471
+ stat = fs.statSync(settings.socket);
472
+ } catch (err) {
473
+ // Ignore if it doesn't exist yet
474
+ }
475
+
476
+ if (stat && stat.isDirectory()) {
477
+ settings.socket = libpath.resolve(settings.socket, settings.name + '.alchemy.sock');
478
+ }
479
+
480
+ this.printLog(this.INFO, ['Using socket setting from argument:', settings.socket]);
481
+ }
482
+
483
+ if (!settings.port && settings.port !== false) {
484
+ settings.port = 3000;
485
+ }
486
+
487
+ if (settings.port > 49151) {
488
+ port_error = 'Could not use port number ' + String(port).bold.red + ' because ';
489
+
490
+ // Make sure the port is valid
491
+ if (settings.port > 65535) {
492
+ this.printLog(this.FATAL, [port_error + 'there is no port higher than 65535. Please use ports below 49151.']);
493
+ } else {
494
+ this.printLog(this.FATAL, [port_error + 'it\'s an ephemeral port. Please use ports below 49151.']);
495
+ }
496
+
497
+ process.exit();
498
+ }
499
+
500
+ if (this.argv.url) {
501
+ settings.url = this.argv.url;
502
+ }
503
+
504
+ if (settings.url && settings.url.indexOf('{') > -1) {
505
+ settings.url = settings.url.assign(settings);
506
+ }
507
+
508
+ if (this.argv.preload) {
509
+ settings.preload = this.argv.preload;
510
+
511
+ if (Array.isArray(settings.preload)) {
512
+ settings.preload = settings.preload.last();
513
+ }
514
+ }
515
+
516
+ if (settings.preload == 'false') {
517
+ settings.preload = false;
518
+ }
519
+
520
+ let key,
521
+ val;
522
+
523
+ for (key in this.argv) {
524
+
525
+ if (!key.startsWith('override-')) {
526
+ continue;
527
+ }
528
+
529
+ val = this.argv[key];
530
+ key = key.after('override-');
531
+
532
+ if (!settings[key] || typeof settings[key] != 'object') {
533
+ settings[key] = val;
534
+ } else {
535
+ Object.merge(settings[key], val);
536
+ }
537
+ }
538
+
539
+ if (settings.preload) {
540
+ this.doPreload();
541
+ }
542
+
543
+ // Set the debug value
544
+ global.DEBUG = settings.debug;
545
+ });
546
+
547
+ /**
548
+ * Set status
549
+ *
550
+ * @author Jelle De Loecker <jelle@develry.be>
551
+ * @since 0.4.1
552
+ * @version 0.4.1
553
+ */
554
+ Alchemy.setMethod(function setStatus(name, value) {
555
+ this.status[name] = value;
556
+ });
557
+
558
+ /**
559
+ * Execute the function when alchemy is ready
560
+ *
561
+ * @author Jelle De Loecker <jelle@develry.be>
562
+ * @since 0.0.1
563
+ * @version 1.1.2
564
+ *
565
+ * @param {Function} callback The function to execute
566
+ *
567
+ * @return {Pledge}
568
+ */
569
+ Alchemy.setMethod(function ready(callback) {
570
+
571
+ var that = this,
572
+ pledge = new Pledge();
573
+
574
+ pledge.done(callback);
575
+
576
+ if (!this.sputnik) {
577
+ Blast.loaded(function hasLoaded() {
578
+ pledge.resolve(that.ready());
579
+ });
580
+ } else {
581
+ this.sputnik.after(['start_server', 'datasources', 'listening'], function afterReady() {
582
+ pledge.resolve();
583
+ });
584
+ }
585
+
586
+ return pledge;
587
+ });
588
+
589
+ /**
590
+ * Preload the client-side stuff
591
+ *
592
+ * @author Jelle De Loecker <jelle@elevenways.be>
593
+ * @since 1.1.2
594
+ * @version 1.1.2
595
+ */
596
+ Alchemy.setMethod(async function doPreload() {
597
+
598
+ await this.ready();
599
+
600
+ let url;
601
+
602
+ if (this.settings.url) {
603
+ url = this.settings.url;
604
+ } else if (this.settings.port) {
605
+ url = 'http://localhost:' + this.settings.port;
606
+ }
607
+
608
+ if (url) {
609
+ log.info('Preloading url', url);
610
+ Blast.fetch(url);
611
+
612
+ url += '/hawkejs/hawkejs-client.js';
613
+
614
+ log.info('Preloading client file via HTTP', url);
615
+ Blast.fetch(url);
616
+ } else {
617
+ log.info('Preloading client file');
618
+
619
+ Blast.getClientPath({
620
+ modify_prototypes : true,
621
+ create_source_map : alchemy.settings.debug,
622
+ enable_coverage : !!global.__coverage__
623
+ });
624
+ }
625
+ });
626
+
627
+ /**
628
+ * Resolve the provided arguments to a useable path string.
629
+ * Only used strings, discards objects.
630
+ *
631
+ * @author Jelle De Loecker <jelle@develry.be>
632
+ * @since 0.0.1
633
+ * @version 0.4.0
634
+ *
635
+ * @param {String} path_to_dirs The path containing the dirs to load
636
+ */
637
+ Alchemy.setMethod(function pathResolve(...path_to_dirs) {
638
+
639
+ var path_arguments,
640
+ i;
641
+
642
+ if (path_to_dirs.length == 1) {
643
+ return path_to_dirs[0];
644
+ }
645
+
646
+ path_arguments = [];
647
+
648
+ for (i = 0; i < path_to_dirs.length; i++) {
649
+ if (typeof path_to_dirs[i] == 'string') {
650
+ path_arguments.push(path_to_dirs[i]);
651
+ }
652
+ }
653
+
654
+ if (path_arguments.length > 1) {
655
+ return libpath.resolve(...path_arguments);
656
+ } else {
657
+ return path_arguments[0];
658
+ }
659
+ });
660
+
661
+ /**
662
+ * A wrapper function for requiring modules
663
+ *
664
+ * @author Jelle De Loecker <jelle@develry.be>
665
+ * @since 0.0.1
666
+ * @version 1.0.0
667
+ *
668
+ * @param {String} module_name The name/path of the module to load
669
+ * @param {String} register_as Cache the module under this name
670
+ * @param {Object} options Extra options
671
+ * @param {Boolean} options.force Force a new requirement and do not cache
672
+ *
673
+ * @return {Object} The required module
674
+ */
675
+ Alchemy.setMethod(function use(module_name, register_as, options) {
676
+
677
+ var module,
678
+ result;
679
+
680
+ if (typeof register_as == 'object') {
681
+ options = register_as;
682
+ register_as = false;
683
+ }
684
+
685
+ // Certain modules can be disabled by registering them as null
686
+ if (module_name == null && register_as) {
687
+ this.modules[register_as] = null;
688
+ return null;
689
+ }
690
+
691
+ // If the module has explicitly been set to null, return that
692
+ if (this.modules[module_name] === null) {
693
+ return null;
694
+ }
695
+
696
+ if (typeof options == 'undefined') options = {};
697
+ if (typeof options.force == 'undefined') options.force = false;
698
+
699
+ // If a module has already been registered under this name, return that
700
+ if (this.modules[module_name] && !options.force) {
701
+ return this.modules[module_name];
702
+ }
703
+
704
+ if (this.argv['debug-requirements']) {
705
+ this.printLog(this.DEBUG, ['Going to load module', module_name], {level: 2});
706
+ }
707
+
708
+ try {
709
+ result = this.findModule(module_name, options);
710
+ module = result.module;
711
+ } catch (err) {
712
+
713
+ if (!useErrors[module_name]) {
714
+ useErrors[module_name] = 0;
715
+ }
716
+
717
+ useErrors[module_name]++;
718
+
719
+ if (!options.silent || this.argv['debug-requirements']) {
720
+ this.printLog(this.SEVERE, ['Failed to load module "' + module_name + '":', err.message], {level: 6, err: err});
721
+ }
722
+ return;
723
+ }
724
+
725
+ if (!usedModules[module_name]) {
726
+
727
+ let entry = {
728
+ internal : result.internal,
729
+ loaded : 0
730
+ };
731
+
732
+ if (result.package) {
733
+ entry.version = result.package.version;
734
+ }
735
+
736
+ usedModules[module_name] = result;
737
+ }
738
+
739
+ usedModules[module_name].loaded++;
740
+
741
+ if (register_as) {
742
+ this.modules[register_as] = module;
743
+ }
744
+
745
+ // If a new requirement needs to be forced, clear the cache
746
+ if (options.force) {
747
+ delete require.cache[result.module_path];
748
+ return require(result.module_path);
749
+ }
750
+
751
+ return module;
752
+ });
753
+
754
+
755
+ /**
756
+ * Look for a module by traversing the filesystem
757
+ *
758
+ * @author Jelle De Loecker <jelle@develry.be>
759
+ * @since 0.0.1
760
+ * @version 0.4.0
761
+ *
762
+ * @param {String} startPath The path to originate the search from
763
+ * @param {String} moduleName
764
+ * @param {Number} recurse
765
+ *
766
+ * @return {String}
767
+ */
768
+ Alchemy.setMethod(function searchModule(startPath, moduleName, recurse) {
769
+
770
+ var moduledirs,
771
+ module_path,
772
+ entries,
773
+ nmPath,
774
+ temp,
775
+ path,
776
+ key,
777
+ i;
778
+
779
+ // Don't do this search if it hasn't been enabled
780
+ // The new npm flat structure makes this an expensive thing to do
781
+ if (!this.settings.search_for_modules) {
782
+
783
+ // Set recurse to 3, so this is the first and last call
784
+ recurse = 3;
785
+
786
+ // Only add 2 folder to look through,
787
+ // the alchemymvc node_modules folder
788
+ // and the base node_modules folder
789
+ moduledirs = ['..', libpath.resolve(startPath, 'node_modules', 'alchemymvc')];
790
+
791
+ // Add plugin folders
792
+ if (!plugModules) {
793
+ path = libpath.resolve(PATH_ROOT, 'node_modules');
794
+
795
+ if (fs.existsSync(path)) {
796
+
797
+ // Get all the entries in the main modules folder
798
+ entries = fs.readdirSync(libpath.resolve(PATH_ROOT, 'node_modules'));
799
+
800
+ // Initiate the plugin modules variables
801
+ plugModules = [];
802
+
803
+
804
+ for (i = 0; i < entries.length; i++) {
805
+ temp = entries[i];
806
+
807
+ if (temp.startsWith('alchemy-')) {
808
+ plugModules.push(libpath.resolve(PATH_ROOT, 'node_modules', temp));
809
+ }
810
+ }
811
+ } else {
812
+ plugModules = [];
813
+ }
814
+ }
815
+
816
+ for (i = 0; i < plugModules.length; i++) {
817
+ moduledirs.push(plugModules[i]);
818
+ }
819
+
820
+ } else if (!searchModule.have_warned) {
821
+ searchModule.have_warned = true;
822
+ log.warn('The "search_for_modules" config has been enabled!');
823
+ }
824
+
825
+ if (!recurse) {
826
+ recurse = 1;
827
+ }
828
+
829
+ nmPath = libpath.resolve(startPath, 'node_modules');
830
+
831
+ if (!moduledirs) {
832
+ // Get all the entries inside the given path
833
+ try {
834
+ moduledirs = fs.readdirSync(nmPath);
835
+ } catch(err) {
836
+ return;
837
+ }
838
+ }
839
+
840
+ // Look in the base node_modules directory first
841
+ if (recurse == 1) {
842
+ moduledirs.unshift('..');
843
+ }
844
+
845
+ // Go over every directory in the main node_modules folder
846
+ for (i = 0; i < moduledirs.length; i++) {
847
+
848
+ key = moduledirs[i];
849
+
850
+ try {
851
+ // Let require find the specific file to get
852
+ module_path = require.resolve(libpath.resolve(nmPath, key, 'node_modules', moduleName));
853
+
854
+ // If no errors have popped up now, we can break the for loop
855
+ break;
856
+
857
+ } catch(e) {
858
+ // Do nothing
859
+ }
860
+ }
861
+
862
+ if (!module_path && recurse < 3) {
863
+ for (i = 0; i < moduledirs.length; i++) {
864
+
865
+ module_path = this.searchModule(libpath.resolve(nmPath, moduledirs[i]), moduleName, recurse+1);
866
+
867
+ if (module_path) {
868
+ break;
869
+ }
870
+ }
871
+ }
872
+
873
+ return module_path;
874
+ });
875
+
876
+ /**
877
+ * Find a module in our customized file structure
878
+ *
879
+ * @author Jelle De Loecker <jelle@develry.be>
880
+ * @since 0.0.1
881
+ * @version 1.1.0
882
+ *
883
+ * @param {String} moduleName
884
+ * @param {Object} options
885
+ *
886
+ * @return {Object}
887
+ */
888
+ Alchemy.setMethod(function findModule(moduleName, options) {
889
+
890
+ var package_json,
891
+ module_path,
892
+ internal,
893
+ module,
894
+ result,
895
+ time,
896
+ key,
897
+ i;
898
+
899
+ if (!options) {
900
+ options = {};
901
+ }
902
+
903
+ if (options.require == null) {
904
+ options.require = true;
905
+ }
906
+
907
+ // If we've required this once before, return it
908
+ if (result = usePaths[moduleName]) {
909
+ if (result.err) {
910
+ throw result.err;
911
+ }
912
+
913
+ // Only return the cached module if it was required then,
914
+ // or if require is now false
915
+ if (result.module || !options.require) {
916
+ return result;
917
+ }
918
+ }
919
+
920
+ result = {
921
+ err : null,
922
+ module : null,
923
+ module_dir : null,
924
+ module_path : null,
925
+ package : null,
926
+ internal : null,
927
+ search_time : null,
928
+ };
929
+
930
+ time = Date.now();
931
+
932
+ // Simply try to resolve the module by name
933
+ try {
934
+ module_path = require.resolve(moduleName);
935
+ } catch (err) {
936
+ result.err = err;
937
+ }
938
+
939
+ // If that path wasn't found, look through the root node_modules
940
+ if (result.err) {
941
+ try {
942
+ module_path = this.searchModule(PATH_ROOT, moduleName);
943
+ } catch (err) {
944
+ console.log(err);
945
+ return;
946
+ }
947
+ }
948
+
949
+ // If the module_path was found, actually require the module
950
+ if (module_path) {
951
+
952
+ // Modules are required by default
953
+ if (options.require) {
954
+ module = require(module_path);
955
+ }
956
+
957
+ // Get the package.json file
958
+ if (~module_path.indexOf(libpath.sep)) {
959
+ internal = false;
960
+
961
+ let module_dir = libpath.dirname(module_path);
962
+ result.module_dir = module_dir;
963
+
964
+ try {
965
+ package_json = require(libpath.resolve(module_dir, 'package.json'));
966
+ } catch (err) {
967
+ package_json = false;
968
+ }
969
+ } else {
970
+ internal = true;
971
+ package_json = {
972
+ version: process.versions.node
973
+ };
974
+ }
975
+ }
976
+
977
+ if (!options.require || module) {
978
+ result.err = null;
979
+ }
980
+
981
+ if (module) {
982
+ result.module = module;
983
+ }
984
+
985
+ result.module_path = module_path;
986
+ result.package = package_json;
987
+ result.internal = internal;
988
+
989
+ // Save the result
990
+ usePaths[moduleName] = result;
991
+
992
+ // If there was an error, throw it now
993
+ if (result.err) {
994
+ throw result.err;
995
+ }
996
+
997
+ result.search_time = Date.now() - time;
998
+
999
+ // Else return the result
1000
+ return result;
1001
+ });
1002
+
1003
+ /**
1004
+ * Create a shared object
1005
+ *
1006
+ * @author Jelle De Loecker <jelle@develry.be>
1007
+ * @since 0.0.1
1008
+ * @version 0.4.0
1009
+ *
1010
+ * @param {String} name The name of the object to get
1011
+ * @param {String} type The type to create (array or object)
1012
+ *
1013
+ * @return {Object|Array}
1014
+ */
1015
+ Alchemy.setMethod(function shared(name, type, value) {
1016
+
1017
+ if (typeof type !== 'string') {
1018
+ value = type;
1019
+ type = 'object';
1020
+ }
1021
+
1022
+ // Create it if it doesn't exist
1023
+ if (!shared_objects[name]) {
1024
+ if (type === 'array' || type === 'Array') {
1025
+ shared_objects[name] = value || [];
1026
+ } else {
1027
+ shared_objects[name] = value || {};
1028
+ }
1029
+ }
1030
+
1031
+ return shared_objects[name];
1032
+ });
1033
+
1034
+ /**
1035
+ * Get an object id,
1036
+ * return undefined if no valid data was given (instead of throwing an error)
1037
+ *
1038
+ * @author Jelle De Loecker <jelle@develry.be>
1039
+ * @since 0.0.1
1040
+ * @version 0.4.0
1041
+ *
1042
+ * @param {String|ObjectID} obj
1043
+ *
1044
+ * @return {ObjectID|undefined}
1045
+ */
1046
+ Alchemy.setMethod(function castObjectId(obj) {
1047
+
1048
+ var type = typeof obj;
1049
+
1050
+ if (obj && type === 'object' && obj.constructor && obj.constructor.name === 'ObjectID') {
1051
+ return obj;
1052
+ } else if (type === 'string' && obj.isObjectId()) {
1053
+ return alchemy.ObjectId(obj);
1054
+ }
1055
+
1056
+ return undefined;
1057
+ });
1058
+
1059
+ /**
1060
+ * See if the given object is a stream
1061
+ *
1062
+ * @author Jelle De Loecker <jelle@develry.be>
1063
+ * @since 0.2.0
1064
+ * @version 1.0.5
1065
+ *
1066
+ * @return {Boolean}
1067
+ */
1068
+ Alchemy.setMethod(function isStream(obj) {
1069
+ return obj && (typeof obj._read == 'function' || typeof obj._write == 'function') && typeof obj.on === 'function';
1070
+ });
1071
+
1072
+ /**
1073
+ * Get or create a new cache instance
1074
+ *
1075
+ * @author Jelle De Loecker <jelle@develry.be>
1076
+ * @since 1.0.0
1077
+ * @version 1.0.4
1078
+ *
1079
+ * @param {String} name
1080
+ * @param {Number|Object} options
1081
+ *
1082
+ * @return {Develry.Cache}
1083
+ */
1084
+ Alchemy.setMethod(function getCache(name, options) {
1085
+
1086
+ var instance,
1087
+ duration,
1088
+ config,
1089
+ type;
1090
+
1091
+ if (this.caches[name]) {
1092
+ return this.caches[name];
1093
+ }
1094
+
1095
+ if (!options) {
1096
+ options = {};
1097
+ }
1098
+
1099
+ type = typeof options;
1100
+
1101
+ if (type == 'number' || type == 'string') {
1102
+ options = {
1103
+ max_age : options,
1104
+ };
1105
+ }
1106
+
1107
+ config = Object.assign({
1108
+ max_length : 5000,
1109
+ }, options);
1110
+
1111
+ // @TODO: Fixed in 0.6.1
1112
+ instance = new Blast.Classes.Develry.Cache();
1113
+ Object.assign(instance, config);
1114
+
1115
+ this.caches[name] = instance;
1116
+
1117
+ return instance;
1118
+ });
1119
+
1120
+ /**
1121
+ * Get a route
1122
+ *
1123
+ * @author Jelle De Loecker <jelle@elevenways.be>
1124
+ * @since 1.1.3
1125
+ * @version 1.1.3
1126
+ *
1127
+ * @param {String} href The href or route name
1128
+ * @param {Object} parameters Route parameters
1129
+ *
1130
+ * @return {String}
1131
+ */
1132
+ Alchemy.setMethod(function routeUrl(href, parameters) {
1133
+
1134
+ let temp = Router.getUrl(href, parameters);
1135
+
1136
+ if (temp && temp.href) {
1137
+ temp = String(temp);
1138
+
1139
+ if (temp) {
1140
+ return temp;
1141
+ }
1142
+ }
1143
+
1144
+ return href;
1145
+ });
1146
+
1147
+ /**
1148
+ * Get paths that should be cached by the client
1149
+ *
1150
+ * @author Jelle De Loecker <jelle@develry.be>
1151
+ * @since 1.0.7
1152
+ * @version 1.0.7
1153
+ *
1154
+ * @return {Pledge}
1155
+ */
1156
+ Alchemy.decorateMethod(Blast.Decorators.memoize(), function getAppcachePaths() {
1157
+
1158
+ var paths = [];
1159
+
1160
+ return Function.parallel(function getHawkejsTemplates(next) {
1161
+
1162
+ var templates = [],
1163
+ directories = alchemy.hawkejs.directories.getSorted(),
1164
+ tasks = [],
1165
+ i;
1166
+
1167
+ function checkDirectory(dir_path, mount_path, next) {
1168
+ fs.readdir(dir_path, function gotDir(err, files) {
1169
+
1170
+ if (err) {
1171
+
1172
+ if (err.code == 'ENOENT') {
1173
+ return next();
1174
+ }
1175
+
1176
+ return next(err);
1177
+ }
1178
+
1179
+ let tasks = [],
1180
+ i;
1181
+
1182
+ for (i = 0; i < files.length; i++) {
1183
+ let file = files[i],
1184
+ full_path = libpath.resolve(dir_path, file),
1185
+ full_mount_path = mount_path + '/' + file;
1186
+
1187
+ tasks.push(function checkPath(next) {
1188
+ fs.stat(full_path, function gotStat(err, stat) {
1189
+
1190
+ if (err) {
1191
+
1192
+ if (err.code == 'ENOENT') {
1193
+ return next();
1194
+ }
1195
+
1196
+ return next(err);
1197
+ }
1198
+
1199
+ if (stat.isDirectory()) {
1200
+ return checkDirectory(full_path, full_mount_path, next);
1201
+ }
1202
+
1203
+ if (stat.isFile()) {
1204
+ if (file.endsWith('.ejs') || file.endsWith('.hwk')) {
1205
+
1206
+ if (full_mount_path[0] == '/') {
1207
+ full_mount_path = full_mount_path.slice(1);
1208
+ }
1209
+
1210
+ templates.push(full_mount_path);
1211
+ }
1212
+ }
1213
+
1214
+ next();
1215
+ });
1216
+ });
1217
+ }
1218
+
1219
+ Function.parallel(tasks, next);
1220
+ });
1221
+ }
1222
+
1223
+ for (i = 0; i < directories.length; i++) {
1224
+ let directory = directories[i];
1225
+
1226
+ tasks.push(function readDir(next) {
1227
+ checkDirectory(directory, '', next);
1228
+ });
1229
+ }
1230
+
1231
+ return Function.parallel(tasks, function done(err) {
1232
+
1233
+ if (err) {
1234
+ return next(err);
1235
+ }
1236
+
1237
+ let path,
1238
+ url,
1239
+ i;
1240
+
1241
+ for (i = 0; i < templates.length; i++) {
1242
+ path = templates[i];
1243
+ url = '/hawkejs/templates?name[0]=' + encodeURIComponent(path.beforeLast('.ejs')) + '&v=' + alchemy.package.version;
1244
+ paths.push(url);
1245
+ }
1246
+
1247
+ next();
1248
+ });
1249
+ }, function done(err) {
1250
+
1251
+ if (err) {
1252
+ return;
1253
+ }
1254
+
1255
+ return paths;
1256
+ });
1257
+ });
1258
+
1259
+ /**
1260
+ * Get the appcache manifest text
1261
+ *
1262
+ * @author Jelle De Loecker <jelle@develry.be>
1263
+ * @since 1.0.7
1264
+ * @version 1.0.7
1265
+ *
1266
+ * @return {Pledge}
1267
+ */
1268
+ Alchemy.decorateMethod(Blast.Decorators.memoize(), function getAppcacheManifest() {
1269
+
1270
+ return Function.series(function gotPaths(next) {
1271
+ alchemy.getAppcachePaths().done(next);
1272
+ }, function createText(err, result) {
1273
+
1274
+ if (err) {
1275
+ return;
1276
+ }
1277
+
1278
+ let manifest = 'CACHE MANIFEST\n\n',
1279
+ entry,
1280
+ url,
1281
+ key,
1282
+ i;
1283
+
1284
+ manifest += 'CACHE:\n';
1285
+
1286
+ // Allways add the client script
1287
+ manifest += '/hawkejs/hawkejs-client.js?v=' + alchemy.package.version + '\n';
1288
+
1289
+ if (ac_entries.cache && ac_entries.cache.length) {
1290
+ for (key in ac_entries.cache) {
1291
+ manifest += ac_entries.cache[key].url + '\n';
1292
+ }
1293
+ }
1294
+
1295
+ for (i = 0; i < result[0].length; i++) {
1296
+ url = result[0][i];
1297
+
1298
+ manifest += url + '\n';
1299
+ }
1300
+
1301
+ manifest += '\n';
1302
+ manifest += 'NETWORK:\n*\n\n';
1303
+
1304
+ // This will cause a cache update each time the server is reset
1305
+ manifest += '#' + alchemy.package.version + '-' + alchemy.discovery_id;
1306
+
1307
+ return manifest;
1308
+ });
1309
+ });
1310
+
1311
+ /**
1312
+ * Add an appcache entry
1313
+ *
1314
+ * @author Jelle De Loecker <jelle@develry.be>
1315
+ * @since 1.0.7
1316
+ * @version 1.0.7
1317
+ *
1318
+ * @param {String|Object}
1319
+ */
1320
+ Alchemy.setMethod(function addAppcacheEntry(entry) {
1321
+
1322
+ if (typeof entry == 'string') {
1323
+ entry = {
1324
+ url : entry
1325
+ };
1326
+ }
1327
+
1328
+ if (!entry.type) {
1329
+ entry.type = 'cache';
1330
+ } else {
1331
+ entry.type = entry.type.toLowerCase();
1332
+ }
1333
+
1334
+ if (!ac_entries[entry.type]) {
1335
+ ac_entries[entry.type] = [];
1336
+ }
1337
+
1338
+ ac_entries[entry.type].push(entry);
1339
+ });
1340
+
1341
+ /**
1342
+ * Get the body of an IncomingMessage
1343
+ *
1344
+ * @author Jelle De Loecker <jelle@develry.be>
1345
+ * @since 1.1.0
1346
+ * @version 1.1.0
1347
+ *
1348
+ * @param {IncomingMessage} req
1349
+ * @param {OutgoingMessage} res Optional
1350
+ * @param {Function} callback
1351
+ */
1352
+ Alchemy.setMethod(function parseRequestBody(req, res, callback) {
1353
+
1354
+ const conduit = req.conduit;
1355
+
1356
+ if (typeof res == 'function') {
1357
+ callback = res;
1358
+ res = null;
1359
+ }
1360
+
1361
+ if (req.original) {
1362
+ req = req.original;
1363
+ }
1364
+
1365
+ if (req.body != null) {
1366
+ return callback(null, req.body);
1367
+ }
1368
+
1369
+ // Multipart data is handled by "formidable"
1370
+ if (req.headers['content-type'] && req.headers['content-type'].startsWith('multipart/form-data')) {
1371
+
1372
+ let form = new formidable.IncomingForm();
1373
+
1374
+ // md5 hash by default
1375
+ form.hash = 'md5';
1376
+
1377
+ form.parse(req, function parsedMultipart(err, form_fields, form_files) {
1378
+
1379
+ var fields = {},
1380
+ files = {},
1381
+ key;
1382
+
1383
+ if (err && req.conduit && req.conduit.aborted) {
1384
+ return callback(null);
1385
+ }
1386
+
1387
+ // Fix the field names
1388
+ for (key in form_fields) {
1389
+ Object.setFormPath(fields, key, form_fields[key]);
1390
+ }
1391
+
1392
+ // Fix the file names
1393
+ for (key in form_files) {
1394
+ Object.setFormPath(files, key, form_files[key]);
1395
+ }
1396
+
1397
+ if (err) {
1398
+ log.error('Error parsing multipart POST', {err: err});
1399
+ req.body = {};
1400
+ req.files = {};
1401
+ } else {
1402
+ req.body = fields;
1403
+ req.files = files;
1404
+
1405
+ if (conduit) {
1406
+ conduit.setRequestBody(fields);
1407
+ conduit.setRequestFiles(files);
1408
+ }
1409
+ }
1410
+
1411
+ callback(null, fields);
1412
+ });
1413
+
1414
+ return;
1415
+ }
1416
+
1417
+ // Regular form-encoded data
1418
+ if (req.headers['content-type'] && req.headers['content-type'].indexOf('form-urlencoded') > -1) {
1419
+
1420
+ urlFormBody(req, res, function parsedBody(err) {
1421
+
1422
+ if (err && req.conduit && req.conduit.aborted) {
1423
+ return callback(null);
1424
+ }
1425
+
1426
+ // You can't send files using a regular post
1427
+ req.files = {};
1428
+
1429
+ if (err) {
1430
+ log.error('Error parsing x-www-form-urlencoded body data', {err: err});
1431
+ req.body = {};
1432
+ } else {
1433
+ req.body = req.body;
1434
+
1435
+ if (conduit) {
1436
+ conduit.setRequestBody(req.body);
1437
+ }
1438
+ }
1439
+
1440
+ callback(null, req.body);
1441
+ });
1442
+
1443
+ return;
1444
+ }
1445
+
1446
+ // Any other encoded data (like JSON)
1447
+ anyBody(req, function parsedBody(err, body) {
1448
+
1449
+ if (err && req.conduit && req.conduit.aborted) {
1450
+ return callback(null);
1451
+ }
1452
+
1453
+ // You can't send files using a regular post
1454
+ req.files = {};
1455
+
1456
+ if (err) {
1457
+ log.error('Error parsing body data', {err: err});
1458
+ req.body = {};
1459
+ } else {
1460
+ req.body = body;
1461
+
1462
+ if (conduit) {
1463
+ conduit.setRequestBody(body);
1464
+ }
1465
+ }
1466
+
1467
+ callback(null, req.body);
1468
+ });
1469
+ });
1470
+
1471
+ /**
1472
+ * Export all data
1473
+ *
1474
+ * @author Jelle De Loecker <jelle@develry.be>
1475
+ * @since 1.0.5
1476
+ * @version 1.0.5
1477
+ *
1478
+ * @return {Stream}
1479
+ */
1480
+ Alchemy.setMethod(function createExportStream(options) {
1481
+
1482
+ var stream = new require('stream').PassThrough();
1483
+
1484
+ this.exportToStream(stream, options);
1485
+
1486
+ return stream;
1487
+ });
1488
+
1489
+ /**
1490
+ * Export all data to stream
1491
+ *
1492
+ * @author Jelle De Loecker <jelle@develry.be>
1493
+ * @since 1.0.5
1494
+ * @version 1.0.5
1495
+ *
1496
+ * @param {Stream} output
1497
+ * @param {Object} options
1498
+ *
1499
+ * @return {Pledge}
1500
+ */
1501
+ Alchemy.setMethod(function exportToStream(output, options) {
1502
+
1503
+ if (!alchemy.isStream(output)) {
1504
+ if (!options) {
1505
+ options = output;
1506
+ output = null;
1507
+ }
1508
+
1509
+ output = options.output;
1510
+ }
1511
+
1512
+ if (!output) {
1513
+ return Pledge.reject(new Error('No target output stream has been given'));
1514
+ }
1515
+
1516
+ if (!options) {
1517
+ options = {};
1518
+ }
1519
+
1520
+ let tasks = [],
1521
+ i;
1522
+
1523
+ for (i = 0; i < Model.children.length; i++) {
1524
+ let model = Model.children[i];
1525
+
1526
+ tasks.push(async function exportModel(next) {
1527
+ await (new model).exportToStream(output);
1528
+ next();
1529
+ });
1530
+ }
1531
+
1532
+ return Function.series(tasks, function done(err) {
1533
+
1534
+ if (err) {
1535
+ return output.emit('error', err);
1536
+ }
1537
+
1538
+ output.end();
1539
+ });
1540
+ });
1541
+
1542
+ /**
1543
+ * Import from a stream
1544
+ *
1545
+ * @author Jelle De Loecker <jelle@develry.be>
1546
+ * @since 1.0.5
1547
+ * @version 1.0.5
1548
+ *
1549
+ * @param {Stream} input
1550
+ * @param {Object} options
1551
+ *
1552
+ * @return {Pledge}
1553
+ */
1554
+ Alchemy.setMethod(function importFromStream(input, options) {
1555
+
1556
+ if (!alchemy.isStream(input)) {
1557
+ if (!options) {
1558
+ options = input;
1559
+ input = null;
1560
+ }
1561
+
1562
+ input = options.input;
1563
+ }
1564
+
1565
+ if (!input) {
1566
+ return Pledge.reject(new Error('No source input stream has been given'));
1567
+ }
1568
+
1569
+ if (!options) {
1570
+ options = {};
1571
+ }
1572
+
1573
+ let that = this,
1574
+ current_type = null,
1575
+ extra_stream,
1576
+ pledge = new Pledge(),
1577
+ stopped,
1578
+ paused,
1579
+ buffer,
1580
+ model,
1581
+ value,
1582
+ seen = 0,
1583
+ left,
1584
+ size,
1585
+ doc;
1586
+
1587
+ input.on('data', function onData(data) {
1588
+
1589
+ if (stopped) {
1590
+ return;
1591
+ }
1592
+
1593
+ if (buffer) {
1594
+ buffer = Buffer.concat([buffer, data]);
1595
+ } else {
1596
+ buffer = data;
1597
+ }
1598
+
1599
+ handleBuffer();
1600
+ });
1601
+
1602
+ function handleBuffer() {
1603
+
1604
+ if (paused) {
1605
+ return;
1606
+ }
1607
+
1608
+ if (!current_type && buffer.length < 2) {
1609
+ return;
1610
+ }
1611
+
1612
+ if (!current_type) {
1613
+ current_type = buffer.readUInt8(0);
1614
+
1615
+ if (current_type == 0x01) {
1616
+ size = buffer.readUInt8(1);
1617
+ buffer = buffer.slice(2);
1618
+ } else if (current_type == 0x02 && buffer.length >= 5) {
1619
+ size = buffer.readUInt32BE(1);
1620
+ buffer = buffer.slice(5);
1621
+ } else if (current_type == 0xFF) {
1622
+ size = buffer.readUInt32BE(1);
1623
+ buffer = buffer.slice(5);
1624
+ seen = 0;
1625
+
1626
+ if (!doc) {
1627
+ stopped = true;
1628
+ pledge.reject(new Error('Found extra import data, but no active document'));
1629
+ } else {
1630
+ extra_stream = new require('stream').PassThrough();
1631
+ doc.extraImportFromStream(extra_stream);
1632
+ }
1633
+ } else {
1634
+ // Not enough data? Wait
1635
+ current_type = null;
1636
+ return;
1637
+ }
1638
+ }
1639
+
1640
+ handleRest();
1641
+ }
1642
+
1643
+ function handleRest() {
1644
+
1645
+ if (current_type == 0xFF) {
1646
+ left = size - seen;
1647
+ value = buffer.slice(0, left);
1648
+
1649
+ seen += value.length;
1650
+
1651
+ if (value.length == buffer.length) {
1652
+ buffer = null;
1653
+ } else if (value.length < buffer.length) {
1654
+ buffer = buffer.slice(left);
1655
+ }
1656
+
1657
+ extra_stream.write(value);
1658
+
1659
+ if (value.length == left) {
1660
+ extra_stream.end();
1661
+ current_type = null;
1662
+
1663
+ if (buffer) {
1664
+ handleBuffer();
1665
+ }
1666
+ }
1667
+
1668
+ return;
1669
+ }
1670
+
1671
+ if (buffer.length >= size) {
1672
+ value = buffer.slice(0, size);
1673
+ buffer = buffer.slice(size);
1674
+ } else {
1675
+ // Wait for next call
1676
+ return;
1677
+ }
1678
+
1679
+ if (current_type == 0x01) {
1680
+ value = value.toString();
1681
+
1682
+ if (!model || model.model_name != value) {
1683
+ model = Model.get(value);
1684
+ doc = null;
1685
+ }
1686
+
1687
+ if (!model) {
1688
+ stopped = true;
1689
+ return pledge.reject(new Error('Could not find Model "' + value + '"'));
1690
+ }
1691
+
1692
+ current_type = null;
1693
+ size = 0;
1694
+ } else if (current_type == 0x02) {
1695
+ doc = model.createDocument();
1696
+ input.pause();
1697
+ paused = true;
1698
+
1699
+ doc.importFromBuffer(value).done(function done(err, result) {
1700
+
1701
+ if (err) {
1702
+ stopped = true;
1703
+ return pledge.reject(err);
1704
+ }
1705
+
1706
+ current_type = null;
1707
+ paused = false;
1708
+ input.resume();
1709
+
1710
+ handleBuffer();
1711
+ });
1712
+
1713
+ return;
1714
+ }
1715
+
1716
+ if (buffer && buffer.length) {
1717
+ handleBuffer();
1718
+ }
1719
+ }
1720
+
1721
+ return pledge;
1722
+ });
1723
+
1724
+ /**
1725
+ * Create a new schema
1726
+ *
1727
+ * @author Jelle De Loecker <jelle@elevenways.be>
1728
+ * @since 1.2.1
1729
+ * @version 1.2.1
1730
+ *
1731
+ * @param {*} parent
1732
+ */
1733
+ Alchemy.setMethod(function createSchema(parent) {
1734
+ let schema = new Classes.Alchemy.Schema(parent);
1735
+ return schema;
1736
+ });
1737
+
1738
+ /**
1739
+ * The alchemy global, where everything will be stored
1740
+ *
1741
+ * @author Jelle De Loecker <jelle@develry.be>
1742
+ * @since 0.0.1
1743
+ * @version 0.4.0
1744
+ *
1745
+ * @type {Alchemy}
1746
+ */
1747
+ DEFINE('alchemy', new Alchemy());
1748
+
1749
+ /**
1750
+ * Define the log function
1751
+ *
1752
+ * @author Jelle De Loecker <jelle@develry.be>
1753
+ * @since 0.0.1
1754
+ * @version 0.4.0
1755
+ *
1756
+ * @type {Function}
1757
+ */
1758
+ DEFINE('log', alchemy.log);
1759
+
1760
+ for (let key in alchemy.Janeway.LEVELS) {
1761
+ let name = key.toLowerCase();
1762
+ let val = alchemy.Janeway.LEVELS[key];
1763
+
1764
+ log[name] = function(...args) {
1765
+ return alchemy.printLog(val, args, {level: 2});
1766
+ };
1767
+ }
1768
+
1769
+ log.warn = log.warning;
1770
+
1771
+ /**
1772
+ * Define the todo log function
1773
+ *
1774
+ * @author Jelle De Loecker <jelle@develry.be>
1775
+ * @since 0.2.0
1776
+ * @version 0.4.0
1777
+ *
1778
+ * @type {Function}
1779
+ */
1780
+ log.todo = function todo(...args) {
1781
+
1782
+ var options = {
1783
+ gutter: alchemy.Janeway.esc(91) + '\u2620 Todo:' + alchemy.Janeway.esc(39),
1784
+ level: 2
1785
+ };
1786
+
1787
+ return alchemy.printLog(alchemy.TODO, args, options);
1788
+ };
1789
+
1790
+ const anyBody = alchemy.use('body/any'),
1791
+ formBody = alchemy.use('body/form'),
1792
+ formidable = alchemy.use('formidable'),
1793
+ bodyParser = alchemy.use('body-parser'),
1780
1794
  urlFormBody = bodyParser.urlencoded({extended: true});