alchemymvc 1.2.7 → 1.3.0

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 (35) hide show
  1. package/lib/app/behaviour/sluggable_behaviour.js +4 -2
  2. package/lib/app/conduit/loopback_conduit.js +2 -2
  3. package/lib/app/controller/alchemy_info_controller.js +5 -5
  4. package/lib/app/helper/router_helper.js +82 -17
  5. package/lib/app/helper_controller/controller.js +45 -30
  6. package/lib/app/helper_datasource/00-nosql_datasource.js +44 -10
  7. package/lib/app/helper_field/enum_field.js +4 -4
  8. package/lib/app/helper_field/schema_field.js +6 -6
  9. package/lib/app/helper_model/document.js +41 -10
  10. package/lib/app/helper_model/field_set.js +11 -0
  11. package/lib/app/helper_model/model.js +35 -10
  12. package/lib/app/view/alchemy/{info.ejs → info.hwk} +43 -38
  13. package/lib/bootstrap.js +1 -0
  14. package/lib/class/conduit.js +231 -244
  15. package/lib/class/datasource.js +6 -2
  16. package/lib/class/document.js +3 -3
  17. package/lib/class/field.js +13 -0
  18. package/lib/class/inode.js +27 -0
  19. package/lib/class/inode_file.js +204 -4
  20. package/lib/class/model.js +4 -4
  21. package/lib/class/path_definition.js +76 -120
  22. package/lib/class/path_param_definition.js +202 -0
  23. package/lib/class/route.js +176 -32
  24. package/lib/class/router.js +17 -3
  25. package/lib/class/schema.js +11 -11
  26. package/lib/class/schema_client.js +52 -19
  27. package/lib/class/sitemap.js +341 -0
  28. package/lib/core/base.js +6 -2
  29. package/lib/core/client_alchemy.js +76 -7
  30. package/lib/core/client_base.js +16 -10
  31. package/lib/core/middleware.js +56 -45
  32. package/lib/init/alchemy.js +12 -9
  33. package/lib/init/constants.js +11 -0
  34. package/lib/init/functions.js +134 -83
  35. package/package.json +5 -5
