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
@@ -225,9 +225,9 @@ Controller.setMethod(function renderDialogIn(config, template) {
225
225
  /**
226
226
  * Render the given template and send it to the client
227
227
  *
228
- * @author Jelle De Loecker <jelle@develry.be>
228
+ * @author Jelle De Loecker <jelle@elevenways.be>
229
229
  * @since 0.2.0
230
- * @version 1.1.0
230
+ * @version 1.3.1
231
231
  *
232
232
  * @param {Number} status
233
233
  * @param {Array} template
@@ -236,17 +236,24 @@ Controller.setMethod(function renderDialogIn(config, template) {
236
236
  */
237
237
  Controller.setMethod(function render(status, template) {
238
238
 
239
- var that = this,
240
- pledge = new Classes.Pledge();
239
+ const conduit = this.conduit;
241
240
 
242
- this.renderHTML(status, template).done(function gotHtml(err, output) {
241
+ const session = conduit.getSession(false);
242
+
243
+ if (session) {
244
+ session.incrementRenderCount();
245
+ }
246
+
247
+ let pledge = new Classes.Pledge();
248
+
249
+ this.renderHTML(status, template).done(async (err, output) => {
243
250
 
244
251
  if (err) {
245
- that.conduit.error(err);
252
+ conduit.error(err);
246
253
  return pledge.resolve(false);
247
254
  }
248
255
 
249
- that.conduit.end(output);
256
+ conduit.end(output);
250
257
  pledge.resolve(true);
251
258
  });
252
259
 
@@ -466,7 +473,7 @@ Controller.setAction(async function readDatasource(conduit) {
466
473
  *
467
474
  * @author Jelle De Loecker <jelle@develry.be>
468
475
  * @since 1.0.4
469
- * @version 1.0.6
476
+ * @version 1.3.1
470
477
  */
471
478
  Controller.setAction(async function saveRecord(conduit) {
472
479
 
@@ -488,14 +495,10 @@ Controller.setAction(async function saveRecord(conduit) {
488
495
  return;
489
496
  }
490
497
 
491
- let that = this,
492
- data = conduit.param('data'),
493
- options = conduit.param('options');
498
+ let body = JSON.undry(conduit.body);
494
499
 
495
- // @TODO: Add some checks here?
496
- if (data) {
497
- data = JSON.undry({data: data}).data;
498
- }
500
+ let options = body.options,
501
+ data = body.data;
499
502
 
500
503
  if (Object.isPlainObject(data)) {
501
504
  data = model.createDocument(data);
@@ -165,7 +165,7 @@ Datasource.setMethod(function getSchema(schema) {
165
165
  *
166
166
  * @author Jelle De Loecker <jelle@develry.be>
167
167
  * @since 0.2.0
168
- * @version 1.2.0
168
+ * @version 1.3.0
169
169
  *
170
170
  * @param {Schema|Model} schema
171
171
  * @param {Object} data
@@ -187,7 +187,11 @@ Datasource.setMethod(function toDatasource(schema, data, callback) {
187
187
  }
188
188
 
189
189
  if (!schema) {
190
- log.todo('Schema not found: not normalizing data', data);
190
+
191
+ if (alchemy.settings.debug) {
192
+ alchemy.distinctProblem('schema-not-found', 'Schema not found: not normalizing data');
193
+ }
194
+
191
195
  pledge = Pledge.resolve(data);
192
196
  pledge.done(callback);
193
197
  return pledge;
@@ -252,7 +256,7 @@ Datasource.setMethod(function toDatasource(schema, data, callback) {
252
256
  *
253
257
  * @author Jelle De Loecker <jelle@develry.be>
254
258
  * @since 0.2.0
255
- * @version 1.1.0
259
+ * @version 1.3.1
256
260
  *
257
261
  * @param {Schema|Model} schema
258
262
  * @param {Object} query
@@ -277,7 +281,7 @@ Datasource.setMethod(function toApp(schema, query, options, data, callback) {
277
281
  schema = this.getSchema(schema);
278
282
 
279
283
  if (schema == null) {
280
- log.todo('Schema not found: not unnormalizing data');
284
+ alchemy.distinctProblem('schema-not-found-unnormalize', 'Schema not found: not un-normalizing data');
281
285
 
282
286
  let pledge = Pledge.resolve(data);
283
287
  pledge.done(callback);
@@ -515,13 +519,22 @@ Datasource.setMethod(function read(model, criteria, callback) {
515
519
  return pledge.reject(err);
516
520
  }
517
521
 
522
+ if (criteria.options.return_raw_data) {
523
+ criteria.setOption('document', false);
524
+ return handleResults(null, results);
525
+ }
526
+
518
527
  tasks = results.map(function eachEntry(entry) {
519
528
  return function entryToApp(next) {
520
529
  that.toApp(model, criteria, {}, entry, next);
521
530
  };
522
531
  });
523
532
 
524
- sub_pledge = Function.parallel(tasks, function done(err, app_results) {
533
+ sub_pledge = Function.parallel(tasks, handleResults);
534
+
535
+ pledge._addProgressPledge(sub_pledge);
536
+
537
+ function handleResults(err, app_results) {
525
538
 
526
539
  if (err) {
527
540
  return pledge.reject(err);
@@ -545,9 +558,7 @@ Datasource.setMethod(function read(model, criteria, callback) {
545
558
  }
546
559
 
547
560
  pledge.resolve(result);
548
- });
549
-
550
- pledge._addProgressPledge(sub_pledge);
561
+ }
551
562
  });
552
563
 
553
564
  if (this.queryCache && model.cache && cache_pledge) {
@@ -647,7 +647,7 @@ Document.setMethod(['populate', 'addAssociatedData'], function addAssociatedData
647
647
  *
648
648
  * @author Jelle De Loecker <jelle@develry.be>
649
649
  * @since 0.3.0
650
- * @version 0.3.0
650
+ * @version 1.3.0
651
651
  *
652
652
  * @param {Object} options
653
653
  *
@@ -663,7 +663,7 @@ Document.setMethod(function getDisplayFieldValue(options) {
663
663
  options = {};
664
664
  }
665
665
 
666
- display_field = Array.cast(this.$model.displayField);
666
+ display_field = Array.cast(this.$model.display_field);
667
667
 
668
668
  // If there are fields we prefer, check those first
669
669
  if (options.prefer) {
@@ -674,7 +674,7 @@ Document.setMethod(function getDisplayFieldValue(options) {
674
674
  result = this[display_field[i]];
675
675
 
676
676
  if (result) {
677
- result = alchemy.pickTranslation(undefined, result).result;
677
+ result = alchemy.pickTranslation(options.prefix, result).result;
678
678
 
679
679
  if (result) {
680
680
  return result;
@@ -100,6 +100,19 @@ Field.setStatic(function createPathEvaluator(path) {
100
100
  return new Classes.Alchemy.PathEvaluator(path);
101
101
  });
102
102
 
103
+ /**
104
+ * Get the description of the field
105
+ *
106
+ * @author Jelle De Loecker <jelle@elevenways.be>
107
+ * @since 1.3.0
108
+ * @version 1.3.0
109
+ *
110
+ * @type {String}
111
+ */
112
+ Field.setProperty(function description() {
113
+ return this.options?.description || this.options?.options?.description || null;
114
+ });
115
+
103
116
  /**
104
117
  * Is this a private field?
105
118
  *
@@ -288,16 +301,17 @@ Field.setStatic(function unDry(value) {
288
301
  *
289
302
  * @author Jelle De Loecker <jelle@develry.be>
290
303
  * @since 0.2.0
291
- * @version 1.1.0
304
+ * @version 1.3.1
292
305
  *
293
306
  * @return {Object}
294
307
  */
295
308
  Field.setMethod(function toDry() {
296
309
  return {
297
310
  value: {
311
+ bla: 1,
298
312
  schema : this.schema,
299
313
  name : this.name,
300
- options : this.options
314
+ options : this.getOptionsForDrying(),
301
315
  }
302
316
  };
303
317
  });
@@ -671,7 +685,7 @@ Field.setMethod(function _toDatasource(value, data, datasource, callback) {
671
685
  *
672
686
  * @author Jelle De Loecker <jelle@develry.be>
673
687
  * @since 0.2.0
674
- * @version 1.1.5
688
+ * @version 1.3.1
675
689
  *
676
690
  * @param {Object} values
677
691
  */
@@ -685,6 +699,10 @@ Field.setMethod(function regularToDatasource(value, data, datasource, callback)
685
699
  return this._toDatasource(value, data, datasource, callback);
686
700
  }
687
701
 
702
+ if (!value) {
703
+ return callback();
704
+ }
705
+
688
706
  // Arrayable fields need to process every value inside the array
689
707
  tasks = value.map(function eachValue(entry) {
690
708
  return function eachValueToDs(next) {
@@ -1020,6 +1038,19 @@ Field.setMethod(function getClientConfigOptions(wm) {
1020
1038
  return JSON.clone(this.options, 'toHawkejs', wm);
1021
1039
  });
1022
1040
 
1041
+ /**
1042
+ * Get the options that can be used for drying
1043
+ *
1044
+ * @author Jelle De Loecker <jelle@elevenways.be>
1045
+ * @since 1.3.1
1046
+ * @version 1.3.1
1047
+ *
1048
+ * @return {Object}
1049
+ */
1050
+ Field.setMethod(function getOptionsForDrying() {
1051
+ return this.options;
1052
+ });
1053
+
1023
1054
  /**
1024
1055
  * Get all the rules for this field
1025
1056
  *
@@ -26,6 +26,33 @@ var Inode = Function.inherits('Alchemy.Base', 'Alchemy.Inode', function Inode(pa
26
26
  this.stat = null;
27
27
  });
28
28
 
29
+ /**
30
+ * Get the correct Inode instance for the given path
31
+ *
32
+ * @author Jelle De Loecker <jelle@develry.be>
33
+ * @since 1.3.0
34
+ * @version 1.3.0
35
+ *
36
+ * @param {String} path
37
+ *
38
+ * @return {Pledge<Inode>}
39
+ */
40
+ Inode.setStatic(function from(path) {
41
+
42
+ const pledge = new Pledge();
43
+
44
+ Inode.process(path, null, (err, inode) => {
45
+
46
+ if (err) {
47
+ return pledge.reject(err);
48
+ }
49
+
50
+ pledge.resolve(inode);
51
+ });
52
+
53
+ return pledge;
54
+ });
55
+
29
56
  /**
30
57
  * Process given path
31
58
  *
@@ -1,4 +1,10 @@
1
- const fs = alchemy.use('fs');
1
+ const HASH = Symbol('hash'),
2
+ ALGORITHM = Symbol('algorithm'),
3
+ LIBMAGIC = alchemy.use('mmmagic'),
4
+ LIBMIME = alchemy.use('mime'),
5
+ fs = alchemy.use('fs');
6
+
7
+ let Magic;
2
8
 
3
9
  /**
4
10
  * The File class
@@ -17,8 +23,8 @@ var File = Function.inherits('Alchemy.Inode', function File(path) {
17
23
  return File.from(path);
18
24
  }
19
25
 
26
+ this.possible_type = null;
20
27
  this.type = null;
21
- this.hash = null;
22
28
 
23
29
  File.super.call(this, path);
24
30
  });
@@ -34,12 +40,130 @@ var File = Function.inherits('Alchemy.Inode', function File(path) {
34
40
  */
35
41
  File.setProperty('is_file', true);
36
42
 
43
+ /**
44
+ * Detect the filetype of the given path
45
+ *
46
+ * @author Jelle De Loecker <jelle@elevenways.be>
47
+ * @since 1.3.0
48
+ * @version 1.3.0
49
+ *
50
+ * @param {String} path
51
+ *
52
+ * @return {Pledge<String>}
53
+ */
54
+ File.setStatic(function getMimetype(path) {
55
+
56
+ let pledge = new Pledge();
57
+
58
+ if (!Magic && LIBMAGIC) {
59
+ Magic = new LIBMAGIC.Magic(LIBMAGIC.MAGIC_MIME_TYPE);
60
+ }
61
+
62
+ if (Magic) {
63
+ Magic.detectFile(path, (err, result) => {
64
+
65
+ if (err || result == 'application/octet-stream' || result == 'text/plain') {
66
+ result = File.guessMimetypeFromPath(path, result);
67
+ }
68
+
69
+ if (result == 'application/javascript') {
70
+ result = 'text/javascript';
71
+ } else if (result == 'image/svg') {
72
+ result = 'image/svg+xml';
73
+ }
74
+
75
+ pledge.resolve(result);
76
+ });
77
+ }
78
+
79
+ return pledge;
80
+ });
81
+
82
+ /**
83
+ * Guess the mimetype from the path
84
+ *
85
+ * @author Jelle De Loecker <jelle@elevenways.be>
86
+ * @since 1.3.0
87
+ * @version 1.3.0
88
+ *
89
+ * @param {String} path
90
+ * @param {String} fallback
91
+ *
92
+ * @return {String}
93
+ */
94
+ File.setStatic(function guessMimetypeFromPath(path, fallback) {
95
+
96
+ let result = fallback;
97
+
98
+ if (LIBMIME) {
99
+ result = LIBMIME.getType(path);
100
+
101
+ if (fallback && result != fallback) {
102
+ if (result == 'application/octet-stream' || result == 'text/plain') {
103
+ result = fallback;
104
+ }
105
+ }
106
+ }
107
+
108
+ return result;
109
+ });
110
+
111
+ /**
112
+ * Create a File instance from the given variable
113
+ * (From an untrusted source)
114
+ *
115
+ * @author Jelle De Loecker <jelle@elevenways.be>
116
+ * @since 1.3.0
117
+ * @version 1.3.0
118
+ */
119
+ File.setStatic(function fromUntrusted(obj) {
120
+
121
+ if (!obj) {
122
+ return;
123
+ }
124
+
125
+ if (typeof obj == 'string') {
126
+ return File.from(obj);
127
+ }
128
+
129
+ let path,
130
+ name,
131
+ type,
132
+ hash,
133
+ size;
134
+
135
+ if (obj.originalFilename) {
136
+ // Formidable 2.0
137
+ path = obj.filepath;
138
+ name = obj.originalFilename;
139
+ type = obj.mimetype;
140
+ hash = obj.hash;
141
+ size = obj.size;
142
+ } else {
143
+ path = obj.path;
144
+ name = obj.name;
145
+ type = obj.type;
146
+ hash = obj.hash;
147
+ size = obj.size;
148
+ }
149
+
150
+ obj = {
151
+ path,
152
+ name,
153
+ hash,
154
+ size,
155
+ possible_type : type,
156
+ };
157
+
158
+ return File.from(obj);
159
+ });
160
+
37
161
  /**
38
162
  * Create a File instance from the given variable
39
163
  *
40
164
  * @author Jelle De Loecker <jelle@develry.be>
41
165
  * @since 1.1.0
42
- * @version 1.2.7
166
+ * @version 1.3.0
43
167
  */
44
168
  File.setStatic(function from(obj) {
45
169
 
@@ -47,7 +171,8 @@ File.setStatic(function from(obj) {
47
171
  return;
48
172
  }
49
173
 
50
- let path,
174
+ let possible_type,
175
+ path,
51
176
  name,
52
177
  type,
53
178
  hash,
@@ -68,6 +193,7 @@ File.setStatic(function from(obj) {
68
193
  type = obj.type;
69
194
  hash = obj.hash;
70
195
  size = obj.size;
196
+ possible_type = obj.possible_type;
71
197
  }
72
198
 
73
199
  let file = new File(path);
@@ -88,9 +214,83 @@ File.setStatic(function from(obj) {
88
214
  file.hash = hash;
89
215
  }
90
216
 
217
+ if (possible_type) {
218
+ file.possible_type = possible_type;
219
+ }
220
+
91
221
  return file;
92
222
  });
93
223
 
224
+ /**
225
+ * Get the mimetype of the file
226
+ *
227
+ * @author Jelle De Loecker <jelle@elevenways.be>
228
+ * @since 1.3.0
229
+ * @version 1.3.0
230
+ *
231
+ * @return {String|Pledge<String>}
232
+ */
233
+ File.setMethod(function getMimetype() {
234
+
235
+ if (!this.type) {
236
+ const pledge = new Pledge();
237
+ this.type = pledge;
238
+
239
+ File.getMimetype(this.path).done((err, result) => {
240
+
241
+ if (err || !result) {
242
+ result = this.possible_type;
243
+ }
244
+
245
+ this.type = result;
246
+ pledge.resolve(result);
247
+ });
248
+ }
249
+
250
+ return this.type;
251
+ });
252
+
253
+ /**
254
+ * Get the SHA1 hash of the file
255
+ *
256
+ * @author Jelle De Loecker <jelle@elevenways.be>
257
+ * @since 1.3.0
258
+ * @version 1.3.0
259
+ *
260
+ * @param {String} algorithm
261
+ *
262
+ * @return {String|Pledge<String>}
263
+ */
264
+ File.setMethod(function getHash(algorithm) {
265
+
266
+ algorithm = algorithm || alchemy.settings.file_hash_algorithm;
267
+
268
+ if (!this[HASH] || this[ALGORITHM] != algorithm) {
269
+ const options = {algorithm};
270
+ this[HASH] = alchemy.hashFile(this.path, options);
271
+ this[ALGORITHM] = algorithm;
272
+
273
+ this[HASH].done((err, result) => {
274
+ this[HASH] = result;
275
+ });
276
+ }
277
+
278
+ return this[HASH];
279
+ });
280
+
281
+ /**
282
+ * Create a read stream
283
+ *
284
+ * @author Jelle De Loecker <jelle@elevenways.be>
285
+ * @since 1.3.0
286
+ * @version 1.3.0
287
+ *
288
+ * @param {Object} options Options passed to the `fs.createReadStream` method
289
+ */
290
+ File.setMethod(function createReadStream(options) {
291
+ return fs.createReadStream(this.path, options);
292
+ });
293
+
94
294
  /**
95
295
  * Read and return the contents
96
296
  *
@@ -118,7 +118,7 @@ Migration.setStatic(async function start() {
118
118
  *
119
119
  * @author Jelle De Loecker <jelle@elevenways.be>
120
120
  * @since 1.2.0
121
- * @version 1.2.0
121
+ * @version 1.3.1
122
122
  */
123
123
  Migration.setMethod(function processRecords(model_name, fnc) {
124
124
 
@@ -127,6 +127,7 @@ Migration.setMethod(function processRecords(model_name, fnc) {
127
127
  let options = {
128
128
  document : false,
129
129
  parallel_limit : 1,
130
+ return_raw_data: true,
130
131
  };
131
132
 
132
133
  return model.eachRecord(options, async (record, index, next) => {
@@ -509,7 +509,7 @@ Model.setStatic(function getField(name) {
509
509
  *
510
510
  * @author Jelle De Loecker <jelle@elevenways.be>
511
511
  * @since 1.0.0
512
- * @version 1.2.7
512
+ * @version 1.3.1
513
513
  */
514
514
  Model.setStatic(function getClientConfig() {
515
515
 
@@ -518,10 +518,21 @@ Model.setStatic(function getClientConfig() {
518
518
  schema : this.schema,
519
519
  primary_key : this.prototype.primary_key,
520
520
  display_field : this.prototype.display_field,
521
+ ancestors : 0,
521
522
  };
522
523
 
523
524
  if (this.super.name != 'Model') {
524
525
  result.parent = this.super.name;
526
+
527
+ let ancestors = 0,
528
+ ancestor = this.super;
529
+
530
+ while (ancestor && ancestor.name != 'Model') {
531
+ ancestors++;
532
+ ancestor = ancestor.super;
533
+ }
534
+
535
+ result.ancestors = ancestors;
525
536
  }
526
537
 
527
538
  return result;
@@ -643,7 +654,7 @@ Model.setMethod(function aggregate(pipeline, callback) {
643
654
  *
644
655
  * @author Jelle De Loecker <jelle@develry.be>
645
656
  * @since 0.2.0
646
- * @version 1.2.7
657
+ * @version 1.3.0
647
658
  *
648
659
  * @param {Array} items
649
660
  * @param {Object} options Optional options object
@@ -661,7 +672,7 @@ Model.setMethod(function translateItems(items, options, callback) {
661
672
  }
662
673
 
663
674
  // No fields in this schema are translatable
664
- if (!this.schema.hasTranslations) {
675
+ if (!this.schema.has_translations) {
665
676
  return callback();
666
677
  }
667
678
 
@@ -775,8 +786,8 @@ Model.setMethod(function translateItems(items, options, callback) {
775
786
  for (j = 0; j < collection.length; j++) {
776
787
  record = collection[j];
777
788
 
778
- for (fieldName in this.schema.translatableFields) {
779
- field = this.schema.translatableFields[fieldName];
789
+ for (fieldName in this.schema.translatable_fields) {
790
+ field = this.schema.translatable_fields[fieldName];
780
791
  field.translateRecord(prefixes, record, options.allow_empty);
781
792
  }
782
793
  }