alchemymvc 1.3.20 → 1.3.22

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.
@@ -49,7 +49,7 @@ Model.constitute(function setModelName() {
49
49
  *
50
50
  * @author Jelle De Loecker <jelle@elevenways.be>
51
51
  * @since 1.2.5
52
- * @version 1.2.5
52
+ * @version 1.3.22
53
53
  */
54
54
  Model.mapEventToMethod({
55
55
  saved : 'afterSave',
@@ -58,6 +58,7 @@ Model.mapEventToMethod({
58
58
  associated : 'afterAssociated',
59
59
  found : 'afterData',
60
60
  foundDocuments : 'afterFind',
61
+ removed : 'afterRemove',
61
62
  });
62
63
 
63
64
  /**
@@ -629,7 +630,7 @@ Model.setMethod(function getAliasModel(alias) {
629
630
  *
630
631
  * @author Jelle De Loecker <jelle@elevenways.be>
631
632
  * @since 0.0.1
632
- * @version 1.3.8
633
+ * @version 1.3.21
633
634
  *
634
635
  * @param {String} type The type of find (first, all)
635
636
  * @param {Criteria} criteria The criteria object
@@ -797,6 +798,27 @@ Model.setMethod(function find(type, criteria, callback) {
797
798
  next();
798
799
  }
799
800
  });
801
+ }, function recomputeAfterFind(next) {
802
+
803
+ if (!that.schema.has_recompute_after_find || criteria.options.document === false) {
804
+ return next();
805
+ }
806
+
807
+ let tasks = [];
808
+
809
+ for (let document of records) {
810
+ tasks.push((next) => {
811
+ let promise = document.recomputeValues();
812
+
813
+ if (!promise) {
814
+ return next();
815
+ }
816
+
817
+ Pledge.done(promise, next);
818
+ });
819
+ }
820
+
821
+ Function.parallel(8, tasks, next);
800
822
  }, function done(err) {
801
823
 
802
824
  var i;
@@ -1773,7 +1795,7 @@ Model.setMethod(function compose(data, options) {
1773
1795
  *
1774
1796
  * @author Jelle De Loecker <jelle@elevenways.be>
1775
1797
  * @since 0.2.0
1776
- * @version 1.3.20
1798
+ * @version 1.3.21
1777
1799
  *
1778
1800
  * @param {Document} document
1779
1801
  * @param {Object} options
@@ -1786,10 +1808,17 @@ Model.setMethod(function createRecord(document, options, callback) {
1786
1808
  // Normalize & clone the data, set default values, ...
1787
1809
  let data = this.compose(document, options);
1788
1810
 
1789
- // Turn it into a new document
1790
- document = this.createDocument(data);
1811
+ document = createDocumentForSaving(this, document, data);
1812
+
1813
+ Function.series(function recompute(next) {
1814
+ let result = document.recomputeValues();
1791
1815
 
1792
- Function.series(function doBeforeValidate(next) {
1816
+ if (result) {
1817
+ result.done(next);
1818
+ } else {
1819
+ next();
1820
+ }
1821
+ }, function doBeforeValidate(next) {
1793
1822
  that.callOrNext('beforeValidate', [document, options], next);
1794
1823
  }, function validate(next) {
1795
1824
 
@@ -1823,12 +1852,35 @@ Model.setMethod(function createRecord(document, options, callback) {
1823
1852
  })
1824
1853
  });
1825
1854
 
1855
+ /**
1856
+ * Create a document used for saving
1857
+ *
1858
+ * @author Jelle De Loecker <jelle@elevenways.be>
1859
+ * @since 1.3.21
1860
+ * @version 1.3.21
1861
+ *
1862
+ * @param {Model} model
1863
+ * @param {Document|Object} original_input
1864
+ * @param {Object} data
1865
+ */
1866
+ function createDocumentForSaving(model, original_input, data) {
1867
+
1868
+ // Turn it into a new document
1869
+ let document = model.createDocument(data);
1870
+
1871
+ if (original_input?.$attributes.original_record) {
1872
+ document.$attributes.original_record = original_input.$attributes.original_record;
1873
+ }
1874
+
1875
+ return document;
1876
+ }
1877
+
1826
1878
  /**
1827
1879
  * Update a record in the database
1828
1880
  *
1829
1881
  * @author Jelle De Loecker <jelle@elevenways.be>
1830
1882
  * @since 0.2.0
1831
- * @version 1.3.20
1883
+ * @version 1.3.21
1832
1884
  *
1833
1885
  * @param {Document} document
1834
1886
  * @param {Object} options
@@ -1842,9 +1894,17 @@ Model.setMethod(function updateRecord(document, options, callback) {
1842
1894
  let data = this.compose(document, Object.assign({update: true}, options));
1843
1895
 
1844
1896
  // Turn it into a new document
1845
- document = this.createDocument(data);
1897
+ document = createDocumentForSaving(this, document, data);
1898
+
1899
+ Function.series(function recompute(next) {
1900
+ let result = document.recomputeValues();
1846
1901
 
1847
- Function.series(function doBeforeValidate(next) {
1902
+ if (result) {
1903
+ result.done(next);
1904
+ } else {
1905
+ next();
1906
+ }
1907
+ }, function doBeforeValidate(next) {
1848
1908
  that.callOrNext('beforeValidate', [document, options], next);
1849
1909
  }, function validate(next) {
1850
1910
 
@@ -1578,7 +1578,7 @@ Conduit.setMethod(function redirect(status, options) {
1578
1578
  *
1579
1579
  * @author Jelle De Loecker <jelle@elevenways.be>
1580
1580
  * @since 0.2.0
1581
- * @version 1.3.16
1581
+ * @version 1.3.21
1582
1582
  *
1583
1583
  * @param {Nulber} status Response statuscode
1584
1584
  * @param {Error} message Optional error to send
@@ -1613,7 +1613,7 @@ Conduit.setMethod(function error(status, message, print_error) {
1613
1613
  if (alchemy.settings.environment == 'dev') {
1614
1614
  print_dev = true;
1615
1615
 
1616
- if (is_400 && this.original_path.indexOf('.js.map') > -1) {
1616
+ if (is_400 && this.original_path && this.original_path.indexOf('.js.map') > -1) {
1617
1617
  print_dev = false;
1618
1618
  print_error = false;
1619
1619
  }
@@ -1806,6 +1806,10 @@ Conduit.setMethod(function end(message) {
1806
1806
 
1807
1807
  if (typeof message !== 'string') {
1808
1808
 
1809
+ if (message && message instanceof Classes.Stream.Stream) {
1810
+ return this._endWithStream(message);
1811
+ }
1812
+
1809
1813
  // Use regular JSON if DRY has been disabled in settings
1810
1814
  if (alchemy.settings.json_dry_response === false || this.json_dry === false) {
1811
1815
  json_type = 'json';
@@ -1878,12 +1882,29 @@ Conduit.setMethod(function end(message) {
1878
1882
  this._end(message, 'utf-8');
1879
1883
  });
1880
1884
 
1885
+ /**
1886
+ * End with a stream
1887
+ *
1888
+ * @author Jelle De Loecker <jelle@elevenways.be>
1889
+ * @since 1.3.22
1890
+ * @version 1.3.22
1891
+ */
1892
+ Conduit.setMethod(function _endWithStream(stream) {
1893
+
1894
+ this.ended = new Date();
1895
+ this.emit('ending');
1896
+
1897
+ this.flushHeaders();
1898
+
1899
+ stream.pipe(this.response);
1900
+ });
1901
+
1881
1902
  /**
1882
1903
  * Call the actual end method
1883
1904
  *
1884
1905
  * @author Jelle De Loecker <jelle@elevenways.be>
1885
1906
  * @since 0.2.0
1886
- * @version 1.3.14
1907
+ * @version 1.3.22
1887
1908
  */
1888
1909
  Conduit.setMethod(function _end(message, encoding = 'utf-8') {
1889
1910
 
@@ -1906,18 +1927,42 @@ Conduit.setMethod(function _end(message, encoding = 'utf-8') {
1906
1927
  return;
1907
1928
  }
1908
1929
 
1909
- let headers = [],
1910
- value,
1911
- key;
1930
+ // Set the content-length if it hasn't been set yet
1931
+ if (arguments.length > 0 && !this.response_headers['content-length']) {
1932
+ this.response_headers['content-length'] = Buffer.byteLength(message);
1933
+ }
1934
+
1935
+ this.flushHeaders();
1936
+
1937
+ if (arguments.length === 0) {
1938
+ return this.response.end();
1939
+ }
1940
+
1941
+ // End the response
1942
+ return this.response.end(message, encoding);
1943
+ });
1944
+
1945
+ /**
1946
+ * Flush the headers
1947
+ *
1948
+ * @author Jelle De Loecker <jelle@elevenways.be>
1949
+ * @since 1.3.22
1950
+ * @version 1.3.22
1951
+ */
1952
+ Conduit.setMethod(function flushHeaders() {
1953
+
1954
+ if (this._flushed) {
1955
+ return;
1956
+ }
1957
+
1958
+ this._flushed = true;
1912
1959
 
1913
1960
  if (this.status) {
1914
1961
  this.response.statusCode = this.status;
1915
1962
  }
1916
1963
 
1917
- // Set the content-length if it hasn't been set yet
1918
- if (arguments.length > 0 && !this.response_headers['content-length']) {
1919
- this.response_headers['content-length'] = Buffer.byteLength(message);
1920
- }
1964
+ let value,
1965
+ key;
1921
1966
 
1922
1967
  for (key in this.response_headers) {
1923
1968
  value = this.response_headers[key];
@@ -1935,13 +1980,6 @@ Conduit.setMethod(function _end(message, encoding = 'utf-8') {
1935
1980
 
1936
1981
  // Write the actual headers
1937
1982
  this.response.writeHead(this.status);
1938
-
1939
- if (arguments.length === 0) {
1940
- return this.response.end();
1941
- }
1942
-
1943
- // End the response
1944
- return this.response.end(message, encoding);
1945
1983
  });
1946
1984
 
1947
1985
  /**
@@ -178,6 +178,33 @@ Document.setStatic(function unDry(obj, cloned) {
178
178
  return result;
179
179
  });
180
180
 
181
+ /**
182
+ * Set the getter for this computed field
183
+ *
184
+ * @author Jelle De Loecker <jelle@elevenways.be>
185
+ * @since 1.3.21
186
+ * @version 1.3.21
187
+ *
188
+ * @param {String} name Name of the property
189
+ * @param {Function} getter Optional getter function
190
+ * @param {Function} setter Optional setter function
191
+ */
192
+ Document.setStatic(function setComputedFieldGetter(name) {
193
+ this.setProperty(name, function getComputedFieldValue() {
194
+ this.recomputeFieldIfNecessary(name);
195
+ return this.$main[name];
196
+ }, function setComputedFieldValue(value) {
197
+
198
+ const field = this.$model.schema.getField(name);
199
+
200
+ if (field?.options?.allow_manual_set) {
201
+ return this.$main[name] = value;
202
+ }
203
+
204
+ console.error('Can not set computed field "' + name + '" to', value);
205
+ });
206
+ });
207
+
181
208
  /**
182
209
  * Set the getter for this field
183
210
  *
@@ -207,6 +207,116 @@ Field.setProperty(function is_array() {
207
207
  return !!result;
208
208
  });
209
209
 
210
+ /**
211
+ * Does this field have a computed value?
212
+ *
213
+ * @author Jelle De Loecker <jelle@elevenways.be>
214
+ * @since 1.3.21
215
+ * @version 1.3.21
216
+ *
217
+ * @type {Boolean}
218
+ */
219
+ Field.setProperty(function is_computed() {
220
+ return !!this.options.is_computed;
221
+ });
222
+
223
+ /**
224
+ * Get the required related fields
225
+ *
226
+ * @author Jelle De Loecker <jelle@elevenways.be>
227
+ * @since 1.3.21
228
+ * @version 1.3.21
229
+ *
230
+ * @type {Field[]}
231
+ */
232
+ Field.enforceProperty(function required_fields(new_value) {
233
+
234
+ if (!new_value) {
235
+ new_value = resolveFieldPaths(this, this.options.required_fields);
236
+ }
237
+
238
+ return new_value;
239
+ });
240
+
241
+ /**
242
+ * Get the optional related fields
243
+ *
244
+ * @author Jelle De Loecker <jelle@elevenways.be>
245
+ * @since 1.3.21
246
+ * @version 1.3.21
247
+ *
248
+ * @type {Field[]}
249
+ */
250
+ Field.enforceProperty(function optional_fields(new_value) {
251
+
252
+ if (!new_value) {
253
+ new_value = resolveFieldPaths(this, this.options.optional_fields);
254
+ }
255
+
256
+ return new_value;
257
+ });
258
+
259
+ /**
260
+ * Get all the dependency fields
261
+ *
262
+ * @author Jelle De Loecker <jelle@elevenways.be>
263
+ * @since 1.3.21
264
+ * @version 1.3.21
265
+ *
266
+ * @type {Field[]}
267
+ */
268
+ Field.enforceProperty(function dependency_fields(new_value) {
269
+
270
+ if (!new_value) {
271
+ let required_fields = this.required_fields,
272
+ optional_fields = this.optional_fields;
273
+
274
+ new_value = [];
275
+
276
+ if (required_fields) {
277
+ new_value = new_value.concat(required_fields);
278
+ }
279
+
280
+ if (optional_fields) {
281
+ new_value = new_value.concat(optional_fields);
282
+ }
283
+ }
284
+
285
+ return new_value;
286
+ });
287
+
288
+ /**
289
+ * Resolve a field path to a field instance
290
+ *
291
+ * @author Jelle De Loecker <jelle@elevenways.be>
292
+ * @since 1.3.21
293
+ * @version 1.3.21
294
+ */
295
+ function resolveFieldPaths(field, field_paths) {
296
+
297
+ let result = [],
298
+ i;
299
+
300
+ if (!field_paths) {
301
+ return result;
302
+ }
303
+
304
+ for (i = 0; i < field_paths.length; i++) {
305
+
306
+ if (!field.parent_schema) {
307
+ continue
308
+ }
309
+
310
+ let other_field = field.parent_schema.getField(field_paths[i]);
311
+
312
+ if (other_field && result.indexOf(other_field) == -1) {
313
+ result.push(other_field);
314
+ }
315
+ }
316
+
317
+ return result;
318
+ }
319
+
210
320
  /**
211
321
  * The datasource this field is used in
212
322
  *
@@ -338,4 +338,29 @@ File.setMethod(function readString(encoding) {
338
338
  }
339
339
 
340
340
  return this.read({encoding: encoding});
341
+ });
342
+
343
+ /**
344
+ * Write the given data to the file
345
+ *
346
+ * @author Jelle De Loecker <jelle@elevenways.be>
347
+ * @since 1.3.22
348
+ * @version 1.3.22
349
+ */
350
+ File.setMethod(function overwrite(contents, options = {}) {
351
+
352
+ let pledge = new Pledge();
353
+
354
+ options.flag = 'w';
355
+
356
+ fs.writeFile(this.path, contents, options, function done(err) {
357
+
358
+ if (err) {
359
+ return pledge.reject(err);
360
+ }
361
+
362
+ pledge.resolve();
363
+ });
364
+
365
+ return pledge;
341
366
  });
@@ -366,24 +366,49 @@ Model.setStatic(async function checkPathValue(value, name, field_name, conduit)
366
366
  return result;
367
367
  });
368
368
 
369
+ /**
370
+ * Add a computed field to this model's schema
371
+ *
372
+ * @author Jelle De Loecker <jelle@elevenways.be>
373
+ * @since 1.3.21
374
+ * @version 1.3.21
375
+ *
376
+ * @return {Alchemy.Field}
377
+ */
378
+ Model.setStatic(function addComputedField(name, type, options) {
379
+ let is_new = !this.schema.has(name);
380
+
381
+ // Add it to the schema
382
+ let field = this.schema.addComputedField(name, type, options);
383
+
384
+ if (is_new) {
385
+ // Add it to the Document class
386
+ this.Document.setComputedFieldGetter(name);
387
+
388
+ // False means it should not be set on the server implementation
389
+ // (because that's where it's coming from)
390
+ // Yes, this also sets private fields on the server-side client document.
391
+ this.ClientDocument.setComputedFieldGetter(name, null, null, false);
392
+ }
393
+
394
+ return field;
395
+ });
396
+
369
397
  /**
370
398
  * Add a field to this model's schema
371
399
  *
372
400
  * @author Jelle De Loecker <jelle@develry.be>
373
401
  * @since 0.2.0
374
- * @version 1.2.4
402
+ * @version 1.2.21
375
403
  *
376
404
  * @return {Alchemy.Field}
377
405
  */
378
406
  Model.setStatic(function addField(name, type, options) {
379
407
 
380
- var field,
381
- is_new;
382
-
383
- is_new = !this.schema.has(name);
408
+ let is_new = !this.schema.has(name);
384
409
 
385
410
  // Add it to the schema
386
- field = this.schema.addField(name, type, options);
411
+ let field = this.schema.addField(name, type, options);
387
412
 
388
413
  if (is_new) {
389
414
  // Add it to the Document class
@@ -1262,7 +1287,7 @@ Model.setMethod(async function executeMongoPipeline(pipeline) {
1262
1287
  *
1263
1288
  * @author Jelle De Loecker <jelle@develry.be>
1264
1289
  * @since 0.0.1
1265
- * @version 1.0.7
1290
+ * @version 1.3.22
1266
1291
  *
1267
1292
  * @param {String} id The object id
1268
1293
  * @param {Function} callback
@@ -1288,17 +1313,51 @@ Model.setMethod(function remove(id, callback) {
1288
1313
  id = String(id);
1289
1314
  }
1290
1315
 
1291
- let query = {};
1292
- query[this.primary_key] = id;
1316
+ let query = {
1317
+ [this.primary_key] : id,
1318
+ };
1319
+
1320
+ let has_remove_events = typeof this.beforeRemove == 'function' || typeof this.afterRemove == 'function' || this.listeners('removed').length;
1321
+ let doc;
1322
+ let tasks = [];
1293
1323
 
1294
- this.datasource.remove(this, query, {}, function afterRemove(err, result) {
1324
+ if (has_remove_events) {
1325
+
1326
+ // Get the actual document
1327
+ tasks.push(async next => {
1328
+ doc = await this.findByPk(id);
1329
+
1330
+ if (!doc) {
1331
+ return next(new Error('Unable to find document with id ' + id));
1332
+ }
1295
1333
 
1296
- if (err != null) {
1297
- return pledge.reject(err);
1334
+ next();
1335
+ });
1336
+
1337
+ tasks.push(next => {
1338
+ this.callOrNext('beforeRemove', [doc], next);
1339
+ });
1340
+ }
1341
+
1342
+ Function.series(tasks, function done(err) {
1343
+
1344
+ if (err) {
1345
+ pledge.reject(err);
1346
+ return;
1298
1347
  }
1299
1348
 
1300
- that.emit('removed', result, {}, false, function afterRemovedEvent() {
1301
- pledge.resolve(result);
1349
+ that.datasource.remove(that, query, {}, function afterRemove(err, result) {
1350
+
1351
+ if (err != null) {
1352
+ return pledge.reject(err);
1353
+ }
1354
+
1355
+ if (has_remove_events) {
1356
+ that.issueEvent('removed', [doc, result], () => pledge.resolve(result));
1357
+ } else {
1358
+ pledge.resolve(result);
1359
+ }
1360
+
1302
1361
  });
1303
1362
  });
1304
1363
 
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * @author Jelle De Loecker <jelle@elevenways.be>
5
5
  * @since 0.2.0
6
- * @version 1.3.16
6
+ * @version 1.3.21
7
7
  */
8
8
  const Route = Function.inherits('Alchemy.Base', function Route(router, paths, options) {
9
9
 
@@ -62,6 +62,9 @@ const Route = Function.inherits('Alchemy.Base', function Route(router, paths, op
62
62
  // All routes can be postponed by default
63
63
  this.can_be_postponed = true;
64
64
 
65
+ // Possible cache instructions
66
+ this.cache = null;
67
+
65
68
  this.setPaths(paths);
66
69
  });
67
70
 
@@ -767,7 +767,7 @@ RouterClass.setMethod(function setOption(name, value) {
767
767
  *
768
768
  * @author Jelle De Loecker <jelle@elevenways.be>
769
769
  * @since 0.2.0
770
- * @version 1.3.16
770
+ * @version 1.3.21
771
771
  *
772
772
  * @param {Object} args
773
773
  * @param {String} args.name Optional route name
@@ -819,6 +819,7 @@ RouterClass.setMethod(function add(args) {
819
819
  route.setBreadcrumb(args.breadcrumb);
820
820
  route.sitemap = args.sitemap;
821
821
  route.title = args.title;
822
+ route.cache = args.cache;
822
823
 
823
824
  if (args.visible_location != null) {
824
825
  route.visible_location = args.visible_location;
@@ -1167,7 +1168,7 @@ RouterClass.setMethod(function getOptions(result) {
1167
1168
  *
1168
1169
  * @author Jelle De Loecker <jelle@elevenways.be>
1169
1170
  * @since 0.2.0
1170
- * @version 1.3.16
1171
+ * @version 1.3.21
1171
1172
  *
1172
1173
  * @param {Object} result Optional object to store sectioned results in
1173
1174
  *
@@ -1219,6 +1220,7 @@ RouterClass.setMethod(function getRoutes(result) {
1219
1220
  is_system_route : route.is_system_route,
1220
1221
  schema : route.schema,
1221
1222
  param_definitions : route.param_definitions,
1223
+ cache : route.cache,
1222
1224
  };
1223
1225
  }
1224
1226