@@ -0,0 +1,202 @@
1
+ const PathDefinition = Classes.Alchemy.PathDefinition;
2
+
3
+ /**
4
+ * Path param definition
5
+ *
6
+ * @author Jelle De Loecker <jelle@elevenways.be>
7
+ * @since 1.3.0
8
+ * @version 1.3.0
9
+ */
10
+ const PathParamDefinition = Function.inherits('Alchemy.Base', function PathParamDefinition(config) {
11
+
12
+ // The delimiter, probably "/"
13
+ this.delimiter = config.delimiter;
14
+
15
+ // The name of this parameter
16
+ this.name = config.name;
17
+
18
+ // Is this parameter optional?
19
+ this.optional = config.optional || false;
20
+
21
+ this.partial = config.partial;
22
+ this.pattern = config.pattern;
23
+ this.prefix = config.prefix;
24
+ this.repeat = config.repeat;
25
+
26
+ // Set the raw type definition
27
+ this.typedef = config.typedef;
28
+
29
+ // Does this have a type definition?
30
+ this.has_type_definition = !!this.typedef;
31
+
32
+ // Does this use a simple typedefinition?
33
+ this.uses_simple_typedef = false;
34
+
35
+ // The optional type class name
36
+ this.type_class_name = null;
37
+
38
+ // The optional field inside the class
39
+ this.type_field_name = null;
40
+
41
+ // The optional type class constructor
42
+ this.type_class_constructor = null;
43
+
44
+ // Has all the config been parsed?
45
+ this.is_parsed = false;
46
+ });
47
+
48
+ /**
49
+ * Create path param definitions
50
+ *
51
+ * @author Jelle De Loecker <jelle@elevenways.be>
52
+ * @since 1.3.0
53
+ * @version 1.3.0
54
+ *
55
+ * @param {Array|Object}
56
+ */
57
+ PathParamDefinition.setStatic(function from(input) {
58
+
59
+ if (!input) {
60
+ return;
61
+ }
62
+
63
+ if (Array.isArray(input)) {
64
+ let result = [],
65
+ entry,
66
+ temp;
67
+
68
+ for (entry of input) {
69
+ temp = PathParamDefinition.from(entry);
70
+
71
+ if (temp) {
72
+ result.push(temp);
73
+ }
74
+ }
75
+
76
+ return result;
77
+ }
78
+
79
+ return new this(input);
80
+ });
81
+
82
+ /**
83
+ * Parse the type definition
84
+ *
85
+ * @author Jelle De Loecker <jelle@elevenways.be>
86
+ * @since 1.3.0
87
+ * @version 1.3.0
88
+ */
89
+ PathParamDefinition.setMethod(function parseTypeDefinition() {
90
+
91
+ if (this.is_parsed) {
92
+ return;
93
+ }
94
+
95
+ this.is_parsed = true;
96
+
97
+ if (!this.has_type_definition) {
98
+ return;
99
+ }
100
+
101
+ let class_name,
102
+ field_name;
103
+
104
+ if (typeof this.typedef == 'string') {
105
+ this.uses_simple_typedef = !!PathDefinition.typedefs[this.typedef];
106
+
107
+ // If it's not a simple type, the type is a class name
108
+ if (!this.uses_simple_typedef) {
109
+ class_name = this.typedef;
110
+ }
111
+ } else {
112
+ class_name = this.typedef[0];
113
+ field_name = this.typedef[1];
114
+ }
115
+
116
+ if (class_name) {
117
+ this.type_class_name = class_name;
118
+ this.type_field_name = field_name;
119
+
120
+ const Model = Classes.Alchemy.Model || Classes.Alchemy.Client.Model;
121
+ this.type_class_constructor = Object.path(Model, class_name) || Object.path(Classes.Alchemy, class_name) || Object.path(Classes, class_name);
122
+ }
123
+ });
124
+
125
+ /**
126
+ * Check the value
127
+ *
128
+ * @author Jelle De Loecker <jelle@elevenways.be>
129
+ * @since 1.3.0
130
+ * @version 1.3.0
131
+ *
132
+ * @param {Boolean|Pledge<Boolean>}
133
+ */
134
+ PathParamDefinition.setMethod(function castValueToType(value, conduit) {
135
+
136
+ if (!this.is_parsed) {
137
+ this.parseTypeDefinition();
138
+ }
139
+
140
+ let result;
141
+
142
+ if (this.uses_simple_typedef) {
143
+ result = PathDefinition.typedefs[this.typedef](value, this.name, conduit);
144
+ } else if (this.type_class_constructor?.checkPathValue) {
145
+ result = this.type_class_constructor.checkPathValue(value, this.name, this.type_field_name, conduit);
146
+ }
147
+
148
+ return result;
149
+ });
150
+
151
+
152
+ /**
153
+ * Parse an original path value
154
+ *
155
+ * @author Jelle De Loecker <jelle@elevenways.be>
156
+ * @since 1.3.0
157
+ * @version 1.3.0
158
+ *
159
+ * @param {Object|Pledge<Object>}
160
+ */
161
+ PathParamDefinition.setMethod(function parsePathValue(original_value, conduit) {
162
+
163
+ let result = {
164
+ name : this.name,
165
+ value : original_value,
166
+ rejected : false,
167
+ original_value,
168
+ };
169
+
170
+ if (this.has_type_definition) {
171
+ let new_value = this.castValueToType(original_value, conduit);
172
+
173
+ if (typeof new_value === 'undefined') {
174
+ return false;
175
+ }
176
+
177
+ if (Pledge.isThenable(new_value)) {
178
+ let pledge = new Pledge();
179
+
180
+ Pledge.done(new_value, (err, new_value) => {
181
+
182
+ if (err) {
183
+ pledge.reject(err);
184
+ } else {
185
+ result.value = new_value;
186
+
187
+ if (new_value == null) {
188
+ result.rejected = true;
189
+ }
190
+
191
+ pledge.resolve(result);
192
+ }
193
+ });
194
+
195
+ return pledge;
196
+ }
197
+
198
+ result.value = new_value;
199
+ }
200
+
201
+ return result;
202
+ });
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * @author Jelle De Loecker <jelle@develry.be>
5
5
  * @since 0.2.0
