alchemymvc 1.2.5 → 1.2.6

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.
Files changed (76) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +0 -0
  3. package/lib/app/assets/scripts/.gitkeep +0 -0
  4. package/lib/app/assets/stylesheets/alchemy-info.less +0 -0
  5. package/lib/app/behaviour/publishable_behaviour.js +0 -0
  6. package/lib/app/behaviour/revision_behaviour.js +0 -0
  7. package/lib/app/behaviour/sluggable_behaviour.js +0 -0
  8. package/lib/app/component/.gitkeep +0 -0
  9. package/lib/app/conduit/electron_conduit.js +0 -0
  10. package/lib/app/conduit/http_conduit.js +173 -173
  11. package/lib/app/conduit/socket_conduit.js +620 -620
  12. package/lib/app/controller/alchemy_info_controller.js +0 -0
  13. package/lib/app/datasource/mongo_datasource.js +0 -0
  14. package/lib/app/helper/client_collection.js +0 -0
  15. package/lib/app/helper/pagination_helper.js +0 -0
  16. package/lib/app/helper/router_helper.js +0 -0
  17. package/lib/app/helper/socket_helper.js +613 -613
  18. package/lib/app/helper_component/paginate_component.js +0 -0
  19. package/lib/app/helper_controller/component.js +0 -0
  20. package/lib/app/helper_controller/conduit.js +0 -0
  21. package/lib/app/helper_controller/controller.js +0 -0
  22. package/lib/app/helper_datasource/00-nosql_datasource.js +0 -0
  23. package/lib/app/helper_datasource/05-fallback_datasource.js +0 -0
  24. package/lib/app/helper_datasource/idb_datasource.js +0 -0
  25. package/lib/app/helper_datasource/indexed_db.js +0 -0
  26. package/lib/app/helper_field/00-objectid_field.js +0 -0
  27. package/lib/app/helper_field/06-text_field.js +0 -0
  28. package/lib/app/helper_field/10-number_field.js +0 -0
  29. package/lib/app/helper_field/boolean_field.js +0 -0
  30. package/lib/app/helper_field/date_field.js +0 -0
  31. package/lib/app/helper_field/datetime_field.js +0 -0
  32. package/lib/app/helper_field/enum_field.js +0 -0
  33. package/lib/app/helper_field/geopoint_field.js +0 -0
  34. package/lib/app/helper_field/habtm_field.js +0 -0
  35. package/lib/app/helper_field/hasoneparent_field.js +0 -0
  36. package/lib/app/helper_field/html_field.js +0 -0
  37. package/lib/app/helper_field/integer_field.js +0 -0
  38. package/lib/app/helper_field/object_field.js +0 -0
  39. package/lib/app/helper_field/regexp_field.js +0 -0
  40. package/lib/app/helper_field/schema_field.js +23 -2
  41. package/lib/app/helper_field/time_field.js +0 -0
  42. package/lib/app/helper_field/url_field.js +0 -0
  43. package/lib/app/helper_model/criteria.js +0 -0
  44. package/lib/app/helper_model/db_query.js +0 -0
  45. package/lib/app/helper_model/document_list.js +0 -0
  46. package/lib/app/model/alchemy_task_model.js +0 -0
  47. package/lib/app/routes.js +0 -0
  48. package/lib/app/view/alchemy/info.ejs +0 -0
  49. package/lib/app/view/error/unknown.ejs +0 -0
  50. package/lib/app/view/paginate/navlist.ejs +0 -0
  51. package/lib/bootstrap.js +0 -0
  52. package/lib/class/behaviour.js +0 -0
  53. package/lib/class/component.js +0 -0
  54. package/lib/class/conduit.js +2555 -2552
  55. package/lib/class/controller.js +4 -1
  56. package/lib/class/document_list.js +0 -0
  57. package/lib/class/helper.js +0 -0
  58. package/lib/class/inode.js +0 -0
  59. package/lib/class/inode_dir.js +0 -0
  60. package/lib/class/inode_file.js +112 -112
  61. package/lib/class/inode_list.js +0 -0
  62. package/lib/class/model.js +1772 -1769
  63. package/lib/class/path_definition.js +0 -0
  64. package/lib/class/route.js +0 -0
  65. package/lib/class/session.js +0 -0
  66. package/lib/class/task.js +0 -0
  67. package/lib/core/base.js +50 -9
  68. package/lib/core/discovery.js +0 -0
  69. package/lib/core/routing.js +0 -0
  70. package/lib/core/socket.js +159 -159
  71. package/lib/init/alchemy.js +1823 -1823
  72. package/lib/init/constants.js +0 -0
  73. package/lib/init/functions.js +8 -4
  74. package/lib/init/load_functions.js +0 -0
  75. package/lib/init/requirements.js +101 -101
  76. package/package.json +74 -74
