alchemymvc 1.3.0 → 1.3.2

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.
@@ -60,13 +60,13 @@ Validator.setMethod(function toDry() {
60
60
  *
61
61
  * @author Jelle De Loecker <jelle@elevenways.be>
62
62
  * @since 1.1.0
63
- * @version 1.1.0
63
+ * @version 1.3.1
64
64
  *
65
65
  * @param {FieldValue} fv The FieldValue instance
66
66
  *
67
67
  * @return {String}
68
68
  */
69
- Validator.setMethod(function getInvalidFieldMessage(fv) {
69
+ Validator.setTypedMethod([Types.Alchemy.FieldValue], function getInvalidFieldMessage(fv) {
70
70
 
71
71
  let field = fv.field,
72
72
  value = fv.value;
@@ -105,20 +105,52 @@ Validator.setMethod(function createFieldViolation(fv) {
105
105
  return result;
106
106
  });
107
107
 
108
+ /**
109
+ * Validate a value
110
+ *
111
+ * @author Jelle De Loecker <jelle@elevenways.be>
112
+ * @since 1.3.1
113
+ * @version 1.3.1
114
+ *
115
+ * @param {Field} field
116
+ * @param {*} value
117
+ *
118
+ * @return {Promise<Violation|undefined>}
119
+ */
120
+ Validator.setTypedMethod([Types.Alchemy.Field, Types.Any], function validateFieldValue(field, value) {
121
+ return this.validateFieldValue(field, field.path, value);
122
+ });
123
+
124
+ /**
125
+ * Validate a value
126
+ *
127
+ * @author Jelle De Loecker <jelle@elevenways.be>
128
+ * @since 1.3.1
129
+ * @version 1.3.1
130
+ *
131
+ * @param {Field} field
132
+ * @param {String} path
133
+ * @param {*} value
134
+ *
135
+ * @return {Promise<Violation|undefined>}
136
+ */
137
+ Validator.setTypedMethod([Types.Alchemy.Field, Types.String, Types.Any], function validateFieldValue(field, path, value) {
138
+ let fv = new Classes.Alchemy.FieldValue(field, path, value);
139
+ return this.validateFieldValue(fv);
140
+ });
141
+
108
142
  /**
109
143
  * Validate a value
110
144
  *
111
145
  * @author Jelle De Loecker <jelle@elevenways.be>
112
146
  * @since 1.1.0
113
- * @version 1.1.4
147
+ * @version 1.3.1
114
148
  *
115
149
  * @param {FieldValue[]} field_values The FieldValue instance(s)
116
150
  *
117
151
  * @return {Promise<Violation|undefined>}
118
152
  */
119
- Validator.setMethod(async function validateFieldValue(field_values) {
120
-
121
- field_values = Array.cast(field_values);
153
+ Validator.setTypedMethod([Types.Alchemy.FieldValue.array()], async function validateFieldValue(field_values) {
122
154
 
123
155
  let violation,
124
156
  passes,
@@ -10,9 +10,7 @@
10
10
  *
11
11
  * @param {Object} options
12
12
  */
13
- var NotEmpty = Function.inherits('Alchemy.Validator', function NotEmpty(options) {
14
- NotEmpty.super.call(this, options);
15
- });
13
+ const NotEmpty = Function.inherits('Alchemy.Validator', 'NotEmpty');
16
14
 
17
15
  /**
18
16
  * The message to use inside errors
package/lib/app/routes.js CHANGED
@@ -1,3 +1,9 @@
1
1
  Router.add(['get', 'post'], 'APIResource', '/api/{action}', 'Api#{action}');
2
2
  Router.get('AlchemyInfo', '/alchemy-info', 'AlchemyInfo#info');
3
- Router.get('Postponed', '/alchemy/postponed/{id}', 'AlchemyInfo#postponed');
3
+
4
+ Router.POSTPONED_ROUTE = Router.add({
5
+ name : 'AlchemyInfo#postponed',
6
+ paths : '/alchemy/postponed/{id}',
7
+ methods : ['get'],
8
+ can_be_postponed : false,
9
+ });
@@ -1231,19 +1231,67 @@ Conduit.setMethod(function callHandler() {
1231
1231
  this.route.callHandler(this);
1232
1232
  });
1233
1233
 
1234
+ /**
1235
+ * Put this request in a queue
1236
+ *
1237
+ * @author Jelle De Loecker <jelle@elevenways.be>
1238
+ * @since 1.3.1
1239
+ * @version 1.3.1
1240
+ *
1241
+ * @param {Object} options Options or url
1242
+ */
1243
+ Conduit.setMethod(function postponeAndQueue(options) {
1244
+
1245
+ if (!options) {
1246
+ options = {};
1247
+ }
1248
+
1249
+ const postponement = this.postponeRequest({
1250
+ put_in_queue : true,
1251
+ });
1252
+
1253
+ return postponement;
1254
+ });
1255
+
1256
+ /**
1257
+ * Postpone the response and the request
1258
+ *
1259
+ * This does not stop the current request from processing.
1260
+ *
1261
+ * @author Jelle De Loecker <jelle@elevenways.be>
1262
+ * @since 1.3.1
1263
+ * @version 1.3.1
1264
+ *
1265
+ * @param {Number|Object} options Options or time to wait
1266
+ *
1267
+ * @return {Alchemy.Conduit.Postponement}
1268
+ */
1269
+ Conduit.setMethod(function postponeRequest(options) {
1270
+
1271
+ let postponement = this.postponeResponse(options);
1272
+
1273
+ this.afterOnce('get-postponed-response', () => {
1274
+ this.callMiddleware();
1275
+ });
1276
+
1277
+ return postponement;
1278
+ });
1279
+
1234
1280
  /**
1235
1281
  * End the current request with a 202 status
1236
- * and tell the client to look at another url later
1282
+ * and tell the client to look at another url later.
1237
1283
  *
1238
- * @author Jelle De Loecker <jelle@develry.be>
1284
+ * This does not stop the current request from processing.
1285
+ *
1286
+ * @author Jelle De Loecker <jelle@elevenways.be>
1239
1287
  * @since 1.1.0
1240
- * @version 1.2.5
1288
+ * @version 1.3.1
1241
1289
  *
1242
- * @param {String|Object} options Options or url
1290
+ * @param {Number|Object} options Options or time to wait
1291
+ *
1292
+ * @return {Alchemy.Conduit.Postponement}
1243
1293
  */
1244
- Conduit.setMethod(function postpone(options) {
1245
-
1246
- let session = this.getSession();
1294
+ Conduit.setMethod(function postponeResponse(options) {
1247
1295
 
1248
1296
  if (typeof options == 'number') {
1249
1297
  options = {
@@ -1253,36 +1301,55 @@ Conduit.setMethod(function postpone(options) {
1253
1301
  options = {};
1254
1302
  }
1255
1303
 
1256
- // Make sure the scene id exists
1257
- this.createScene();
1304
+ return this._postpone(options);
1305
+ });
1258
1306
 
1259
- // Already set the cookies
1260
- if (this.new_cookie_header.length) {
1261
- this.response.setHeader('set-cookie', this.new_cookie_header);
1262
- }
1307
+ /**
1308
+ * Handle the postponement
1309
+ *
1310
+ * @author Jelle De Loecker <jelle@elevenways.be>
1311
+ * @since 1.3.1
1312
+ * @version 1.3.1
1313
+ *
1314
+ * @param {Object} options
1315
+ *
1316
+ * @return {Alchemy.Conduit.Postponement}
1317
+ */
1318
+ Conduit.setMethod(function _postpone(options) {
1263
1319
 
1264
- let postponed_id = session.postpone(this),
1265
- url = '/alchemy/postponed/' + postponed_id;
1320
+ let session = this.getSession();
1266
1321
 
1267
- // Set the location header where the client should look at later
1268
- this.response.setHeader('Location', url);
1269
- this.response.setHeader('Content-Type', 'text/html');
1322
+ let postponement = session.getExistingPostponement(this);
1270
1323
 
1271
- if (options.expected_duration) {
1272
- this.response.setHeader('Expected-Duration', Number(options.expected_duration / 1000).toFixed(2));
1324
+ if (postponement) {
1325
+ return postponement.showPostponementMessage(this);
1273
1326
  }
1274
1327
 
1275
- // Write the headers & 202 status
1276
- this.response.writeHead(202);
1328
+ let response = this.response;
1277
1329
 
1278
- // End the response
1279
- this.response.end('The response has been postponed, you can find it at <a href="' + url + '">' + url + '</a>');
1330
+ this.postponed_response = response;
1331
+
1332
+ // Make sure the scene id exists
1333
+ this.createScene();
1334
+
1335
+ postponement = session.postpone(this, options);
1336
+
1337
+ if (options.put_in_queue) {
1338
+ postponement.putInQueue();
1339
+ }
1340
+
1341
+ if (options.show_postponement_message !== false) {
1342
+ postponement.showPostponementMessage();
1343
+ }
1280
1344
 
1281
1345
  // Nullify the response
1282
1346
  this.response = null;
1283
1347
 
1284
1348
  // Set the original url
1285
- this.overrideResponseUrl(this.url)
1349
+ this.overrideResponseUrl(this.url);
1350
+
1351
+ // Return the postponement
1352
+ return postponement;
1286
1353
  });
1287
1354
 
1288
1355
  /**
@@ -1436,15 +1503,17 @@ Conduit.setMethod(function redirect(status, options) {
1436
1503
  /**
1437
1504
  * Respond with an error
1438
1505
  *
1439
- * @author Jelle De Loecker <jelle@develry.be>
1506
+ * @author Jelle De Loecker <jelle@elevenways.be>
1440
1507
  * @since 0.2.0
1441
- * @version 1.1.0
1508
+ * @version 1.3.1
1442
1509
  *
1443
1510
  * @param {Nulber} status Response statuscode
1444
1511
  * @param {Error} message Optional error to send
1445
- * @param {Boolean} printError Print the error, defaults to true
1512
+ * @param {Boolean} print_error Print the error, defaults to true
1446
1513
  */
1447
- Conduit.setMethod(function error(status, message, printError) {
1514
+ Conduit.setMethod(function error(status, message, print_error) {
1515
+
1516
+ let print_dev = false;
1448
1517
 
1449
1518
  if (status instanceof Classes.Alchemy.Error.HTTP) {
1450
1519
  message = status;
@@ -1466,14 +1535,33 @@ Conduit.setMethod(function error(status, message, printError) {
1466
1535
  message = error;
1467
1536
  }
1468
1537
 
1469
- let subject = 'Error found on ' + this.original_path + '';
1538
+ let is_400 = (status >= 400 && status <= 500);
1470
1539
 
1471
- if (printError === false) {
1472
- log.error(subject + ':\n' + message);
1473
- } else if (message instanceof Error) {
1474
- alchemy.printLog('error', [subject, String(message), message], {err: message, level: -2});
1475
- } else {
1476
- log.error(subject + ':\n' + message);
1540
+ if (alchemy.settings.environment == 'dev') {
1541
+ print_dev = true;
1542
+ }
1543
+
1544
+ if (print_error == null) {
1545
+ if (is_400) {
1546
+ print_error = false;
1547
+ } else {
1548
+ print_error = true;
1549
+ }
1550
+ }
1551
+
1552
+ if (print_dev) {
1553
+ let subject = 'Error found on ' + this.original_path + '';
1554
+ log.error(subject + ':\n' + message, this);
1555
+ } else if (print_error) {
1556
+ let subject = 'Error found on ' + this.original_path + '';
1557
+
1558
+ if (is_400) {
1559
+ log.error(subject + ':\n' + message);
1560
+ } else if (message instanceof Error) {
1561
+ alchemy.printLog('error', [subject, String(message), message], {err: message, level: -2});
1562
+ } else {
1563
+ log.error(subject + ':\n' + message);
1564
+ }
1477
1565
  }
1478
1566
 
1479
1567
  // Make sure the client doesn't expect compression
@@ -1497,7 +1585,12 @@ Conduit.setMethod(function error(status, message, printError) {
1497
1585
  } else {
1498
1586
  this.set('status', status);
1499
1587
  this.set('message', message);
1500
- this.render(['error/' + status, 'error/unknown']);
1588
+
1589
+ if (alchemy.isTooBusyForRequests()) {
1590
+ this._end(`Error ${status}:\n${message}`);
1591
+ } else {
1592
+ this.render(['error/' + status, 'error/unknown']);
1593
+ }
1501
1594
  }
1502
1595
  } else {
1503
1596
  // Requests for images or scripts just get a non-expensive string response
@@ -1710,9 +1803,9 @@ Conduit.setMethod(function end(message) {
1710
1803
  /**
1711
1804
  * Call the actual end method
1712
1805
  *
1713
- * @author Jelle De Loecker <jelle@develry.be>
1806
+ * @author Jelle De Loecker <jelle@elevenways.be>
1714
1807
  * @since 0.2.0
1715
- * @version 1.1.0
1808
+ * @version 1.3.1
1716
1809
  */
1717
1810
  Conduit.setMethod(function _end(message, encoding = 'utf-8') {
1718
1811
 
@@ -1728,6 +1821,8 @@ Conduit.setMethod(function _end(message, encoding = 'utf-8') {
1728
1821
 
1729
1822
  this._end_arguments = args;
1730
1823
 
1824
+ this.emit('after-postponed-end', args);
1825
+
1731
1826
  return;
1732
1827
  }
1733
1828
 
@@ -2120,9 +2215,9 @@ Conduit.setMethod(function _sendStream(stream, options) {
2120
2215
  /**
2121
2216
  * Create a session
2122
2217
  *
2123
- * @author Jelle De Loecker <jelle@develry.be>
2218
+ * @author Jelle De Loecker <jelle@elevenways.be>
2124
2219
  * @since 0.2.0
2125
- * @version 1.1.0
2220
+ * @version 1.3.1
2126
2221
  *
2127
2222
  * @param {Boolean} create Create a session if none exist
2128
2223
  *
@@ -2130,16 +2225,16 @@ Conduit.setMethod(function _sendStream(stream, options) {
2130
2225
  */
2131
2226
  Conduit.setMethod(function getSession(allow_create = true) {
2132
2227
 
2133
- var cookie_name,
2134
- fingerprint,
2135
- session_id,
2136
- session;
2137
-
2138
2228
  // Only do this once per request
2139
2229
  if (this.sessionData != null) {
2140
2230
  return this.sessionData;
2141
2231
  }
2142
2232
 
2233
+ let cookie_name,
2234
+ fingerprint,
2235
+ session_id,
2236
+ session;
2237
+
2143
2238
  // Set the name of the cookie (could change in the future)
2144
2239
  cookie_name = alchemy.settings.session_key || 'alchemy_sid';
2145
2240
 
@@ -2498,6 +2593,72 @@ Conduit.setMethod(function supports(feature) {
2498
2593
  return null;
2499
2594
  });
2500
2595
 
2596
+ /**
2597
+ * Should this request be delayed because the server is too busy?
2598
+ * This performs an early check, the controller might also check this later.
2599
+ * This is mainly so we can prevent Server-Side-Renders from happening
2600
+ * when the system is already overloaded.
2601
+ *
2602
+ * @author Jelle De Loecker <jelle@elevenways.be>
2603
+ * @since 1.3.1
2604
+ * @version 1.3.1
2605
+ *
2606
+ * @return {Boolean}
2607
+ */
2608
+ Conduit.setMethod(function shouldBePostponed() {
2609
+
2610
+ if (!alchemy.settings.postpone_requests_on_overload) {
2611
+ return false;
2612
+ }
2613
+
2614
+ const route = this.route;
2615
+
2616
+ // If no route is found, it can be allowed.
2617
+ // Most of the time this means it's a middleware-request
2618
+ // (like stylesheets, images, scripts, ...)
2619
+ // But also 404s (This can be delayed later by the controller)
2620
+ if (route == null) {
2621
+ return false;
2622
+ }
2623
+
2624
+ // Some routes can't even be postponed
2625
+ if (!route.can_be_postponed) {
2626
+ return false;
2627
+ }
2628
+
2629
+ // If alchemy isn't even too busy, it's obviously allowed
2630
+ if (!alchemy.isTooBusyForRequests()) {
2631
+ return false;
2632
+ }
2633
+
2634
+ // Some users have the permission to skip postponements
2635
+ if (this.hasPermission('alchemy.queue.skip')) {
2636
+ return false;
2637
+ }
2638
+
2639
+ let session = this.getSession(false);
2640
+
2641
+ if (session) {
2642
+
2643
+ // Do not postpone clients that have already been in the queue
2644
+ if (session.hasAlreadyQueued()) {
2645
+ return false;
2646
+ }
2647
+
2648
+ // Do not postpone clients that have already rendered something
2649
+ if (session.render_counter > 0 && session.is_active) {
2650
+ return false;
2651
+ }
2652
+ }
2653
+
2654
+ // AJAX requests are allowed a bit more
2655
+ if (this.ajax) {
2656
+ return alchemy.isTooBusyForAjax();
2657
+ }
2658
+
2659
+ return true;
2660
+ });
2661
+
2501
2662
  /**
2502
2663
  * Broadcast data to every connected user
2503
2664
  *
@@ -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);
@@ -189,7 +189,7 @@ Datasource.setMethod(function toDatasource(schema, data, callback) {
189
189
  if (!schema) {
190
190
 
191
191
  if (alchemy.settings.debug) {
192
- log.todo('Schema not found: not normalizing data', data);
192
+ alchemy.distinctProblem('schema-not-found', 'Schema not found: not normalizing data');
193
193
  }
194
194
 
195
195
  pledge = Pledge.resolve(data);
@@ -256,7 +256,7 @@ Datasource.setMethod(function toDatasource(schema, data, callback) {
256
256
  *
257
257
  * @author Jelle De Loecker <jelle@develry.be>
258
258
  * @since 0.2.0
259
- * @version 1.1.0
259
+ * @version 1.3.1
260
260
  *
261
261
  * @param {Schema|Model} schema
262
262
  * @param {Object} query
@@ -281,7 +281,7 @@ Datasource.setMethod(function toApp(schema, query, options, data, callback) {
281
281
  schema = this.getSchema(schema);
282
282
 
283
283
  if (schema == null) {
284
- log.todo('Schema not found: not unnormalizing data');
284
+ alchemy.distinctProblem('schema-not-found-unnormalize', 'Schema not found: not un-normalizing data');
285
285
 
286
286
  let pledge = Pledge.resolve(data);
287
287
  pledge.done(callback);
@@ -519,13 +519,22 @@ Datasource.setMethod(function read(model, criteria, callback) {
519
519
  return pledge.reject(err);
520
520
  }
521
521
 
522
+ if (criteria.options.return_raw_data) {
523
+ criteria.setOption('document', false);
524
+ return handleResults(null, results);
525
+ }
526
+
522
527
  tasks = results.map(function eachEntry(entry) {
523
528
  return function entryToApp(next) {
524
529
  that.toApp(model, criteria, {}, entry, next);
525
530
  };
526
531
  });
527
532
 
528
- 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) {
529
538
 
530
539
  if (err) {
531
540
  return pledge.reject(err);
@@ -549,9 +558,7 @@ Datasource.setMethod(function read(model, criteria, callback) {
549
558
  }
550
559
 
551
560
  pledge.resolve(result);
552
- });
553
-
554
- pledge._addProgressPledge(sub_pledge);
561
+ }
555
562
  });
556
563
 
557
564
  if (this.queryCache && model.cache && cache_pledge) {
@@ -301,16 +301,17 @@ Field.setStatic(function unDry(value) {
301
301
  *
302
302
  * @author Jelle De Loecker <jelle@develry.be>
303
303
  * @since 0.2.0
304
- * @version 1.1.0
304
+ * @version 1.3.1
305
305
  *
306
306
  * @return {Object}
307
307
  */
308
308
  Field.setMethod(function toDry() {
309
309
  return {
310
310
  value: {
311
+ bla: 1,
311
312
  schema : this.schema,
312
313
  name : this.name,
313
- options : this.options
314
+ options : this.getOptionsForDrying(),
314
315
  }
315
316
  };
316
317
  });
@@ -684,7 +685,7 @@ Field.setMethod(function _toDatasource(value, data, datasource, callback) {
684
685
  *
685
686
  * @author Jelle De Loecker <jelle@develry.be>
686
687
  * @since 0.2.0
687
- * @version 1.1.5
688
+ * @version 1.3.1
688
689
  *
689
690
  * @param {Object} values
690
691
  */
@@ -698,6 +699,10 @@ Field.setMethod(function regularToDatasource(value, data, datasource, callback)
698
699
  return this._toDatasource(value, data, datasource, callback);
699
700
  }
700
701
 
702
+ if (!value) {
703
+ return callback();
704
+ }
705
+
701
706
  // Arrayable fields need to process every value inside the array
702
707
  tasks = value.map(function eachValue(entry) {
703
708
  return function eachValueToDs(next) {
@@ -1033,6 +1038,19 @@ Field.setMethod(function getClientConfigOptions(wm) {
1033
1038
  return JSON.clone(this.options, 'toHawkejs', wm);
1034
1039
  });
1035
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
+
1036
1054
  /**
1037
1055
  * Get all the rules for this field
1038
1056
  *
@@ -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;