alchemymvc 1.2.8 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/lib/app/behaviour/sluggable_behaviour.js +4 -2
  2. package/lib/app/conduit/http_conduit.js +7 -2
  3. package/lib/app/conduit/loopback_conduit.js +2 -2
  4. package/lib/app/conduit/socket_conduit.js +20 -5
  5. package/lib/app/controller/alchemy_info_controller.js +4 -8
  6. package/lib/app/helper/backed_map.js +2 -2
  7. package/lib/app/helper/router_helper.js +98 -24
  8. package/lib/app/helper_controller/controller.js +45 -30
  9. package/lib/app/helper_datasource/00-nosql_datasource.js +44 -10
  10. package/lib/app/helper_field/enum_field.js +4 -4
  11. package/lib/app/helper_field/schema_field.js +50 -36
  12. package/lib/app/helper_model/document.js +81 -46
  13. package/lib/app/helper_model/field_set.js +11 -0
  14. package/lib/app/helper_model/model.js +107 -53
  15. package/lib/app/helper_validator/00_validator.js +38 -6
  16. package/lib/app/helper_validator/not_empty_validator.js +1 -3
  17. package/lib/app/routes.js +7 -1
  18. package/lib/bootstrap.js +1 -0
  19. package/lib/class/conduit.js +438 -290
  20. package/lib/class/controller.js +18 -15
  21. package/lib/class/datasource.js +19 -8
  22. package/lib/class/document.js +3 -3
  23. package/lib/class/field.js +34 -3
  24. package/lib/class/inode.js +27 -0
  25. package/lib/class/inode_file.js +204 -4
  26. package/lib/class/migration.js +2 -1
  27. package/lib/class/model.js +16 -5
  28. package/lib/class/path_definition.js +76 -120
  29. package/lib/class/path_param_definition.js +202 -0
  30. package/lib/class/postponement.js +573 -0
  31. package/lib/class/route.js +193 -33
  32. package/lib/class/router.js +22 -4
  33. package/lib/class/schema.js +47 -11
  34. package/lib/class/schema_client.js +65 -35
  35. package/lib/class/session.js +138 -12
  36. package/lib/class/sitemap.js +341 -0
  37. package/lib/core/base.js +13 -3
  38. package/lib/core/client_alchemy.js +78 -7
  39. package/lib/core/client_base.js +16 -10
  40. package/lib/core/middleware.js +56 -45
  41. package/lib/init/alchemy.js +124 -11
  42. package/lib/init/constants.js +11 -0
  43. package/lib/init/functions.js +163 -86
  44. package/lib/stages.js +18 -3
  45. package/package.json +6 -6
@@ -11,8 +11,9 @@ var mkdirp = alchemy.use('mkdirp'),
11
11
  timers = {},
12
12
  lpQueue = 0,
13
13
  moduledirs,
14
- spawnQueue,
15
- Magic;
14
+ spawnQueue;
15
+
16
+ const HAS_EXPOSED = Symbol('has_exposed');
16
17
 
17
18
  // Create a queue for functions opening files
18
19
  spawnQueue = Function.createQueue();
@@ -519,25 +520,22 @@ alchemy.ObjectId = mongo.ObjectID;
519
520
 
520
521
  /**
521
522
  * Get mimetype info of a file
523
+ *
524
+ * @deprecated Use Classes.Alchemy.Inode.File.getMimetype(path)
522
525
  *
523
526
  * @author Jelle De Loecker <jelle@develry.be>
524
527
  * @since 0.2.0
525
- * @version 0.4.0
528
+ * @version 1.3.0
526
529
  *
527
- * @param {String} filePath A path to the file
530
+ * @param {String} filepath A path to the file
528
531
  * @param {Function} callback
532
+ *
533
+ * @return {Pledge<String>}
529
534
  */