@@ -1,1824 +1,1824 @@
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
- * Look for a module by traversing the filesystem
756
- *
757
- * @author Jelle De Loecker <jelle@develry.be>
758
- * @since 0.0.1
759
- * @version 0.4.0
760
- *
761
- * @param {String} startPath The path to originate the search from
762
- * @param {String} moduleName
763
- * @param {Number} recurse
764
- *
765
- * @return {String}
766
- */
767
- Alchemy.setMethod(function searchModule(startPath, moduleName, recurse) {
768
-
769
- var moduledirs,
770
- module_path,
771
- entries,
772
- nmPath,
773
- temp,
774
- path,
775
- key,
776
- i;
777
-
778
- // Don't do this search if it hasn't been enabled
779
- // The new npm flat structure makes this an expensive thing to do
780
- if (!this.settings.search_for_modules) {
781
-
782
- // Set recurse to 3, so this is the first and last call
783
- recurse = 3;
784
-
785
- // Only add 2 folder to look through,
786
- // the alchemymvc node_modules folder
787
- // and the base node_modules folder
788
- moduledirs = ['..', libpath.resolve(startPath, 'node_modules', 'alchemymvc')];
789
-
790
- // Add plugin folders
791
- if (!plugModules) {
792
- path = libpath.resolve(PATH_ROOT, 'node_modules');
793
-
794
- if (fs.existsSync(path)) {
795
-
796
- // Get all the entries in the main modules folder
797
- entries = fs.readdirSync(libpath.resolve(PATH_ROOT, 'node_modules'));
798
-
799
- // Initiate the plugin modules variables
800
- plugModules = [];
801
-
802
-
803
- for (i = 0; i < entries.length; i++) {
804
- temp = entries[i];
805
-
806
- if (temp.startsWith('alchemy-')) {
807
- plugModules.push(libpath.resolve(PATH_ROOT, 'node_modules', temp));
808
- }
809
- }
810
- } else {
811
- plugModules = [];
812
- }
813
- }
814
-
815
- for (i = 0; i < plugModules.length; i++) {
816
- moduledirs.push(plugModules[i]);
817
- }
818
-
819
- } else if (!searchModule.have_warned) {
820
- searchModule.have_warned = true;
821
- log.warn('The "search_for_modules" config has been enabled!');
822
- }
823
-
824
- if (!recurse) {
825
- recurse = 1;
826
- }
827
-
828
- nmPath = libpath.resolve(startPath, 'node_modules');
829
-
830
- if (!moduledirs) {
831
- // Get all the entries inside the given path
832
- try {
833
- moduledirs = fs.readdirSync(nmPath);
834
- } catch(err) {
835
- return;
836
- }
837
- }
838
-
839
- // Look in the base node_modules directory first
840
- if (recurse == 1) {
841
- moduledirs.unshift('..');
842
- }
843
-
844
- // Go over every directory in the main node_modules folder
845
- for (i = 0; i < moduledirs.length; i++) {
846
-
847
- key = moduledirs[i];
848
-
849
- try {
850
- // Let require find the specific file to get
851
- module_path = require.resolve(libpath.resolve(nmPath, key, 'node_modules', moduleName));
852
-
853
- // If no errors have popped up now, we can break the for loop
854
- break;
855
-
856
- } catch(e) {
857
- // Do nothing
858
- }
859
- }
860
-
861
- if (!module_path && recurse < 3) {
862
- for (i = 0; i < moduledirs.length; i++) {
863
-
864
- module_path = this.searchModule(libpath.resolve(nmPath, moduledirs[i]), moduleName, recurse+1);
865
-
866
- if (module_path) {
867
- break;
868
- }
869
- }
870
- }
871
-
872
- return module_path;
873
- });
874
-
875
- /**
876
- * Find a module in our customized file structure
877
- *
878
- * @author Jelle De Loecker <jelle@develry.be>
879
- * @since 0.0.1
880
- * @version 1.2.2
881
- *
882
- * @param {String} moduleName
883
- * @param {Object} options
884
- *
885
- * @return {Object}
886
- */
887
- Alchemy.setMethod(function findModule(moduleName, options) {
888
-
889
- var package_json,
890
- module_path,
891
- internal,
892
- module,
893
- result,
894
- time,
895
- key,
896
- i;
897
-
898
- if (!options) {
899
- options = {};
900
- }
901
-
902
- if (options.require == null) {
903
- options.require = true;
904
- }
905
-
906
- // If we've required this once before, return it
907
- if (result = usePaths[moduleName]) {
908
- if (result.err) {
909
- throw result.err;
910
- }
911
-
912
- // Only return the cached module if it was required then,
913
- // or if require is now false
914
- if (result.module || !options.require) {
915
- return result;
916
- }
917
- }
918
-
919
- result = {
920
- err : null,
921
- module : null,
922
- module_dir : null,
923
- module_path : null,
924
- package : null,
925
- internal : null,
926
- search_time : null,
927
- };
928
-
929
- time = Date.now();
930
-
931
- // Simply try to resolve the module by name
932
- try {
933
- module_path = require.resolve(moduleName);
934
- } catch (err) {
935
- result.err = err;
936
- }
937
-
938
- // If that path wasn't found, look through the root node_modules
939
- if (result.err) {
940
- try {
941
- module_path = this.searchModule(PATH_ROOT, moduleName);
942
- } catch (err) {
943
- console.log(err);
944
- return;
945
- }
946
- }
947
-
948
- // If the module_path was found, actually require the module
949
- if (module_path) {
950
-
951
- // Get the package.json file
952
- if (~module_path.indexOf(libpath.sep)) {
953
- internal = false;
954
-
955
- let last_piece = moduleName.split(libpath.sep).last(),
956
- package_path;
957
-
958
- let module_dir = libpath.dirname(module_path);
959
- result.module_dir = module_dir;
960
-
961
- // If the path doesn't end with the module name, look for it
962
- if (!module_path.endsWith(last_piece)) {
963
- package_path = module_path.beforeLast(last_piece) + last_piece;
964
- package_path = libpath.resolve(package_path, 'package.json');
965
- } else {
966
- package_path = libpath.resolve(module_dir, 'package.json');
967
- }
968
-
969
- try {
970
- package_json = require(package_path);
971
- } catch (err) {
972
- package_json = false;
973
- }
974
- } else {
975
- internal = true;
976
- package_json = {
977
- version: process.versions.node
978
- };
979
- }
980
-
981
- // Modules are required by default
982
- if (options.require) {
983
- if (package_json && package_json.type == 'module' && !(package_json.main && package_json.module)) {
984
- module = doImport(module_path)
985
- } else {
986
- module = require(module_path);
987
- }
988
- }
989
- }
990
-
991
- if (!options.require || module) {
992
- result.err = null;
993
- }
994
-
995
- if (module) {
996
- result.module = module;
997
- }
998
-
999
- result.module_path = module_path;
1000
- result.package = package_json;
1001
- result.internal = internal;
1002
-
1003
- // Save the result
1004
- usePaths[moduleName] = result;
1005
-
1006
- // If there was an error, throw it now
1007
- if (result.err) {
1008
- throw result.err;
1009
- }
1010
-
1011
- result.search_time = Date.now() - time;
1012
-
1013
- // Else return the result
1014
- return result;
1015
- });
1016
-
1017
- /**
1018
- * Create a shared object
1019
- *
1020
- * @author Jelle De Loecker <jelle@develry.be>
1021
- * @since 0.0.1
1022
- * @version 0.4.0
1023
- *
1024
- * @param {String} name The name of the object to get
1025
- * @param {String} type The type to create (array or object)
1026
- *
1027
- * @return {Object|Array}
1028
- */
1029
- Alchemy.setMethod(function shared(name, type, value) {
1030
-
1031
- if (typeof type !== 'string') {
1032
- value = type;
1033
- type = 'object';
1034
- }
1035
-
1036
- // Create it if it doesn't exist
1037
- if (!shared_objects[name]) {
1038
- if (type === 'array' || type === 'Array') {
1039
- shared_objects[name] = value || [];
1040
- } else {
1041
- shared_objects[name] = value || {};
1042
- }
1043
- }
1044
-
1045
- return shared_objects[name];
1046
- });
1047
-
1048
- /**
1049
- * Get an object id,
1050
- * return undefined if no valid data was given (instead of throwing an error)
1051
- *
1052
- * @author Jelle De Loecker <jelle@develry.be>
1053
- * @since 0.0.1
1054
- * @version 0.4.0
1055
- *
1056
- * @param {String|ObjectID} obj
1057
- *
1058
- * @return {ObjectID|undefined}
1059
- */
1060
- Alchemy.setMethod(function castObjectId(obj) {
1061
-
1062
- var type = typeof obj;
1063
-
1064
- if (obj && type === 'object' && obj.constructor && obj.constructor.name === 'ObjectID') {
1065
- return obj;
1066
- } else if (type === 'string' && obj.isObjectId()) {
1067
- return alchemy.ObjectId(obj);
1068
- }
1069
-
1070
- return undefined;
1071
- });
1072
-
1073
- /**
1074
- * See if the given object is a stream
1075
- *
1076
- * @author Jelle De Loecker <jelle@develry.be>
1077
- * @since 0.2.0
1078
- * @version 1.0.5
1079
- *
1080
- * @return {Boolean}
1081
- */
1082
- Alchemy.setMethod(function isStream(obj) {
1083
- return obj && (typeof obj._read == 'function' || typeof obj._write == 'function') && typeof obj.on === 'function';
1084
- });
1085
-
1086
- /**
1087
- * Get or create a new cache instance
1088
- *
1089
- * @author Jelle De Loecker <jelle@develry.be>
1090
- * @since 1.0.0
1091
- * @version 1.0.4
1092
- *
1093
- * @param {String} name
1094
- * @param {Number|Object} options
1095
- *
1096
- * @return {Develry.Cache}
1097
- */
1098
- Alchemy.setMethod(function getCache(name, options) {
1099
-
1100
- var instance,
1101
- duration,
1102
- config,
1103
- type;
1104
-
1105
- if (this.caches[name]) {
1106
- return this.caches[name];
1107
- }
1108
-
1109
- if (!options) {
1110
- options = {};
1111
- }
1112
-
1113
- type = typeof options;
1114
-
1115
- if (type == 'number' || type == 'string') {
1116
- options = {
1117
- max_age : options,
1118
- };
1119
- }
1120
-
1121
- config = Object.assign({
1122
- max_length : 5000,
1123
- }, options);
1124
-
1125
- // @TODO: Fixed in 0.6.1
1126
- instance = new Blast.Classes.Develry.Cache();
1127
- Object.assign(instance, config);
1128
-
1129
- this.caches[name] = instance;
1130
-
1131
- return instance;
1132
- });
1133
-
1134
- /**
1135
- * Get a route
1136
- *
1137
- * @author Jelle De Loecker <jelle@elevenways.be>
1138
- * @since 1.1.3
1139
- * @version 1.1.3
1140
- *
1141
- * @param {String} href The href or route name
1142
- * @param {Object} parameters Route parameters
1143
- *
1144
- * @return {String}
1145
- */
1146
- Alchemy.setMethod(function routeUrl(href, parameters) {
1147
-
1148
- let temp = Router.getUrl(href, parameters);
1149
-
1150
- if (temp && temp.href) {
1151
- temp = String(temp);
1152
-
1153
- if (temp) {
1154
- return temp;
1155
- }
1156
- }
1157
-
1158
- return href;
1159
- });
1160
-
1161
- /**
1162
- * Get paths that should be cached by the client
1163
- *
1164
- * @author Jelle De Loecker <jelle@develry.be>
1165
- * @since 1.0.7
1166
- * @version 1.0.7
1167
- *
1168
- * @return {Pledge}
1169
- */
1170
- Alchemy.decorateMethod(Blast.Decorators.memoize(), function getAppcachePaths() {
1171
-
1172
- var paths = [];
1173
-
1174
- return Function.parallel(function getHawkejsTemplates(next) {
1175
-
1176
- var templates = [],
1177
- directories = alchemy.hawkejs.directories.getSorted(),
1178
- tasks = [],
1179
- i;
1180
-
1181
- function checkDirectory(dir_path, mount_path, next) {
1182
- fs.readdir(dir_path, function gotDir(err, files) {
1183
-
1184
- if (err) {
1185
-
1186
- if (err.code == 'ENOENT') {
1187
- return next();
1188
- }
1189
-
1190
- return next(err);
1191
- }
1192
-
1193
- let tasks = [],
1194
- i;
1195
-
1196
- for (i = 0; i < files.length; i++) {
1197
- let file = files[i],
1198
- full_path = libpath.resolve(dir_path, file),
1199
- full_mount_path = mount_path + '/' + file;
1200
-
1201
- tasks.push(function checkPath(next) {
1202
- fs.stat(full_path, function gotStat(err, stat) {
1203
-
1204
- if (err) {
1205
-
1206
- if (err.code == 'ENOENT') {
1207
- return next();
1208
- }
1209
-
1210
- return next(err);
1211
- }
1212
-
1213
- if (stat.isDirectory()) {
1214
- return checkDirectory(full_path, full_mount_path, next);
1215
- }
1216
-
1217
- if (stat.isFile()) {
1218
- if (file.endsWith('.ejs') || file.endsWith('.hwk')) {
1219
-
1220
- if (full_mount_path[0] == '/') {
1221
- full_mount_path = full_mount_path.slice(1);
1222
- }
1223
-
1224
- templates.push(full_mount_path);
1225
- }
1226
- }
1227
-
1228
- next();
1229
- });
1230
- });
1231
- }
1232
-
1233
- Function.parallel(tasks, next);
1234
- });
1235
- }
1236
-
1237
- for (i = 0; i < directories.length; i++) {
1238
- let directory = directories[i];
1239
-
1240
- tasks.push(function readDir(next) {
1241
- checkDirectory(directory, '', next);
1242
- });
1243
- }
1244
-
1245
- return Function.parallel(tasks, function done(err) {
1246
-
1247
- if (err) {
1248
- return next(err);
1249
- }
1250
-
1251
- let path,
1252
- url,
1253
- i;
1254
-
1255
- for (i = 0; i < templates.length; i++) {
1256
- path = templates[i];
1257
- url = '/hawkejs/templates?name[0]=' + encodeURIComponent(path.beforeLast('.ejs')) + '&v=' + alchemy.package.version;
1258
- paths.push(url);
1259
- }
1260
-
1261
- next();
1262
- });
1263
- }, function done(err) {
1264
-
1265
- if (err) {
1266
- return;
1267
- }
1268
-
1269
- return paths;
1270
- });
1271
- });
1272
-
1273
- /**
1274
- * Get the appcache manifest text
1275
- *
1276
- * @author Jelle De Loecker <jelle@develry.be>
1277
- * @since 1.0.7
1278
- * @version 1.0.7
1279
- *
1280
- * @return {Pledge}
1281
- */
1282
- Alchemy.decorateMethod(Blast.Decorators.memoize(), function getAppcacheManifest() {
1283
-
1284
- return Function.series(function gotPaths(next) {
1285
- alchemy.getAppcachePaths().done(next);
1286
- }, function createText(err, result) {
1287
-
1288
- if (err) {
1289
- return;
1290
- }
1291
-
1292
- let manifest = 'CACHE MANIFEST\n\n',
1293
- entry,
1294
- url,
1295
- key,
1296
- i;
1297
-
1298
- manifest += 'CACHE:\n';
1299
-
1300
- // Allways add the client script
1301
- manifest += '/hawkejs/hawkejs-client.js?v=' + alchemy.package.version + '\n';
1302
-
1303
- if (ac_entries.cache && ac_entries.cache.length) {
1304
- for (key in ac_entries.cache) {
1305
- manifest += ac_entries.cache[key].url + '\n';
1306
- }
1307
- }
1308
-
1309
- for (i = 0; i < result[0].length; i++) {
1310
- url = result[0][i];
1311
-
1312
- manifest += url + '\n';
1313
- }
1314
-
1315
- manifest += '\n';
1316
- manifest += 'NETWORK:\n*\n\n';
1317
-
1318
- // This will cause a cache update each time the server is reset
1319
- manifest += '#' + alchemy.package.version + '-' + alchemy.discovery_id;
1320
-
1321
- return manifest;
1322
- });
1323
- });
1324
-
1325
- /**
1326
- * Add an appcache entry
1327
- *
1328
- * @author Jelle De Loecker <jelle@develry.be>
1329
- * @since 1.0.7
1330
- * @version 1.0.7
1331
- *
1332
- * @param {String|Object}
1333
- */
1334
- Alchemy.setMethod(function addAppcacheEntry(entry) {
1335
-
1336
- if (typeof entry == 'string') {
1337
- entry = {
1338
- url : entry
1339
- };
1340
- }
1341
-
1342
- if (!entry.type) {
1343
- entry.type = 'cache';
1344
- } else {
1345
- entry.type = entry.type.toLowerCase();
1346
- }
1347
-
1348
- if (!ac_entries[entry.type]) {
1349
- ac_entries[entry.type] = [];
1350
- }
1351
-
1352
- ac_entries[entry.type].push(entry);
1353
- });
1354
-
1355
- /**
1356
- * Get the body of an IncomingMessage
1357
- *
1358
- * @author Jelle De Loecker <jelle@develry.be>
1359
- * @since 1.1.0
1360
- * @version 1.1.0
1361
- *
1362
- * @param {IncomingMessage} req
1363
- * @param {OutgoingMessage} res Optional
1364
- * @param {Function} callback
1365
- */
1366
- Alchemy.setMethod(function parseRequestBody(req, res, callback) {
1367
-
1368
- const conduit = req.conduit;
1369
-
1370
- if (typeof res == 'function') {
1371
- callback = res;
1372
- res = null;
1373
- }
1374
-
1375
- if (req.original) {
1376
- req = req.original;
1377
- }
1378
-
1379
- if (req.body != null) {
1380
- return callback(null, req.body);
1381
- }
1382
-
1383
- // Multipart data is handled by "formidable"
1384
- if (req.headers['content-type'] && req.headers['content-type'].startsWith('multipart/form-data')) {
1385
-
1386
- let form = new formidable.IncomingForm();
1387
-
1388
- // md5 hash by default
1389
- form.hash = 'md5';
1390
-
1391
- form.parse(req, function parsedMultipart(err, form_fields, form_files) {
1392
-
1393
- var fields = {},
1394
- files = {},
1395
- key;
1396
-
1397
- if (err && req.conduit && req.conduit.aborted) {
1398
- return callback(null);
1399
- }
1400
-
1401
- // Fix the field names
1402
- for (key in form_fields) {
1403
- Object.setFormPath(fields, key, form_fields[key]);
1404
- }
1405
-
1406
- // Fix the file names
1407
- for (key in form_files) {
1408
- Object.setFormPath(files, key, form_files[key]);
1409
- }
1410
-
1411
- if (err) {
1412
- log.error('Error parsing multipart POST', {err: err});
1413
- req.body = {};
1414
- req.files = {};
1415
- } else {
1416
- req.body = fields;
1417
- req.files = files;
1418
-
1419
- if (conduit) {
1420
- conduit.setRequestBody(fields);
1421
- conduit.setRequestFiles(files);
1422
- }
1423
- }
1424
-
1425
- callback(null, fields);
1426
- });
1427
-
1428
- return;
1429
- }
1430
-
1431
- // Regular form-encoded data
1432
- if (req.headers['content-type'] && req.headers['content-type'].indexOf('form-urlencoded') > -1) {
1433
-
1434
- urlFormBody(req, res, function parsedBody(err) {
1435
-
1436
- if (err && req.conduit && req.conduit.aborted) {
1437
- return callback(null);
1438
- }
1439
-
1440
- // You can't send files using a regular post
1441
- req.files = {};
1442
-
1443
- if (err) {
1444
- log.error('Error parsing x-www-form-urlencoded body data', {err: err});
1445
- req.body = {};
1446
- } else {
1447
- req.body = req.body;
1448
-
1449
- if (conduit) {
1450
- conduit.setRequestBody(req.body);
1451
- }
1452
- }
1453
-
1454
- callback(null, req.body);
1455
- });
1456
-
1457
- return;
1458
- }
1459
-
1460
- // Any other encoded data (like JSON)
1461
- anyBody(req, function parsedBody(err, body) {
1462
-
1463
- if (err && req.conduit && req.conduit.aborted) {
1464
- return callback(null);
1465
- }
1466
-
1467
- // You can't send files using a regular post
1468
- req.files = {};
1469
-
1470
- if (err) {
1471
- log.error('Error parsing body data', {err: err});
1472
- req.body = {};
1473
- } else {
1474
- req.body = body;
1475
-
1476
- if (conduit) {
1477
- conduit.setRequestBody(body);
1478
- }
1479
- }
1480
-
1481
- callback(null, req.body);
1482
- });
1483
- });
1484
-
1485
- /**
1486
- * Export all data
1487
- *
1488
- * @author Jelle De Loecker <jelle@develry.be>
1489
- * @since 1.0.5
1490
- * @version 1.0.5
1491
- *
1492
- * @return {Stream}
1493
- */
1494
- Alchemy.setMethod(function createExportStream(options) {
1495
-
1496
- var stream = new require('stream').PassThrough();
1497
-
1498
- this.exportToStream(stream, options);
1499
-
1500
- return stream;
1501
- });
1502
-
1503
- /**
1504
- * Export all data to stream
1505
- *
1506
- * @author Jelle De Loecker <jelle@develry.be>
1507
- * @since 1.0.5
1508
- * @version 1.0.5
1509
- *
1510
- * @param {Stream} output
1511
- * @param {Object} options
1512
- *
1513
- * @return {Pledge}
1514
- */
1515
- Alchemy.setMethod(function exportToStream(output, options) {
1516
-
1517
- if (!alchemy.isStream(output)) {
1518
- if (!options) {
1519
- options = output;
1520
- output = null;
1521
- }
1522
-
1523
- output = options.output;
1524
- }
1525
-
1526
- if (!output) {
1527
- return Pledge.reject(new Error('No target output stream has been given'));
1528
- }
1529
-
1530
- if (!options) {
1531
- options = {};
1532
- }
1533
-
1534
- let tasks = [],
1535
- i;
1536
-
1537
- for (i = 0; i < Model.children.length; i++) {
1538
- let model = Model.children[i];
1539
-
1540
- tasks.push(async function exportModel(next) {
1541
- await (new model).exportToStream(output);
1542
- next();
1543
- });
1544
- }
1545
-
1546
- return Function.series(tasks, function done(err) {
1547
-
1548
- if (err) {
1549
- return output.emit('error', err);
1550
- }
1551
-
1552
- output.end();
1553
- });
1554
- });
1555
-
1556
- /**
1557
- * Import from a stream
1558
- *
1559
- * @author Jelle De Loecker <jelle@develry.be>
1560
- * @since 1.0.5
1561
- * @version 1.0.5
1562
- *
1563
- * @param {Stream} input
1564
- * @param {Object} options
1565
- *
1566
- * @return {Pledge}
1567
- */
1568
- Alchemy.setMethod(function importFromStream(input, options) {
1569
-
1570
- if (!alchemy.isStream(input)) {
1571
- if (!options) {
1572
- options = input;
1573
- input = null;
1574
- }
1575
-
1576
- input = options.input;
1577
- }
1578
-
1579
- if (!input) {
1580
- return Pledge.reject(new Error('No source input stream has been given'));
1581
- }
1582
-
1583
- if (!options) {
1584
- options = {};
1585
- }
1586
-
1587
- let that = this,
1588
- current_type = null,
1589
- extra_stream,
1590
- pledge = new Pledge(),
1591
- stopped,
1592
- paused,
1593
- buffer,
1594
- model,
1595
- value,
1596
- seen = 0,
1597
- left,
1598
- size,
1599
- doc;
1600
-
1601
- input.on('data', function onData(data) {
1602
-
1603
- if (stopped) {
1604
- return;
1605
- }
1606
-
1607
- if (buffer) {
1608
- buffer = Buffer.concat([buffer, data]);
1609
- } else {
1610
- buffer = data;
1611
- }
1612
-
1613
- handleBuffer();
1614
- });
1615
-
1616
- function handleBuffer() {
1617
-
1618
- if (paused) {
1619
- return;
1620
- }
1621
-
1622
- if (!current_type && buffer.length < 2) {
1623
- return;
1624
- }
1625
-
1626
- if (!current_type) {
1627
- current_type = buffer.readUInt8(0);
1628
-
1629
- if (current_type == 0x01) {
1630
- size = buffer.readUInt8(1);
1631
- buffer = buffer.slice(2);
1632
- } else if (current_type == 0x02 && buffer.length >= 5) {
1633
- size = buffer.readUInt32BE(1);
1634
- buffer = buffer.slice(5);
1635
- } else if (current_type == 0xFF) {
1636
- size = buffer.readUInt32BE(1);
1637
- buffer = buffer.slice(5);
1638
- seen = 0;
1639
-
1640
- if (!doc) {
1641
- stopped = true;
1642
- pledge.reject(new Error('Found extra import data, but no active document'));
1643
- } else {
1644
- extra_stream = new require('stream').PassThrough();
1645
- doc.extraImportFromStream(extra_stream);
1646
- }
1647
- } else {
1648
- // Not enough data? Wait
1649
- current_type = null;
1650
- return;
1651
- }
1652
- }
1653
-
1654
- handleRest();
1655
- }
1656
-
1657
- function handleRest() {
1658
-
1659
- if (current_type == 0xFF) {
1660
- left = size - seen;
1661
- value = buffer.slice(0, left);
1662
-
1663
- seen += value.length;
1664
-
1665
- if (value.length == buffer.length) {
1666
- buffer = null;
1667
- } else if (value.length < buffer.length) {
1668
- buffer = buffer.slice(left);
1669
- }
1670
-
1671
- extra_stream.write(value);
1672
-
1673
- if (value.length == left) {
1674
- extra_stream.end();
1675
- current_type = null;
1676
-
1677
- if (buffer) {
1678
- handleBuffer();
1679
- }
1680
- }
1681
-
1682
- return;
1683
- }
1684
-
1685
- if (buffer.length >= size) {
1686
- value = buffer.slice(0, size);
1687
- buffer = buffer.slice(size);
1688
- } else {
1689
- // Wait for next call
1690
- return;
1691
- }
1692
-
1693
- if (current_type == 0x01) {
1694
- value = value.toString();
1695
-
1696
- if (!model || model.model_name != value) {
1697
- model = Model.get(value);
1698
- doc = null;
1699
- }
1700
-
1701
- if (!model) {
1702
- stopped = true;
1703
- return pledge.reject(new Error('Could not find Model "' + value + '"'));
1704
- }
1705
-
1706
- current_type = null;
1707
- size = 0;
1708
- } else if (current_type == 0x02) {
1709
- doc = model.createDocument();
1710
- input.pause();
1711
- paused = true;
1712
-
1713
- doc.importFromBuffer(value).done(function done(err, result) {
1714
-
1715
- if (err) {
1716
- stopped = true;
1717
- return pledge.reject(err);
1718
- }
1719
-
1720
- current_type = null;
1721
- paused = false;
1722
- input.resume();
1723
-
1724
- handleBuffer();
1725
- });
1726
-
1727
- return;
1728
- }
1729
-
1730
- if (buffer && buffer.length) {
1731
- handleBuffer();
1732
- }
1733
- }
1734
-
1735
- return pledge;
1736
- });
1737
-
1738
- /**
1739
- * Create a new schema
1740
- *
1741
- * @author Jelle De Loecker <jelle@elevenways.be>
1742
- * @since 1.2.1
1743
- * @version 1.2.1
1744
- *
1745
- * @param {*} parent
1746
- */
1747
- Alchemy.setMethod(function createSchema(parent) {
1748
- let schema = new Classes.Alchemy.Schema(parent);
1749
- return schema;
1750
- });
1751
-
1752
- /**
1753
- * Get a client-side model
1754
- *
1755
- * @author Jelle De Loecker <jelle@elevenways.be>
1756
- * @since 1.2.4
1757
- * @version 1.2.4
1758
- *
1759
- * @param {String} name
1760
- * @param {Object} options
1761
- *
1762
- * @return {Model}
1763
- */
1764
- Alchemy.setMethod(function getClientModel(name, init, options) {
1765
- return Classes.Alchemy.Client.Base.prototype.getModel.call(this, name, init, options);
1766
- });
1767
-
1768
- /**
1769
- * The alchemy global, where everything will be stored
1770
- *
1771
- * @author Jelle De Loecker <jelle@develry.be>
1772
- * @since 0.0.1
1773
- * @version 0.4.0
1774
- *
1775
- * @type {Alchemy}
1776
- */
1777
- DEFINE('alchemy', new Alchemy());
1778
-
1779
- /**
1780
- * Define the log function
1781
- *
1782
- * @author Jelle De Loecker <jelle@develry.be>
1783
- * @since 0.0.1
1784
- * @version 0.4.0
1785
- *
1786
- * @type {Function}
1787
- */
1788
- DEFINE('log', alchemy.log);
1789
-
1790
- for (let key in alchemy.Janeway.LEVELS) {
1791
- let name = key.toLowerCase();
1792
- let val = alchemy.Janeway.LEVELS[key];
1793
-
1794
- log[name] = function(...args) {
1795
- return alchemy.printLog(val, args, {level: 2});
1796
- };
1797
- }
1798
-
1799
- log.warn = log.warning;
1800
-
1801
- /**
1802
- * Define the todo log function
1803
- *
1804
- * @author Jelle De Loecker <jelle@develry.be>
1805
- * @since 0.2.0
1806
- * @version 0.4.0
1807
- *
1808
- * @type {Function}
1809
- */
1810
- log.todo = function todo(...args) {
1811
-
1812
- var options = {
1813
- gutter: alchemy.Janeway.esc(91) + '\u2620 Todo:' + alchemy.Janeway.esc(39),
1814
- level: 2
1815
- };
1816
-
1817
- return alchemy.printLog(alchemy.TODO, args, options);
1818
- };
1819
-
1820
- const anyBody = alchemy.use('body/any'),
1821
- formBody = alchemy.use('body/form'),
1822
- formidable = alchemy.use('formidable'),
1823
- 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
+ * Look for a module by traversing the filesystem
756
+ *
757
+ * @author Jelle De Loecker <jelle@develry.be>
758
+ * @since 0.0.1
759
+ * @version 0.4.0
760
+ *
761
+ * @param {String} startPath The path to originate the search from
762
+ * @param {String} moduleName
763
+ * @param {Number} recurse
764
+ *
765
+ * @return {String}
766
+ */
767
+ Alchemy.setMethod(function searchModule(startPath, moduleName, recurse) {
768
+
769
+ var moduledirs,
770
+ module_path,
771
+ entries,
772
+ nmPath,
773
+ temp,
774
+ path,
775
+ key,
776
+ i;
777
+
778
+ // Don't do this search if it hasn't been enabled
779
+ // The new npm flat structure makes this an expensive thing to do
780
+ if (!this.settings.search_for_modules) {
781
+
782
+ // Set recurse to 3, so this is the first and last call
783
+ recurse = 3;
784
+
785
+ // Only add 2 folder to look through,
786
+ // the alchemymvc node_modules folder
787
+ // and the base node_modules folder
788
+ moduledirs = ['..', libpath.resolve(startPath, 'node_modules', 'alchemymvc')];
789
+
790
+ // Add plugin folders
791
+ if (!plugModules) {
792
+ path = libpath.resolve(PATH_ROOT, 'node_modules');
793
+
794
+ if (fs.existsSync(path)) {
795
+
796
+ // Get all the entries in the main modules folder
797
+ entries = fs.readdirSync(libpath.resolve(PATH_ROOT, 'node_modules'));
798
+
799
+ // Initiate the plugin modules variables
800
+ plugModules = [];
801
+
802
+
803
+ for (i = 0; i < entries.length; i++) {
804
+ temp = entries[i];
805
+
806
+ if (temp.startsWith('alchemy-')) {
807
+ plugModules.push(libpath.resolve(PATH_ROOT, 'node_modules', temp));
808
+ }
809
+ }
810
+ } else {
811
+ plugModules = [];
812
+ }
813
+ }
814
+
815
+ for (i = 0; i < plugModules.length; i++) {
816
+ moduledirs.push(plugModules[i]);
817
+ }
818
+
819
+ } else if (!searchModule.have_warned) {
820
+ searchModule.have_warned = true;
821
+ log.warn('The "search_for_modules" config has been enabled!');
822
+ }
823
+
824
+ if (!recurse) {
825
+ recurse = 1;
826
+ }
827
+
828
+ nmPath = libpath.resolve(startPath, 'node_modules');
829
+
830
+ if (!moduledirs) {
831
+ // Get all the entries inside the given path
832
+ try {
833
+ moduledirs = fs.readdirSync(nmPath);
834
+ } catch(err) {
835
+ return;
836
+ }
837
+ }
838
+
839
+ // Look in the base node_modules directory first
840
+ if (recurse == 1) {
841
+ moduledirs.unshift('..');
842
+ }
843
+
844
+ // Go over every directory in the main node_modules folder
845
+ for (i = 0; i < moduledirs.length; i++) {
846
+
847
+ key = moduledirs[i];
848
+
849
+ try {
850
+ // Let require find the specific file to get
851
+ module_path = require.resolve(libpath.resolve(nmPath, key, 'node_modules', moduleName));
852
+
853
+ // If no errors have popped up now, we can break the for loop
854
+ break;
855
+
856
+ } catch(e) {
857
+ // Do nothing
858
+ }
859
+ }
860
+
861
+ if (!module_path && recurse < 3) {
862
+ for (i = 0; i < moduledirs.length; i++) {
863
+
864
+ module_path = this.searchModule(libpath.resolve(nmPath, moduledirs[i]), moduleName, recurse+1);
865
+
866
+ if (module_path) {
867
+ break;
868
+ }
869
+ }
870
+ }
871
+
872
+ return module_path;
873
+ });
874
+
875
+ /**
876
+ * Find a module in our customized file structure
877
+ *
878
+ * @author Jelle De Loecker <jelle@develry.be>
879
+ * @since 0.0.1
880
+ * @version 1.2.2
881
+ *
882
+ * @param {String} moduleName
883
+ * @param {Object} options
884
+ *
885
+ * @return {Object}
886
+ */
887
+ Alchemy.setMethod(function findModule(moduleName, options) {
888
+
889
+ var package_json,
890
+ module_path,
891
+ internal,
892
+ module,
893
+ result,
894
+ time,
895
+ key,
896
+ i;
897
+
898
+ if (!options) {
899
+ options = {};
900
+ }
901
+
902
+ if (options.require == null) {
903
+ options.require = true;
904
+ }
905
+
906
+ // If we've required this once before, return it
907
+ if (result = usePaths[moduleName]) {
908
+ if (result.err) {
909
+ throw result.err;
910
+ }
911
+
912
+ // Only return the cached module if it was required then,
913
+ // or if require is now false
914
+ if (result.module || !options.require) {
915
+ return result;
916
+ }
917
+ }
918
+
919
+ result = {
920
+ err : null,
921
+ module : null,
922
+ module_dir : null,
923
+ module_path : null,
924
+ package : null,
925
+ internal : null,
926
+ search_time : null,
927
+ };
928
+
929
+ time = Date.now();
930
+
931
+ // Simply try to resolve the module by name
932
+ try {
933
+ module_path = require.resolve(moduleName);
934
+ } catch (err) {
935
+ result.err = err;
936
+ }
937
+
938
+ // If that path wasn't found, look through the root node_modules
939
+ if (result.err) {
940
+ try {
941
+ module_path = this.searchModule(PATH_ROOT, moduleName);
942
+ } catch (err) {
943
+ console.log(err);
944
+ return;
945
+ }
946
+ }
947
+
948
+ // If the module_path was found, actually require the module
949
+ if (module_path) {
950
+
951
+ // Get the package.json file
952
+ if (~module_path.indexOf(libpath.sep)) {
953
+ internal = false;
954
+
955
+ let last_piece = moduleName.split(libpath.sep).last(),
956
+ package_path;
957
+
958
+ let module_dir = libpath.dirname(module_path);
959
+ result.module_dir = module_dir;
960
+
961
+ // If the path doesn't end with the module name, look for it
962
+ if (!module_path.endsWith(last_piece)) {
963
+ package_path = module_path.beforeLast(last_piece) + last_piece;
964
+ package_path = libpath.resolve(package_path, 'package.json');
965
+ } else {
966
+ package_path = libpath.resolve(module_dir, 'package.json');
967
+ }
968
+
969
+ try {
970
+ package_json = require(package_path);
971
+ } catch (err) {
972
+ package_json = false;
973
+ }
974
+ } else {
975
+ internal = true;
976
+ package_json = {
977
+ version: process.versions.node
978
+ };
979
+ }
980
+
981
+ // Modules are required by default
982
+ if (options.require) {
983
+ if (package_json && package_json.type == 'module' && !(package_json.main && package_json.module)) {
984
+ module = doImport(module_path)
985
+ } else {
986
+ module = require(module_path);
987
+ }
988
+ }
989
+ }
990
+
991
+ if (!options.require || module) {
992
+ result.err = null;
993
+ }
994
+
995
+ if (module) {
996
+ result.module = module;
997
+ }
998
+
999
+ result.module_path = module_path;
1000
+ result.package = package_json;
1001
+ result.internal = internal;
1002
+
1003
+ // Save the result
1004
+ usePaths[moduleName] = result;
1005
+
1006
+ // If there was an error, throw it now
1007
+ if (result.err) {
1008
+ throw result.err;
1009
+ }
1010
+
1011
+ result.search_time = Date.now() - time;
1012
+
1013
+ // Else return the result
1014
+ return result;
1015
+ });
1016
+
1017
+ /**
1018
+ * Create a shared object
1019
+ *
1020
+ * @author Jelle De Loecker <jelle@develry.be>
1021
+ * @since 0.0.1
1022
+ * @version 0.4.0
1023
+ *
1024
+ * @param {String} name The name of the object to get
1025
+ * @param {String} type The type to create (array or object)
1026
+ *
1027
+ * @return {Object|Array}
1028
+ */
1029
+ Alchemy.setMethod(function shared(name, type, value) {
1030
+
1031
+ if (typeof type !== 'string') {
1032
+ value = type;
1033
+ type = 'object';
1034
+ }
1035
+
1036
+ // Create it if it doesn't exist
1037
+ if (!shared_objects[name]) {
1038
+ if (type === 'array' || type === 'Array') {
1039
+ shared_objects[name] = value || [];
1040
+ } else {
1041
+ shared_objects[name] = value || {};
1042
+ }
1043
+ }
1044
+
1045
+ return shared_objects[name];
1046
+ });
1047
+
1048
+ /**
1049
+ * Get an object id,
1050
+ * return undefined if no valid data was given (instead of throwing an error)
1051
+ *
1052
+ * @author Jelle De Loecker <jelle@develry.be>
1053
+ * @since 0.0.1
1054
+ * @version 0.4.0
1055
+ *
1056
+ * @param {String|ObjectID} obj
1057
+ *
1058
+ * @return {ObjectID|undefined}
1059
+ */
1060
+ Alchemy.setMethod(function castObjectId(obj) {
1061
+
1062
+ var type = typeof obj;
1063
+
1064
+ if (obj && type === 'object' && obj.constructor && obj.constructor.name === 'ObjectID') {
1065
+ return obj;
1066
+ } else if (type === 'string' && obj.isObjectId()) {
1067
+ return alchemy.ObjectId(obj);
1068
+ }
1069
+
1070
+ return undefined;
1071
+ });
1072
+
1073
+ /**
1074
+ * See if the given object is a stream
1075
+ *
1076
+ * @author Jelle De Loecker <jelle@develry.be>
1077
+ * @since 0.2.0
1078
+ * @version 1.0.5
1079
+ *
1080
+ * @return {Boolean}
1081
+ */
1082
+ Alchemy.setMethod(function isStream(obj) {
1083
+ return obj && (typeof obj._read == 'function' || typeof obj._write == 'function') && typeof obj.on === 'function';
1084
+ });
1085
+
1086
+ /**
1087
+ * Get or create a new cache instance
1088
+ *
1089
+ * @author Jelle De Loecker <jelle@develry.be>
1090
+ * @since 1.0.0
1091
+ * @version 1.0.4
1092
+ *
1093
+ * @param {String} name
1094
+ * @param {Number|Object} options
1095
+ *
1096
+ * @return {Develry.Cache}
1097
+ */
1098
+ Alchemy.setMethod(function getCache(name, options) {
1099
+
1100
+ var instance,
1101
+ duration,
1102
+ config,
1103
+ type;
1104
+
1105
+ if (this.caches[name]) {
1106
+ return this.caches[name];
1107
+ }
1108
+
1109
+ if (!options) {
1110
+ options = {};
1111
+ }
1112
+
1113
+ type = typeof options;
1114
+
1115
+ if (type == 'number' || type == 'string') {
1116
+ options = {
1117
+ max_age : options,
1118
+ };
1119
+ }
1120
+
1121
+ config = Object.assign({
1122
+ max_length : 5000,
1123
+ }, options);
1124
+
1125
+ // @TODO: Fixed in 0.6.1
1126
+ instance = new Blast.Classes.Develry.Cache();
1127
+ Object.assign(instance, config);
1128
+
1129
+ this.caches[name] = instance;
1130
+
1131
+ return instance;
1132
+ });
1133
+
1134
+ /**
1135
+ * Get a route
1136
+ *
1137
+ * @author Jelle De Loecker <jelle@elevenways.be>
1138
+ * @since 1.1.3
1139
+ * @version 1.1.3
1140
+ *
1141
+ * @param {String} href The href or route name
1142
+ * @param {Object} parameters Route parameters
1143
+ *
1144
+ * @return {String}
1145
+ */
1146
+ Alchemy.setMethod(function routeUrl(href, parameters) {
1147
+
1148
+ let temp = Router.getUrl(href, parameters);
1149
+
1150
+ if (temp && temp.href) {
1151
+ temp = String(temp);
1152
+
1153
+ if (temp) {
1154
+ return temp;
1155
+ }
1156
+ }
1157
+
1158
+ return href;
1159
+ });
1160
+
1161
+ /**
1162
+ * Get paths that should be cached by the client
1163
+ *
1164
+ * @author Jelle De Loecker <jelle@develry.be>
1165
+ * @since 1.0.7
1166
+ * @version 1.0.7
1167
+ *
1168
+ * @return {Pledge}
1169
+ */
1170
+ Alchemy.decorateMethod(Blast.Decorators.memoize(), function getAppcachePaths() {
1171
+
1172
+ var paths = [];
1173
+
1174
+ return Function.parallel(function getHawkejsTemplates(next) {
1175
+
1176
+ var templates = [],
1177
+ directories = alchemy.hawkejs.directories.getSorted(),
1178
+ tasks = [],
1179
+ i;
1180
+
1181
+ function checkDirectory(dir_path, mount_path, next) {
1182
+ fs.readdir(dir_path, function gotDir(err, files) {
1183
+
1184
+ if (err) {
1185
+
1186
+ if (err.code == 'ENOENT') {
1187
+ return next();
1188
+ }
1189
+
1190
+ return next(err);
1191
+ }
1192
+
1193
+ let tasks = [],
1194
+ i;
1195
+
1196
+ for (i = 0; i < files.length; i++) {
1197
+ let file = files[i],
1198
+ full_path = libpath.resolve(dir_path, file),
1199
+ full_mount_path = mount_path + '/' + file;
1200
+
1201
+ tasks.push(function checkPath(next) {
1202
+ fs.stat(full_path, function gotStat(err, stat) {
1203
+
1204
+ if (err) {
1205
+
1206
+ if (err.code == 'ENOENT') {
1207
+ return next();
1208
+ }
1209
+
1210
+ return next(err);
1211
+ }
1212
+
1213
+ if (stat.isDirectory()) {
1214
+ return checkDirectory(full_path, full_mount_path, next);
1215
+ }
1216
+
1217
+ if (stat.isFile()) {
1218
+ if (file.endsWith('.ejs') || file.endsWith('.hwk')) {
1219
+
1220
+ if (full_mount_path[0] == '/') {
1221
+ full_mount_path = full_mount_path.slice(1);
1222
+ }
1223
+
1224
+ templates.push(full_mount_path);
1225
+ }
1226
+ }
1227
+
1228
+ next();
1229
+ });
1230
+ });
1231
+ }
1232
+
1233
+ Function.parallel(tasks, next);
1234
+ });
1235
+ }
1236
+
1237
+ for (i = 0; i < directories.length; i++) {
1238
+ let directory = directories[i];
1239
+
1240
+ tasks.push(function readDir(next) {
1241
+ checkDirectory(directory, '', next);
1242
+ });
1243
+ }
1244
+
1245
+ return Function.parallel(tasks, function done(err) {
1246
+
1247
+ if (err) {
1248
+ return next(err);
1249
+ }
1250
+
1251
+ let path,
1252
+ url,
1253
+ i;
1254
+
1255
+ for (i = 0; i < templates.length; i++) {
1256
+ path = templates[i];
1257
+ url = '/hawkejs/templates?name[0]=' + encodeURIComponent(path.beforeLast('.ejs')) + '&v=' + alchemy.package.version;
1258
+ paths.push(url);
1259
+ }
1260
+
1261
+ next();
1262
+ });
1263
+ }, function done(err) {
1264
+
1265
+ if (err) {
1266
+ return;
1267
+ }
1268
+
1269
+ return paths;
1270
+ });
1271
+ });
1272
+
1273
+ /**
1274
+ * Get the appcache manifest text
1275
+ *
1276
+ * @author Jelle De Loecker <jelle@develry.be>
1277
+ * @since 1.0.7
1278
+ * @version 1.0.7
1279
+ *
1280
+ * @return {Pledge}
1281
+ */
1282
+ Alchemy.decorateMethod(Blast.Decorators.memoize(), function getAppcacheManifest() {
1283
+
1284
+ return Function.series(function gotPaths(next) {
1285
+ alchemy.getAppcachePaths().done(next);
1286
+ }, function createText(err, result) {
1287
+
1288
+ if (err) {
1289
+ return;
1290
+ }
1291
+
1292
+ let manifest = 'CACHE MANIFEST\n\n',
1293
+ entry,
1294
+ url,
1295
+ key,
1296
+ i;
1297
+
1298
+ manifest += 'CACHE:\n';
1299
+
1300
+ // Allways add the client script
1301
+ manifest += '/hawkejs/hawkejs-client.js?v=' + alchemy.package.version + '\n';
1302
+
1303
+ if (ac_entries.cache && ac_entries.cache.length) {
1304
+ for (key in ac_entries.cache) {
1305
+ manifest += ac_entries.cache[key].url + '\n';
1306
+ }
1307
+ }
1308
+
1309
+ for (i = 0; i < result[0].length; i++) {
1310
+ url = result[0][i];
1311
+
1312
+ manifest += url + '\n';
1313
+ }
1314
+
1315
+ manifest += '\n';
1316
+ manifest += 'NETWORK:\n*\n\n';
1317
+
1318
+ // This will cause a cache update each time the server is reset
1319
+ manifest += '#' + alchemy.package.version + '-' + alchemy.discovery_id;
1320
+
1321
+ return manifest;
1322
+ });
1323
+ });
1324
+
1325
+ /**
1326
+ * Add an appcache entry
1327
+ *
1328
+ * @author Jelle De Loecker <jelle@develry.be>
1329
+ * @since 1.0.7
1330
+ * @version 1.0.7
1331
+ *
1332
+ * @param {String|Object}
1333
+ */
1334
+ Alchemy.setMethod(function addAppcacheEntry(entry) {
1335
+
1336
+ if (typeof entry == 'string') {
1337
+ entry = {
1338
+ url : entry
1339
+ };
1340
+ }
1341
+
1342
+ if (!entry.type) {
1343
+ entry.type = 'cache';
1344
+ } else {
1345
+ entry.type = entry.type.toLowerCase();
1346
+ }
1347
+
1348
+ if (!ac_entries[entry.type]) {
1349
+ ac_entries[entry.type] = [];
1350
+ }
1351
+
1352
+ ac_entries[entry.type].push(entry);
1353
+ });
1354
+
1355
+ /**
1356
+ * Get the body of an IncomingMessage
1357
+ *
1358
+ * @author Jelle De Loecker <jelle@develry.be>
1359
+ * @since 1.1.0
1360
+ * @version 1.2.6
1361
+ *
1362
+ * @param {IncomingMessage} req
1363
+ * @param {OutgoingMessage} res Optional
1364
+ * @param {Function} callback
1365
+ */
1366
+ Alchemy.setMethod(function parseRequestBody(req, res, callback) {
1367
+
1368
+ const conduit = req.conduit;
1369
+
1370
+ if (typeof res == 'function') {
1371
+ callback = res;
1372
+ res = null;
1373
+ }
1374
+
1375
+ if (req.original) {
1376
+ req = req.original;
1377
+ }
1378
+
1379
+ if (req.body != null) {
1380
+ return callback(null, req.body);
1381
+ }
1382
+
1383
+ // Multipart data is handled by "formidable"
1384
+ if (req.headers['content-type'] && req.headers['content-type'].startsWith('multipart/form-data')) {
1385
+
1386
+ let form = new formidable.IncomingForm({multiples: true});
1387
+
1388
+ // md5 hash by default
1389
+ form.hash = 'md5';
1390
+
1391
+ form.parse(req, function parsedMultipart(err, form_fields, form_files) {
1392
+
1393
+ var fields = {},
1394
+ files = {},
1395
+ key;
1396
+
1397
+ if (err && req.conduit && req.conduit.aborted) {
1398
+ return callback(null);
1399
+ }
1400
+
1401
+ // Fix the field names
1402
+ for (key in form_fields) {
1403
+ Object.setFormPath(fields, key, form_fields[key]);
1404
+ }
1405
+
1406
+ // Fix the file names
1407
+ for (key in form_files) {
1408
+ Object.setFormPath(files, key, form_files[key]);
1409
+ }
1410
+
1411
+ if (err) {
1412
+ log.error('Error parsing multipart POST', {err: err});
1413
+ req.body = {};
1414
+ req.files = {};
1415
+ } else {
1416
+ req.body = fields;
1417
+ req.files = files;
1418
+
1419
+ if (conduit) {
1420
+ conduit.setRequestBody(fields);
1421
+ conduit.setRequestFiles(files);
1422
+ }
1423
+ }
1424
+
1425
+ callback(null, fields);
1426
+ });
1427
+
1428
+ return;
1429
+ }
1430
+
1431
+ // Regular form-encoded data
1432
+ if (req.headers['content-type'] && req.headers['content-type'].indexOf('form-urlencoded') > -1) {
1433
+
1434
+ urlFormBody(req, res, function parsedBody(err) {
1435
+
1436
+ if (err && req.conduit && req.conduit.aborted) {
1437
+ return callback(null);
1438
+ }
1439
+
1440
+ // You can't send files using a regular post
1441
+ req.files = {};
1442
+
1443
+ if (err) {
1444
+ log.error('Error parsing x-www-form-urlencoded body data', {err: err});
1445
+ req.body = {};
1446
+ } else {
1447
+ req.body = req.body;
1448
+
1449
+ if (conduit) {
1450
+ conduit.setRequestBody(req.body);
1451
+ }
1452
+ }
1453
+
1454
+ callback(null, req.body);
1455
+ });
1456
+
1457
+ return;
1458
+ }
1459
+
1460
+ // Any other encoded data (like JSON)
1461
+ anyBody(req, function parsedBody(err, body) {
1462
+
1463
+ if (err && req.conduit && req.conduit.aborted) {
1464
+ return callback(null);
1465
+ }
1466
+
1467
+ // You can't send files using a regular post
1468
+ req.files = {};
1469
+
1470
+ if (err) {
1471
+ log.error('Error parsing body data', {err: err});
1472
+ req.body = {};
1473
+ } else {
1474
+ req.body = body;
1475
+
1476
+ if (conduit) {
1477
+ conduit.setRequestBody(body);
1478
+ }
1479
+ }
1480
+
1481
+ callback(null, req.body);
1482
+ });
1483
+ });
1484
+
1485
+ /**
1486
+ * Export all data
1487
+ *
1488
+ * @author Jelle De Loecker <jelle@develry.be>
1489
+ * @since 1.0.5
1490
+ * @version 1.0.5
1491
+ *
1492
+ * @return {Stream}
1493
+ */
1494
+ Alchemy.setMethod(function createExportStream(options) {
1495
+
1496
+ var stream = new require('stream').PassThrough();
1497
+
1498
+ this.exportToStream(stream, options);
1499
+
1500
+ return stream;
1501
+ });
1502
+
1503
+ /**
1504
+ * Export all data to stream
1505
+ *
1506
+ * @author Jelle De Loecker <jelle@develry.be>
1507
+ * @since 1.0.5
1508
+ * @version 1.0.5
1509
+ *
1510
+ * @param {Stream} output
1511
+ * @param {Object} options
1512
+ *
1513
+ * @return {Pledge}
1514
+ */
1515
+ Alchemy.setMethod(function exportToStream(output, options) {
1516
+
1517
+ if (!alchemy.isStream(output)) {
1518
+ if (!options) {
1519
+ options = output;
1520
+ output = null;
1521
+ }
1522
+
1523
+ output = options.output;
1524
+ }
1525
+
1526
+ if (!output) {
1527
+ return Pledge.reject(new Error('No target output stream has been given'));
1528
+ }
1529
+
1530
+ if (!options) {
1531
+ options = {};
1532
+ }
1533
+
1534
+ let tasks = [],
1535
+ i;
1536
+
1537
+ for (i = 0; i < Model.children.length; i++) {
1538
+ let model = Model.children[i];
1539
+
1540
+ tasks.push(async function exportModel(next) {
1541
+ await (new model).exportToStream(output);
1542
+ next();
1543
+ });
1544
+ }
1545
+
1546
+ return Function.series(tasks, function done(err) {
1547
+
1548
+ if (err) {
1549
+ return output.emit('error', err);
1550
+ }
1551
+
1552
+ output.end();
1553
+ });
1554
+ });
1555
+
1556
+ /**
1557
+ * Import from a stream
1558
+ *
1559
+ * @author Jelle De Loecker <jelle@develry.be>
1560
+ * @since 1.0.5
1561
+ * @version 1.0.5
1562
+ *
1563
+ * @param {Stream} input
1564
+ * @param {Object} options
1565
+ *
1566
+ * @return {Pledge}
1567
+ */
1568
+ Alchemy.setMethod(function importFromStream(input, options) {
1569
+
1570
+ if (!alchemy.isStream(input)) {
1571
+ if (!options) {
1572
+ options = input;
1573
+ input = null;
1574
+ }
1575
+
1576
+ input = options.input;
1577
+ }
1578
+
1579
+ if (!input) {
1580
+ return Pledge.reject(new Error('No source input stream has been given'));
1581
+ }
1582
+
1583
+ if (!options) {
1584
+ options = {};
1585
+ }
1586
+
1587
+ let that = this,
1588
+ current_type = null,
1589
+ extra_stream,
1590
+ pledge = new Pledge(),
1591
+ stopped,
1592
+ paused,
1593
+ buffer,
1594
+ model,
1595
+ value,
1596
+ seen = 0,
1597
+ left,
1598
+ size,
1599
+ doc;
1600
+
1601
+ input.on('data', function onData(data) {
1602
+
1603
+ if (stopped) {
1604
+ return;
1605
+ }
1606
+
1607
+ if (buffer) {
1608
+ buffer = Buffer.concat([buffer, data]);
1609
+ } else {
1610
+ buffer = data;
1611
+ }
1612
+
1613
+ handleBuffer();
1614
+ });
1615
+
1616
+ function handleBuffer() {
1617
+
1618
+ if (paused) {
1619
+ return;
1620
+ }
1621
+
1622
+ if (!current_type && buffer.length < 2) {
1623
+ return;
1624
+ }
1625
+
1626
+ if (!current_type) {
1627
+ current_type = buffer.readUInt8(0);
1628
+
1629
+ if (current_type == 0x01) {
1630
+ size = buffer.readUInt8(1);
1631
+ buffer = buffer.slice(2);
1632
+ } else if (current_type == 0x02 && buffer.length >= 5) {
1633
+ size = buffer.readUInt32BE(1);
1634
+ buffer = buffer.slice(5);
1635
+ } else if (current_type == 0xFF) {
1636
+ size = buffer.readUInt32BE(1);
1637
+ buffer = buffer.slice(5);
1638
+ seen = 0;
1639
+
1640
+ if (!doc) {
1641
+ stopped = true;
1642
+ pledge.reject(new Error('Found extra import data, but no active document'));
1643
+ } else {
1644
+ extra_stream = new require('stream').PassThrough();
1645
+ doc.extraImportFromStream(extra_stream);
1646
+ }
1647
+ } else {
1648
+ // Not enough data? Wait
1649
+ current_type = null;
1650
+ return;
1651
+ }
1652
+ }
1653
+
1654
+ handleRest();
1655
+ }
1656
+
1657
+ function handleRest() {
1658
+
1659
+ if (current_type == 0xFF) {
1660
+ left = size - seen;
1661
+ value = buffer.slice(0, left);
1662
+
1663
+ seen += value.length;
1664
+
1665
+ if (value.length == buffer.length) {
1666
+ buffer = null;
1667
+ } else if (value.length < buffer.length) {
1668
+ buffer = buffer.slice(left);
1669
+ }
1670
+
1671
+ extra_stream.write(value);
1672
+
1673
+ if (value.length == left) {
1674
+ extra_stream.end();
1675
+ current_type = null;
1676
+
1677
+ if (buffer) {
1678
+ handleBuffer();
1679
+ }
1680
+ }
1681
+
1682
+ return;
1683
+ }
1684
+
1685
+ if (buffer.length >= size) {
1686
+ value = buffer.slice(0, size);
1687
+ buffer = buffer.slice(size);
1688
+ } else {
1689
+ // Wait for next call
1690
+ return;
1691
+ }
1692
+
1693
+ if (current_type == 0x01) {
1694
+ value = value.toString();
1695
+
1696
+ if (!model || model.model_name != value) {
1697
+ model = Model.get(value);
1698
+ doc = null;
1699
+ }
1700
+
1701
+ if (!model) {
1702
+ stopped = true;
1703
+ return pledge.reject(new Error('Could not find Model "' + value + '"'));
1704
+ }
1705
+
1706
+ current_type = null;
1707
+ size = 0;
1708
+ } else if (current_type == 0x02) {
1709
+ doc = model.createDocument();
1710
+ input.pause();
1711
+ paused = true;
1712
+
1713
+ doc.importFromBuffer(value).done(function done(err, result) {
1714
+
1715
+ if (err) {
1716
+ stopped = true;
1717
+ return pledge.reject(err);
1718
+ }
1719
+
1720
+ current_type = null;
1721
+ paused = false;
1722
+ input.resume();
1723
+
1724
+ handleBuffer();
1725
+ });
1726
+
1727
+ return;
1728
+ }
1729
+
1730
+ if (buffer && buffer.length) {
1731
+ handleBuffer();
1732
+ }
1733
+ }
1734
+
1735
+ return pledge;
1736
+ });
1737
+
1738
+ /**
1739
+ * Create a new schema
1740
+ *
1741
+ * @author Jelle De Loecker <jelle@elevenways.be>
1742
+ * @since 1.2.1
1743
+ * @version 1.2.1
1744
+ *
1745
+ * @param {*} parent
1746
+ */
1747
+ Alchemy.setMethod(function createSchema(parent) {
1748
+ let schema = new Classes.Alchemy.Schema(parent);
1749
+ return schema;
1750
+ });
1751
+
1752
+ /**
1753
+ * Get a client-side model
1754
+ *
1755
+ * @author Jelle De Loecker <jelle@elevenways.be>
1756
+ * @since 1.2.4
1757
+ * @version 1.2.4
1758
+ *
1759
+ * @param {String} name
1760
+ * @param {Object} options
1761
+ *
1762
+ * @return {Model}
1763
+ */
1764
+ Alchemy.setMethod(function getClientModel(name, init, options) {
1765
+ return Classes.Alchemy.Client.Base.prototype.getModel.call(this, name, init, options);
1766
+ });
1767
+
1768
+ /**
1769
+ * The alchemy global, where everything will be stored
1770
+ *
1771
+ * @author Jelle De Loecker <jelle@develry.be>
1772
+ * @since 0.0.1
1773
+ * @version 0.4.0
1774
+ *
1775
+ * @type {Alchemy}
1776
+ */
1777
+ DEFINE('alchemy', new Alchemy());
1778
+
1779
+ /**
1780
+ * Define the log function
1781
+ *
1782
+ * @author Jelle De Loecker <jelle@develry.be>
1783
+ * @since 0.0.1
1784
+ * @version 0.4.0
1785
+ *
1786
+ * @type {Function}
1787
+ */
1788
+ DEFINE('log', alchemy.log);
1789
+
1790
+ for (let key in alchemy.Janeway.LEVELS) {
1791
+ let name = key.toLowerCase();
1792
+ let val = alchemy.Janeway.LEVELS[key];
1793
+
1794
+ log[name] = function(...args) {
1795
+ return alchemy.printLog(val, args, {level: 2});
1796
+ };
1797
+ }
1798
+
1799
+ log.warn = log.warning;
1800
+
1801
+ /**
1802
+ * Define the todo log function
1803
+ *
1804
+ * @author Jelle De Loecker <jelle@develry.be>
1805
+ * @since 0.2.0
1806
+ * @version 0.4.0
1807
+ *
1808
+ * @type {Function}
1809
+ */
1810
+ log.todo = function todo(...args) {
1811
+
1812
+ var options = {
1813
+ gutter: alchemy.Janeway.esc(91) + '\u2620 Todo:' + alchemy.Janeway.esc(39),
1814
+ level: 2
1815
+ };
1816
+
1817
+ return alchemy.printLog(alchemy.TODO, args, options);
1818
+ };
1819
+
1820
+ const anyBody = alchemy.use('body/any'),
1821
+ formBody = alchemy.use('body/form'),
1822
+ formidable = alchemy.use('formidable'),
1823
+ bodyParser = alchemy.use('body-parser'),
1824
1824
  urlFormBody = bodyParser.urlencoded({extended: true});