6
- * @version 1.2.5
6
+ * @version 1.3.0
7
7
  */
8
- var Route = Function.inherits('Alchemy.Base', function Route(router, paths, options) {
8
+ const Route = Function.inherits('Alchemy.Base', function Route(router, paths, options) {
9
9
 
10
10
  // Store the parent router
11
11
  this.router = router;
@@ -45,6 +45,12 @@ var Route = Function.inherits('Alchemy.Base', function Route(router, paths, opti
45
45
  this.permission = null;
46
46
  this.has_permission_assignments = false;
47
47
 
48
+ // The sitemap configuration
49
+ this.sitemap = null;
50
+
51
+ // Can this route's path be used in the browser's address location?
52
+ this.visible_location = true;
53
+
48
54
  // If no fnc is given, these will be called
49
55
  this.controller = null;
50
56
  this.action = null;
@@ -61,6 +67,65 @@ var Route = Function.inherits('Alchemy.Base', function Route(router, paths, opti
61
67
  */
62
68
  Route.setDeprecatedProperty('isMiddleware', 'is_middleware');
63
69
 
70
+ /**
71
+ * Does this route require extra data for translating to another language?
72
+ *
73
+ * @author Jelle De Loecker <jelle@elevenways.be>
74
+ * @since 1.3.0
75
+ * @version 1.3.0
76
+ *
77
+ * @return {Boolean}
78
+ */
79
+ Route.setProperty(function requires_data_for_translation() {
80
+
81
+ if (!this.is_prefix_aware || !this.has_path_assignments) {
82
+ return false;
83
+ }
84
+
85
+ return true;
86
+ });
87
+
88
+ /**
89
+ * Does this route have any path assignments?
90
+ *
91
+ * @author Jelle De Loecker <jelle@elevenways.be>
92
+ * @since 1.3.0
93
+ * @version 1.3.0
94
+ *
95
+ * @return {Boolean}
96
+ */
97
+ Route.setProperty(function has_path_assignments() {
98
+ return !!this.keys?.length;
99
+ });
100
+
101
+ /**
102
+ * Does this route use any type class checks?
103
+ * (Type CLASS checks use the type checker of a specific class)
104
+ *
105
+ * @author Jelle De Loecker <jelle@elevenways.be>
106
+ * @since 1.3.0
107
+ * @version 1.3.0
108
+ *
109
+ * @return {Boolean}
110
+ */
111
+ Route.setProperty(function has_type_class_checks() {
112
+
113
+ let result = false,
114
+ path,
115
+ key;
116
+
117
+ for (key in this.paths) {
118
+ path = this.paths[key];
119
+
120
+ if (path.uses_type_class_checks) {
121
+ result = true;
122
+ break;
123
+ }
124
+ }
125
+
126
+ return result;
127
+ });
128
+
64
129
  /**
65
130
  * Set the breadcrumb for this route
66
131
  *
@@ -122,7 +187,7 @@ Route.setMethod(function setPermission(permission) {
122
187
  *
123
188
  * @author Jelle De Loecker <jelle@develry.be>
124
189
  * @since 0.2.0
125
- * @version 1.1.0
190
+ * @version 1.3.0
126
191
  *
127
192
  * @param {String|Object} paths
128
193
  */
@@ -150,7 +215,10 @@ Route.setMethod(function setPaths(paths) {
150
215
 
151
216
  for (prefix in paths) {
152
217
  path = paths[prefix];
153
- definition = new Classes.Alchemy.PathDefinition(path, {end: !this.is_middleware});
218
+ definition = new Classes.Alchemy.PathDefinition(path, {
219
+ prefix,
220
+ end: !this.is_middleware,
221
+ });
154
222
  this.paths[prefix] = definition;
155
223
 
156
224
  if (definition.keys.length) {
@@ -513,29 +581,21 @@ Route.setMethod(function generateUrl(parameters, conduit) {
513
581
  *
514
582
  * @author Jelle De Loecker <jelle@develry.be>
515
583
  * @since 0.2.0
516
- * @version 1.0.5
584
+ * @version 1.3.0
517
585
  *
518
586
  * @param {Conduit} conduit
519
587
  */
520
- Route.setMethod(function callController(conduit) {
588
+ Route.setMethod(function callController(...args) {
521
589
 
522
- var instance,
523
- args,
524
- i;
590
+ const conduit = args[0];
525
591
 
526
592
  if (this.controller) {
527
- instance = Controller.get(this.controller, conduit);
593
+ let instance = Controller.get(this.controller, conduit);
528
594
 
529
595
  if (!instance) {
530
596
  return conduit.error(new Error('Could not find controller "' + this.controller + '"'));
531
597
  }
532
598
 
533
- args = new Array(arguments.length);
534
-
535
- for (i = 0; i < args.length; i++) {
536
- args[i] = arguments[i];
537
- }
538
-
539
599
  return instance.doAction(this.action, args);
540
600
  }
541
601
 
@@ -546,41 +606,125 @@ Route.setMethod(function callController(conduit) {
546
606
  * Call a controller action as handler, but get info from the url first.
547
607
  * This gets called by `callHandler`.
548
608
  *
549
- * @author Jelle De Loecker <jelle@develry.be>
609
+ * @author Jelle De Loecker <jelle@elevenways.be>
550
610
  * @since 0.2.0
551
- * @version 0.2.0
611
+ * @version 1.3.0
552
612
  *
553
613
  * @param {Conduit} conduit
554
614
  */
555
- Route.setMethod(function callControllerAssignments(conduit) {
615
+ Route.setMethod(function callControllerAssignments(...args) {
556
616
 
557
- var controller,
558
- instance,
559
- action,
560
- args,
561
- i;
617
+ const conduit = args[0];
562
618
 
563
619
  if (this.controller) {
564
620
 
565
621
  // Get the controller name from the route parameters
566
- controller = this.controller.assign(conduit.params);
622
+ let controller = this.controller.assign(conduit.params);
567
623
 
568
624
  // Try getting a controller instance
569
- instance = Controller.get(controller, conduit);
625
+ let instance = Controller.get(controller, conduit);
570
626
 
571
627
  if (!instance) {
572
628
  throw new Error('Could not find controller "' + controller + '"');
573
629
  }
574
630
 
575
- args = new Array(arguments.length);
631
+ let action = this.action.assign(conduit.params);
632
+ return instance.doAction(action, args);
633
+ }
634
+
635
+ throw new Error('No valid controller was set');
636
+ });
637
+
638
+ /**
639
+ * Set the values needed for translating this route
640
+ *
641
+ * @author Jelle De Loecker <jelle@elevenways.be>
642
+ * @since 1.3.0
643
+ * @version 1.3.0
644
+ *
645
+ * @param {Controller} controller
646
+ * @param {Conduit} conduit
647
+ */
648
+ Route.setMethod(function getRouteTranslations(controller, conduit) {
649
+
650
+ if (!this.has_type_class_checks) {
651
+ return;
652
+ }
653
+
654
+ const current_prefix = conduit.prefix,
655
+ paths = [];
656
+
657
+ let path;
658
+
659
+ for (let key in this.paths) {
660
+
661
+ if (!key || key == current_prefix) {
662
+ continue;
663
+ }
664
+
665
+ path = this.paths[key];
576
666
 
577
- for (i = 0; i < args.length; i++) {
578
- args[i] = arguments[i];
667
+ if (!path?.param_definitions?.length) {
668
+ continue;
579
669
  }
580
670
 
581
- action = this.action.assign(conduit.params);
582
- return instance.doAction(action, args);
671
+ paths.push(path);
583
672
  }
584
673
 
585
- throw new Error('No valid controller was set');
674
+ if (!paths.length) {
675
+ return;
676
+ }
677
+
678
+ return this._getRouteTranslations(controller, conduit, paths);
679
+ });
680
+
681
+ /**
682
+ * Do the actual path value translations
683
+ *
684
+ * @author Jelle De Loecker <jelle@elevenways.be>
685
+ * @since 1.3.0
686
+ * @version 1.3.0
687
+ *
688
+ * @param {Controller} controller
689
+ * @param {Conduit} conduit
690
+ */
691
+ Route.setMethod(async function _getRouteTranslations(controller, conduit, paths) {
692
+
693
+ let param_def,
694
+ result = {},
695
+ path;
696
+
697
+ // Also add the current prefix
698
+ result[conduit.prefix] = alchemy.routeUrl(this.name, conduit.route_string_parameters, {prefix: conduit.prefix});
699
+
700
+ for (path of paths) {
701
+
702
+ // Create a shallow clone of the original string parameters
703
+ let params = Object.assign({}, conduit.route_string_parameters);
704
+
705
+ for (param_def of path.param_definitions) {
706
+ let current_value = conduit.param(param_def.name),
707
+ new_value;
708
+
709
+ if (current_value && current_value.getTranslatedValueOfField) {
710
+ new_value = await current_value.getTranslatedValueOfField(param_def.name, path.prefix);
711
+ }
712
+
713
+ if (!new_value) {
714
+ params = false;
715
+ break;
716
+ }
717
+
718
+ params[param_def.name] = new_value;
719
+ }
720
+
721
+ if (!params) {
722
+ result[path.prefix] = false;
723
+ continue;
724
+ }
725
+
726
+ result[path.prefix] = alchemy.routeUrl(this.name, params, {prefix: path.prefix});
727
+ }
728
+
729
+ return result;
586
730
  });
@@ -701,7 +701,7 @@ RouterClass.setMethod(function use(_paths, _fnc, _options) {
701
701
  *
702
702
  * @author Jelle De Loecker <jelle@develry.be>
703
703
  * @since 1.1.0
704
- * @version 1.1.0
704
+ * @version 1.3.0
705
705
  *
706
706
  * @param {String} module_name The name of the dependency
707
707
  * @param {Object} options The options or single string
@@ -740,6 +740,10 @@ RouterClass.setMethod(function serveDependencyFile(module_name, options) {
740
740
  }
741
741
 
742
742
  this.use(options.alias, function serveFile(req, res) {
743
+
744
+ // Cache this file so earlier middleware can handle this from now on
745
+ alchemy.getCache('files.assets').set(req.url, {path: options.path});
746
+
743
747
  req.conduit.serveFile(options.path);
744
748
  });
745
749
  });
@@ -763,7 +767,7 @@ RouterClass.setMethod(function setOption(name, value) {
763
767
  *
764
768
  * @author Jelle De Loecker <jelle@develry.be>
765
769
  * @since 0.2.0
766
- * @version 1.2.5
770
+ * @version 1.3.0
767
771
  *
768
772
  * @param {Object} args
769
773
  * @param {String} args.name Optional route name
@@ -813,6 +817,12 @@ RouterClass.setMethod(function add(args) {
813
817
  route.name = args.name;
814
818
  route.methods = args.methods;
815
819
  route.setBreadcrumb(args.breadcrumb);
820
+ route.sitemap = args.sitemap;
821
+ route.title = args.title;
822
+
823
+ if (args.visible_location != null) {
824
+ route.visible_location = args.visible_location;
825
+ }
816
826
 
817
827
  if (args.permission) {
818
828
  route.setPermission(args.permission);
@@ -846,6 +856,8 @@ RouterClass.setMethod(function add(args) {
846
856
  allroutes[this.name + '::' + args.name] = '' + this.name + ' - ' + args.name;
847
857
  }
848
858
 
859
+ alchemy.checkExposedRouteData();
860
+
849
861
  return route;
850
862
  });
851
863
 
@@ -1147,7 +1159,7 @@ RouterClass.setMethod(function getOptions(result) {
1147
1159
  *
1148
1160
  * @author Jelle De Loecker <jelle@elevenways.be>
1149
1161
  * @since 0.2.0
1150
- * @version 1.2.7
1162
+ * @version 1.3.0
1151
1163
  *
1152
1164
  * @param {Object} result Optional object to store sectioned results in
1153
1165
  *
@@ -1194,6 +1206,8 @@ RouterClass.setMethod(function getRoutes(result) {
1194
1206
  methods : route.methods,
1195
1207
  permission : route.permission || undefined,
1196
1208
  has_permission_assignments : route.has_permission_assignments || undefined,
1209
+ title : route.title,
1210
+ visible_location : route.visible_location,
1197
1211
  };
1198
1212
  }
1199
1213
 
@@ -74,7 +74,7 @@ Schema.setStatic(function unDry(value) {
74
74
  *
75
75
  * @author Jelle De Loecker <jelle@develry.be>
76
76
  * @since 0.2.0
77
- * @version 0.2.3
77
+ * @version 1.3.0
78
78
  *
79
79
  * @param {String} behaviour_name
80
80
  * @param {Object} options
@@ -88,7 +88,7 @@ Schema.setMethod(function addBehaviour(behaviour_name, options) {
88
88
  throw new Error('Could not find Behaviour "' + behaviour_name + '"');
89
89
  }
90
90
 
91
- this.hasBehaviours++;
91
+ this.has_behaviours++;
92
92
 
93
93
  if (!options) {
94
94
  options = {};
@@ -122,7 +122,7 @@ Schema.setMethod(function addBehaviour(behaviour_name, options) {
122
122
  *
123
123
  * @author Jelle De Loecker <jelle@develry.be>
124
124
  * @since 0.2.0
125
- * @version 0.3.0
125
+ * @version 1.3.0
126
126
  *
127
127
  * @param {Object} data
128
128
  * @param {String} hasType Get indexes with this type only
@@ -138,8 +138,8 @@ Schema.setMethod(function getRecordIndexes(data, hasType) {
138
138
  result = {};
139
139
 
140
140
  for (fieldName in data) {
141
- if (this.indexFields[fieldName] != null) {
142
- indexName = this.indexFields[fieldName].name;
141
+ if (this.index_fields[fieldName] != null) {
142
+ indexName = this.index_fields[fieldName].name;
143
143
 
144
144
  // Skip non alternates
145
145
  if (hasType && !this.indexes[indexName].options[hasType]) {
@@ -412,18 +412,18 @@ Schema.setMethod(function addAssociation(type, alias, modelName, options) {
412
412
  *
413
413
  * @author Jelle De Loecker <jelle@develry.be>
414
414
  * @since 0.2.0
415
- * @version 0.2.0
415
+ * @version 1.3.0
416
416
  *
417
417
  * @param {String} name
418
418
  * @param {Object} values
419
419
  */
420
420
  Schema.setMethod(function addEnumValues(name, values) {
421
421
 
422
- if (this.enumValues[name] == null) {
423
- this.enumValues[name] = {};
422
+ if (this.enum_values[name] == null) {
423
+ this.enum_values[name] = {};
424
424
  }
425
425
 
426
- Object.assign(this.enumValues[name], values);
426
+ Object.assign(this.enum_values[name], values);
427
427
  });
428
428
 
429
429
  /**
@@ -432,13 +432,13 @@ Schema.setMethod(function addEnumValues(name, values) {
432
432
  *
433
433
  * @author Jelle De Loecker <jelle@develry.be>
434
434
  * @since 0.2.0
435
- * @version 0.2.0
435
+ * @version 1.3.0
436
436
  *
437
437
  * @param {String} name
438
438
  * @param {Object} values
439
439
  */
440
440
  Schema.setMethod(function setEnumValues(name, values) {
441
- this.enumValues[name] = values;
441
+ this.enum_values[name] = values;
442
442
  });
443
443
 
444
444
  /**