alchemymvc 1.2.8 → 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.
@@ -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
  *
@@ -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
  *
@@ -643,7 +643,7 @@ Model.setMethod(function aggregate(pipeline, callback) {
643
643
  *
644
644
  * @author Jelle De Loecker <jelle@develry.be>
645
645
  * @since 0.2.0
646
- * @version 1.2.7
646
+ * @version 1.3.0
647
647
  *
648
648
  * @param {Array} items
649
649
  * @param {Object} options Optional options object
@@ -661,7 +661,7 @@ Model.setMethod(function translateItems(items, options, callback) {
661
661
  }
662
662
 
663
663
  // No fields in this schema are translatable
664
- if (!this.schema.hasTranslations) {
664
+ if (!this.schema.has_translations) {
665
665
  return callback();
666
666
  }
667
667
 
@@ -775,8 +775,8 @@ Model.setMethod(function translateItems(items, options, callback) {
775
775
  for (j = 0; j < collection.length; j++) {
776
776
  record = collection[j];
777
777
 
778
- for (fieldName in this.schema.translatableFields) {
779
- field = this.schema.translatableFields[fieldName];
778
+ for (fieldName in this.schema.translatable_fields) {
779
+ field = this.schema.translatable_fields[fieldName];
780
780
  field.translateRecord(prefixes, record, options.allow_empty);
781
781
  }
782
782
  }
@@ -3,12 +3,9 @@
3
3
  *
4
4
  * @author Jelle De Loecker <jelle@develry.be>
5
5
  * @since 1.0.0
6
- * @version 1.0.0
6
+ * @version 1.3.0
7
7
  */
8
- var PathDefinition = Function.inherits('Alchemy.Base', function PathDefinition(path, options) {
9
-
10
- var tokens,
11
- i;
8
+ const PathDefinition = Function.inherits('Alchemy.Base', function PathDefinition(path, options) {
12
9
 
13
10
  // Store the original path
14
11
  this.path = path;
@@ -17,22 +14,32 @@ var PathDefinition = Function.inherits('Alchemy.Base', function PathDefinition(p
17
14
  // Store the key tokens
18
15
  this.key_tokens = [];
19
16
 
17
+ // Store the param definitions
18
+ this.param_definitions = null;
19
+
20
20
  // Store the key names
21
21
  this.keys = [];
22
22
 
23
23
  // Path options
24
24
  this.options = options || {};
25
25
 
26
+ // The prefix of this path
27
+ this.prefix = this.options.prefix;
28
+
26
29
  // Get the tokens
27
- tokens = this.parse(path);
30
+ let tokens = this.parse(path);
28
31
 
29
32
  if (!this.regex) {
33
+ let i;
34
+
30
35
  // Compile the regex
31
36
  this.regex = this.tokensToRegexp(tokens, this.key_tokens);
32
37
 
33
38
  for (i = 0; i < this.key_tokens.length; i++) {
34
39
  this.keys[i] = this.key_tokens[i].name;
35
40
  }
41
+
42
+ this.param_definitions = Classes.Alchemy.PathParamDefinition.from(this.key_tokens);
36
43
  }
37
44
  });
38
45
 
@@ -103,6 +110,34 @@ PathDefinition.setStatic(function escapeString(str) {
103
110
  return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1')
104
111
  });
105
112
 
113
+
114
+ /**
115
+ * Does this path use any type class checks?
116
+ *
117
+ * @author Jelle De Loecker <jelle@elevenways.be>
118
+ * @since 1.3.0
119
+ * @version 1.3.0
120
+ */
121
+ PathDefinition.enforceProperty(function uses_type_class_checks(new_value) {
122
+
123
+ if (new_value == null) {
124
+ new_value = false;
125
+
126
+ if (this.param_definitions?.length) {
127
+ for (let def of this.param_definitions) {
128
+ def.parseTypeDefinition();
129
+
130
+ if (def.type_class_constructor) {
131
+ new_value = true;
132
+ break;
133
+ }
134
+ }
135
+ }
136
+ }
137
+
138
+ return new_value;
139
+ });
140
+
106
141
  /**
107
142
  * Get the complete section identifier
108
143
  *
@@ -364,7 +399,7 @@ PathDefinition.setMethod(function getParametersObject(values, info_type) {
364
399
  *
365
400
  * @author Jelle De Loecker <jelle@develry.be>
366
401
  * @since 1.0.0
367
- * @version 1.1.0
402
+ * @version 1.3.0
368
403
  *
369
404
  * @param {String} path
370
405
  *
@@ -372,15 +407,7 @@ PathDefinition.setMethod(function getParametersObject(values, info_type) {
372
407
  */
373
408
  PathDefinition.setMethod(function test(path, conduit) {
374
409
 
375
- var that = this,
376
- reject,
377
- values,
378
- result,
379
- pledge,
380
- tasks;
381
-
382
- // See if it matches & get the values
383
- values = this.regex.exec(path);
410
+ const values = this.regex.exec(path);
384
411
 
385
412
  if (!values) {
386
413
  return null;
@@ -389,133 +416,62 @@ PathDefinition.setMethod(function test(path, conduit) {
389
416
  // Remove the first part of the value, it's just the match
390
417
  values.shift();
391
418
 
392
- // Create a new array
393
- result = new Array(values.length);
394
- tasks = [];
419
+ let result = new Array(values.length),
420
+ do_await = false;
395
421
 
396
422
  // Iterate over the rest of the found values
397
- values.forEach(function eachValue(value, index) {
398
-
399
- var type_check,
400
- TypeChecker,
401
- token,
402
- entry;
403
-
404
- if (reject) {
405
- return;
406
- }
407
-
408
- token = that.key_tokens[index] || {};
409
-
410
- value = RURL.decodeUriSegment(value);
411
-
412
- entry = {
413
- name : token.name,
414
- value : value,
415
- original_value : value
416
- };
423
+ for (let index = 0; index < values.length; index++) {
417
424
 
418
- if (token.typedef) {
419
- type_check = that.checkType(value, token.typedef, token, conduit);
425
+ let value = RURL.decodeUriSegment(values[index]);
426
+ let param_def = this.param_definitions?.[index];
420
427
 
421
- if (typeof type_check === 'undefined') {
422
- reject = true;
423
- return;
424
- }
428
+ // There will be no param definition for regex-style matching
429
+ if (!param_def) {
430
+ let token = this.key_tokens[index] || {};
425
431
 
426
- entry.original_value = value;
427
- entry.value = type_check;
432
+ result[index] = {
433
+ name : token.name,
434
+ value : value,
435
+ original_value : value
436
+ };
428
437
 
429
- if (type_check && type_check.then) {
430
- tasks.push(function waitForCheck(next) {
431
- type_check.then(function gotValue(value) {
438
+ continue;
439
+ }
432
440
 
433
- if (value == null) {
434
- // Returning with an error still fucks shit up
435
- //return next(new Error('Path definition type check returned null'));
436
- reject = true;
437
- return next();
438
- }
441
+ let entry = param_def.parsePathValue(value, conduit);
439
442
 
440
- entry.value = value;
441
- next();
442
- });
443
+ // Return early when one of the checks fail
444
+ if (!entry) {
445
+ return null;
446
+ }
443
447
 
444
- type_check.catch(function onError(err) {
445
- next(err);
446
- });
447
- });
448
- }
448
+ if (Pledge.isThenable(entry)) {
449
+ do_await = true;
450
+ } else if (entry.rejected) {
451
+ return null;
449
452
  }
450
453
 
451
454
  result[index] = entry;
452
- });
453
-
454
- // Something made reject truthy,
455
- // so return null
456
- if (reject) {
457
- return null;
458
455
  }
459
456
 
460
- // If there are tasks, return the pledge
461
- if (tasks.length) {
462
- pledge = Function.parallel(tasks, function done(err) {
457
+ if (do_await) {
458
+
459
+ return Function.parallel(result, (err, result) => {
463
460
 
464
461
  if (err) {
465
462
  return null;
466
463
  }
467
464
 
468
- if (reject) {
469
- return null;
465
+ let i;
466
+
467
+ for (i = 0; i < result.length; i++) {
468
+ if (result[i]?.rejected) {
469
+ return null;
470
+ }
470
471
  }
471
472
 
472
473
  return result;
473
474
  });
474
-
475
- return pledge;
476
- }
477
-
478
- return result;
479
- });
480
-
481
- /**
482
- * Get the type checker
483
- *
484
- * @author Jelle De Loecker <jelle@develry.be>
485
- * @since 1.0.0
486
- * @version 1.1.0
487
- *
488
- * @param {String|Array} typedef
489
- *
490
- * @return {Function}
491
- */
492
- PathDefinition.setMethod(function checkType(value, typedef, token, conduit) {
493
-
494
- var class_name,
495
- field_name,
496
- TypeClass,
497
- is_string = typeof typedef === 'string',
498
- result,
499
- Model;
500
-
501
- if (is_string && PathDefinition.typedefs[typedef]) {
502
- result = PathDefinition.typedefs[typedef](value, token.name, conduit);
503
- } else {
504
-
505
- Model = Blast.Classes.Alchemy.Model || Blast.Classes.Alchemy.Client.Model;
506
-
507
- if (is_string) {
508
- class_name = typedef;
509
- } else {
510
- class_name = typedef[0];
511
- field_name = typedef[1];
512
- }
513
-
514
- TypeClass = Object.path(Model, class_name) || Object.path(Blast.Classes.Alchemy, class_name) || Object.path(Blast.Classes, class_name);
515
-
516
- if (TypeClass && TypeClass.checkPathValue) {
517
- result = TypeClass.checkPathValue(value, token.name, field_name, conduit);
518
- }
519
475
  }
520
476
 
521
477
  return result;