530
- Alchemy.setMethod(function getMimetype(filePath, callback) {
531
-
532
- var magic,
533
- mmm = alchemy.use('mmmagic');
534
-
535
- if (!mmm) {
536
- return callback(new Error('The mmmagic module is not available'));
537
- }
538
-
539
- magic = new mmm.Magic(mmm.MAGIC_MIME_TYPE);
540
- magic.detectFile(filePath, callback);
535
+ Alchemy.setMethod(function getMimetype(filepath, callback) {
536
+ const pledge = Classes.Alchemy.Inode.File.getMimetype(filepath);
537
+ pledge.done(callback);
538
+ return pledge;
541
539
  });
542
540
 
543
541
  /**
@@ -687,9 +685,9 @@ Alchemy.setMethod(function getMedhash(file_path, options) {
687
685
  /**
688
686
  * Hash the given file_path using the given hash
689
687
  *
690
- * @author Jelle De Loecker <jelle@develry.be>
688
+ * @author Jelle De Loecker <jelle@elevenways.be>
691
689
  * @since 1.0.7
692
- * @version 1.0.7
690
+ * @version 1.3.0
693
691
  *
694
692
  * @param {String} file_path
695
693
  * @param {Object} options
@@ -698,7 +696,7 @@ Alchemy.setMethod(function hashFile(file_path, options) {
698
696
 
699
697
  var stream_options = {},
700
698
  digest_type,
701
- type;
699
+ hash_algorithm;
702
700
 
703
701
  if (!options) {
704
702
  options = {};
@@ -710,10 +708,10 @@ Alchemy.setMethod(function hashFile(file_path, options) {
710
708
  };
711
709
  }
712
710
 
713
- type = options.type || 'sha1';
711
+ hash_algorithm = options.algorithm || options.type || alchemy.settings.file_hash_algorithm || 'sha1';
714
712
  digest_type = options.digest_type || 'hex';
715
713
 
716
- let checksum = crypto.createHash(type),
714
+ let checksum = crypto.createHash(hash_algorithm),
717
715
  pledge = new Pledge(),
718
716
  stream;
719
717
 
@@ -781,7 +779,7 @@ Alchemy.setMethod(function statPath(path, options) {
781
779
  *
782
780
  * @author Jelle De Loecker <jelle@develry.be>
783
781
  * @since 0.0.1
784
- * @version 1.0.7
782
+ * @version 1.3.0
785
783
  *
786
784
  * @param {String} filePath A path to the file
787
785
  * @param {Object} options
@@ -802,7 +800,7 @@ Alchemy.setMethod(function getFileInfo(filePath, options, callback) {
802
800
 
803
801
  // Use sha1 as hashing default
804
802
  if (typeof options.hash == 'undefined' || options.hash === true) {
805
- options.hash = 'sha1';
803
+ options.hash = alchemy.settings.file_hash_algorithm || 'sha1';
806
804
  }
807
805
 
808
806
  // Get basic file information
@@ -1011,42 +1009,45 @@ Alchemy.setMethod(function request(url, options, callback) {
1011
1009
  });
1012
1010
 
1013
1011
  /**
1014
- * Download a url to a temporary location
1012
+ * Download a file
1015
1013
  *
1016
- * @author Jelle De Loecker <jelle@develry.be>
1017
- * @since 0.1.0
1018
- * @version 1.2.6
1014
+ * @author Jelle De Loecker <jelle@elevenways.be>
1015
+ * @since 1.3.0
1016
+ * @version 1.3.0
1019
1017
  *
1020
- * @param {String} url The url
1021
- * @param {Object} options
1022
- * @param {Function} callback Callback
1018
+ * @param {String} url
1019
+ *
1020
+ * @return {Pledge<File>}
1023
1021
  */
1024
- Alchemy.setMethod(function downloadFile(url, options, callback) {
1025
-
1026
- var filepath,
1027
- options,
1028
- name,
1029
- file,
1030
- res;
1022
+ Alchemy.setMethod(function download(url, options) {
1031
1023
 
1032
- if (typeof options === 'function') {
1033
- callback = options;
1034
- options = {};
1035
- } else if (!options && typeof options !== 'object') {
1036
- options = {};
1037
- }
1038
-
1039
- if (typeof callback !== 'function') {
1040
- callback = Function.thrower;
1041
- }
1024
+ const pledge = new Pledge();
1042
1025
 
1043
1026
  // Get the file
1044
- Blast.fetch({url, get_stream: true}, function gotStream(err, res, output) {
1027
+ Blast.fetch({url, get_stream: true}, async function gotStream(err, res, output) {
1045
1028
 
1046
1029
  if (err) {
1047
- return callback(err);
1030
+ return pledge.reject(err);
1048
1031
  }
1049
1032
 
1033
+ if (res.statusCode == 404) {
1034
+ err = new Error('Path "' + url + '" does not exist');
1035
+ err.number = 404;
1036
+ return pledge.reject(err);
1037
+ }
1038
+
1039
+ if (options?.type && res.headers['content-type']) {
1040
+ if (res.headers['content-type'].indexOf(options.type) < 0) {
1041
+
1042
+ err = new Error('Received unexpected filetype');
1043
+ err.number = 500;
1044
+
1045
+ return pledge.reject(err);
1046
+ }
1047
+ }
1048
+
1049
+ let name;
1050
+
1050
1051
  if (res.headers['content-disposition']) {
1051
1052
  let disposition = res.headers['content-disposition'],
1052
1053
  pieces = disposition.split(';'),
@@ -1075,44 +1076,60 @@ Alchemy.setMethod(function downloadFile(url, options, callback) {
1075
1076
  }
1076
1077
  }
1077
1078
 
1078
- // Construct a temporary file path
1079
- filepath = '/tmp/alchemy_' + Crypto.pseudoHex(8) + '_' + name;
1080
-
1081
- // Open the temp file
1082
- file = fs.createWriteStream(filepath);
1083
-
1084
- if (res.statusCode == 404) {
1085
- err = new Error('Path "' + url + '" does not exist');
1086
- err.number = 404;
1087
-
1088
- return callback(err);
1079
+ if (name.indexOf('/') > -1) {
1080
+ name = name.replaceAll('/', '-');
1089
1081
  }
1090
1082
 
1091
- if (options.type && res.headers['content-type']) {
1092
- if (res.headers['content-type'].indexOf(options.type) < 0) {
1093
-
1094
- err = new Error('Received unexpected filetype');
1095
- err.number = 500;
1096
-
1097
- return callback(err);
1098
- }
1099
- }
1083
+ let temp_dir = await Blast.createTempDir({prefix: 'aldl'}),
1084
+ full_path = libpath.resolve(temp_dir, name);
1085
+
1086
+ let write_stream = fs.createWriteStream(full_path);
1100
1087
 
1101
1088
  if (output) {
1102
1089
  // Pipe the response stream into the file
1103
- output.pipe(file);
1090
+ output.pipe(write_stream);
1104
1091
  } else {
1105
- file.end('');
1092
+ write_stream.end('');
1106
1093
  }
1107
1094
 
1108
- // @todo: maybe implement content-type for downloaded file, too?
1109
- // Because webservers can lie about filetypes
1095
+ // Wait for it to finish writing to the temp file
1096
+ write_stream.on('finish', async function writeFinished() {
1110
1097
 
1111
- // Close the file when finished and callback
1112
- file.on('finish', function writeFinished() {
1113
- callback(null, filepath, name);
1098
+ try {
1099
+ let file = await Classes.Alchemy.Inode.Inode.from(full_path);
1100
+ pledge.resolve(file);
1101
+ } catch (err) {
1102
+ pledge.reject(err);
1103
+ }
1114
1104
  });
1115
1105
  });
1106
+
1107
+ return pledge;
1108
+ });
1109
+
1110
+ /**
1111
+ * Download a url to a temporary location
1112
+ *
1113
+ * @author Jelle De Loecker <jelle@elevenways.be>
1114
+ * @since 0.1.0
1115
+ * @version 1.3.0
1116
+ *
1117
+ * @deprecated Use `download` instead
1118
+ *
1119
+ * @param {String} url The url
1120
+ * @param {Object} options
1121
+ * @param {Function} callback Callback
1122
+ */
1123
+ Alchemy.setMethod(function downloadFile(url, options, callback) {
1124
+
1125
+ if (typeof options == 'function') {
1126
+ callback = options;
1127
+ options = null;
1128
+ }
1129
+
1130
+ let pledge = this.download(url, options);
1131
+ pledge.done(callback);
1132
+ return pledge;
1116
1133
  });
1117
1134
 
1118
1135
  /**
@@ -1327,32 +1344,48 @@ Alchemy.setMethod(function exposeStatic(name, value) {
1327
1344
  /**
1328
1345
  * Expose the default static variables
1329
1346
  *
1330
- * @author Jelle De Loecker <jelle@develry.be>
1347
+ * @author Jelle De Loecker <jelle@elevenways.be>
1331
1348
  * @since 1.1.0
1332
- * @version 1.2.1
1349
+ * @version 1.3.1
1333
1350
  */
1334
1351
  Alchemy.setMethod(function exposeDefaultStaticVariables() {
1335
1352
 
1336
- let model_info = {},
1353
+ let model_info = [],
1354
+ model_code = '',
1337
1355
  hawkejs = alchemy.hawkejs,
1338
1356
  models = Model.getAllChildren(),
1357
+ info,
1339
1358
  i;
1340
1359
 
1341
1360
  for (i = 0; i < models.length; i++) {
1342
- model_info[models[i].model_name] = models[i].getClientConfig();
1361
+ info = models[i].getClientConfig();
1362
+ model_info.push(info);
1363
+ }
1364
+
1365
+ // Sort the models by their ancestor count
1366
+ model_info.sortByPath(1, 'ancestors');
1367
+
1368
+ model_code = `function inheritModel(parent, child) {
1369
+ return Classes.Hawkejs.Model.getClass(child, true, parent);
1370
+ }\n`;
1371
+
1372
+ for (let info of model_info) {
1373
+ model_code += 'inheritModel(' + JSON.stringify(info.parent) + ', ' + JSON.stringify(info.name) + ')\n';
1343
1374
  }
1344
1375
 
1345
1376
  // Expose the model configuration
1346
1377
  hawkejs.exposeStatic('model_info', model_info);
1347
1378
 
1348
- // Expose router options
1349
- hawkejs.exposeStatic('router_options', Router.getOptions());
1350
-
1351
- // Expose basic HTTP routes
1352
- hawkejs.exposeStatic('routes', Router.getRoutes());
1379
+ Blast.require('client_models', {
1380
+ after : 'helper_model/model',
1381
+ resolver : async function getExportedFunction() {
1382
+ return model_code;
1383
+ },
1384
+ client : true,
1385
+ server : false,
1386
+ });
1353
1387
 
1354
- // Expose socket routes
1355
- hawkejs.exposeStatic('socket_routes', Router.getSocketRoutes());
1388
+ this.exposeRouteData();
1356
1389
 
1357
1390
  // Expose breadcrumb info
1358
1391
  hawkejs.exposeStatic('breadcrumb_info', Router.getBreadcrumbInfo());
@@ -1367,6 +1400,9 @@ Alchemy.setMethod(function exposeDefaultStaticVariables() {
1367
1400
  hawkejs.exposeStatic('enable_websockets', true);
1368
1401
  }
1369
1402
 
1403
+ // Expose the layout settings
1404
+ hawkejs.exposeStatic('alchemy_layout', alchemy.settings.layout);
1405
+
1370
1406
  let app_version = alchemy.package.version;
1371
1407
 
1372
1408
  // The current app version
@@ -1379,6 +1415,47 @@ Alchemy.setMethod(function exposeDefaultStaticVariables() {
1379
1415
 
1380
1416
  // Emit as a global event so plugins can also expose their data
1381
1417
  this.emit('generate_static_variables', hawkejs);
1418
+
1419
+ this[HAS_EXPOSED] = true;
1420
+ });
1421
+
1422
+ /**
1423
+ * Regenerate exposed route data
1424
+ *
1425
+ * @author Jelle De Loecker <jelle@elevenways.be>
1426
+ * @since 1.3.0
1427
+ * @version 1.3.0
1428
+ */
1429
+ Alchemy.setMethod(function exposeRouteData() {
1430
+
1431
+ const hawkejs = alchemy.hawkejs;
1432
+
1433
+ // Expose router options
1434
+ hawkejs.exposeStatic('router_options', Router.getOptions());
1435
+
1436
+ // Expose basic HTTP routes
1437
+ hawkejs.exposeStatic('routes', Router.getRoutes());
1438
+
1439
+ // Expose socket routes
1440
+ hawkejs.exposeStatic('socket_routes', Router.getSocketRoutes());
1441
+ });
1442
+
1443
+
1444
+ /**
1445
+ * Flush the exposed route data
1446
+ * (Will only regenerate if default static variables haven't been generated yet)
1447
+ *
1448
+ * @author Jelle De Loecker <jelle@elevenways.be>
1449
+ * @since 1.3.0
1450
+ * @version 1.3.0
1451
+ */
1452
+ Alchemy.setMethod(function checkExposedRouteData() {
1453
+
1454
+ if (!this[HAS_EXPOSED]) {
1455
+ return;
1456
+ }
1457
+
1458
+ this.exposeRouteData();
1382
1459
  });
1383
1460
 
1384
1461
  /**
package/lib/stages.js CHANGED
@@ -13,11 +13,12 @@
13
13
  * @since 0.0.1
14
14
  * @version 1.1.0
15
15
  */
16
- var path = alchemy.modules.path,
16
+ let path = alchemy.modules.path,
17
17
  settings = alchemy.settings,
18
18
  http = alchemy.modules.http,
19
19
  hawkejs = alchemy.hawkejs,
20
- fs = alchemy.use('fs');
20
+ fs = alchemy.use('fs'),
21
+ total_http_requests = 0;
21
22
 
22
23
  if (alchemy.settings.debug) {
23
24
  alchemy.sputnik.on('launching', function onLaunch(stage) {
@@ -38,13 +39,26 @@ if (alchemy.settings.debug) {
38
39
  });
39
40
  }
40
41
 
42
+ /**
43
+ * Add a getter for the total amount of http requests
44
+ *
45
+ * @author Jelle De Loecker <jelle@elevenways.be>
46
+ * @since 1.3.1
47
+ * @version 1.3.1
48
+ *
49
+ * @type {Number}
50
+ */
51
+ Alchemy.setProperty(function http_request_counter() {
52
+ return total_http_requests;
53
+ });
54
+
41
55
  /**
42
56
  * The "http" stage:
43
57
  * Create the server and listen to requests
44
58
  *
45
59
  * @author Jelle De Loecker <jelle@develry.be>
46
60
  * @since 0.0.1
47
- * @version 1.1.0
61
+ * @version 1.3.1
48
62
  */
49
63
  alchemy.sputnik.add(function http() {
50
64
 
@@ -54,6 +68,7 @@ alchemy.sputnik.add(function http() {
54
68
  // Listen for requests
55
69
  alchemy.server.on('request', function onRequest(request, response) {
56
70
  Router.resolve(request, response);
71
+ total_http_requests++;
57
72
  });
58
73
  });
59
74
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "alchemymvc",
3
3
  "description": "MVC framework for Node.js",
4
- "version": "1.2.8",
4
+ "version": "1.3.1",
5
5
  "author": "Jelle De Loecker <jelle@elevenways.be>",
6
6
  "keywords": [
7
7
  "alchemy",
@@ -22,7 +22,7 @@
22
22
  "chokidar" : "~3.5.3",
23
23
  "formidable" : "~2.0.1",
24
24
  "graceful-fs" : "~4.2.9",
25
- "hawkejs" : "~2.2.18",
25
+ "hawkejs" : "~2.3.1",
26
26
  "jsondiffpatch" : "~0.4.1",
27
27
  "mime" : "~3.0.0",
28
28
  "minimist" : "~1.2.5",
@@ -31,7 +31,7 @@
31
31
  "mongodb" : "~3.6.6",
32
32
  "ncp" : "~2.0.0",
33
33
  "postcss" : "~8.4.6",
34
- "protoblast" : "~0.7.24",
34
+ "protoblast" : "~0.8.0",
35
35
  "semver" : "~7.3.5",
36
36
  "socket.io" : "~2.4.0",
37
37
  "@11ways/socket.io-stream" : "~0.9.2",
@@ -45,7 +45,7 @@
45
45
  "index.js"
46
46
  ],
47
47
  "optionalDependencies": {
48
- "janeway" : "~0.3.6",
48
+ "janeway" : "~0.4.0",
49
49
  "less" : "~4.1.1",
50
50
  "sass" : "~1.53.0",
51
51
  "sass-embedded" : "~1.53.0",
@@ -56,9 +56,9 @@
56
56
  "codecov" : "~3.8.1",
57
57
  "istanbul-lib-instrument" : "~4.0.3",
58
58
  "mocha" : "~8.3.2",
59
- "mongo-unit" : "~2.0.1",
59
+ "mongo-unit" : "~3.2.0",
60
60
  "nyc" : "^15.1.0",
61
- "puppeteer" : "~9.0.0",
61
+ "puppeteer" : "~19.0.0",
62
62
  "source-map" : "~0.7.3"
63
63
  },
64
64
  "scripts": {