alchemymvc 1.3.0 → 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.
@@ -152,12 +152,16 @@ Schema.setProperty(function has_translatable_fields() {
152
152
  *
153
153
  * @author Jelle De Loecker <jelle@elevenways.be>
154
154
  * @since 1.1.3
155
- * @version 1.1.3
155
+ * @version 1.3.1
156
156
  *
157
157
  * @return {Object}
158
158
  */
159
- Schema.setMethod(function dryClone() {
160
- return this.clone();
159
+ Schema.setMethod(function dryClone(wm, custom_method) {
160
+
161
+ let obj = JSON.toDryObject(this),
162
+ cloned = JSON.undry(obj);
163
+
164
+ return cloned;
161
165
  });
162
166
 
163
167
  /**
@@ -166,16 +170,12 @@ Schema.setMethod(function dryClone() {
166
170
  *
167
171
  * @author Jelle De Loecker <jelle@elevenways.be>
168
172
  * @since 1.1.3
169
- * @version 1.1.3
173
+ * @version 1.3.1
170
174
  *
171
175
  * @return {Object}
172
176
  */
173
177
  Schema.setMethod(function clone() {
174
-
175
- let obj = JSON.toDryObject(this),
176
- cloned = JSON.undry(obj);
177
-
178
- return cloned;
178
+ return this.dryClone();
179
179
  });
180
180
 
181
181
  /**
@@ -183,7 +183,7 @@ Schema.setMethod(function clone() {
183
183
  *
184
184
  * @author Jelle De Loecker <jelle@develry.be>
185
185
  * @since 1.1.0
186
- * @version 1.3.0
186
+ * @version 1.3.1
187
187
  *
188
188
  * @return {Object}
189
189
  */
@@ -210,10 +210,6 @@ Schema.setMethod(function toDry() {
210
210
  value.rules.set(key, entry);
211
211
  }
212
212
 
213
- if (this.parent) {
214
- value.parent = this.parent;
215
- }
216
-
217
213
  if (this.model_name) {
218
214
  value.model_name = this.model_name;
219
215
 
@@ -224,24 +220,25 @@ Schema.setMethod(function toDry() {
224
220
 
225
221
  value.options = this.options;
226
222
 
223
+ let result = {value};
224
+
227
225
  // Get the sorted fields
228
- let options,
229
- fields = this.getSorted(false),
226
+ let fields = this.getSorted(false),
230
227
  field,
231
228
  i;
232
-
229
+
233
230
  for (i = 0; i < fields.length; i++) {
234
231
  field = fields[i];
235
232
 
236
233
  value.fields.push({
237
234
  name : field.name,
238
- options : field.options,
235
+ options : field.getOptionsForDrying(),
239
236
  class_name : field.constructor.name,
240
237
  namespace : field.constructor.namespace,
241
238
  });
242
239
  }
243
240
 
244
- return {value: value};
241
+ return result;
245
242
  });
246
243
 
247
244
  /**
@@ -5,7 +5,7 @@ var data_listeners = alchemy.shared('data_binding_listeners');
5
5
  *
6
6
  * @author Jelle De Loecker <jelle@develry.be>
7
7
  * @since 0.2.0
8
- * @version 1.1.0
8
+ * @version 1.3.1
9
9
  *
10
10
  * @param {Conduit} conduit The initializing conduit
11
11
  */
@@ -36,10 +36,19 @@ var Session = Function.inherits('Alchemy.Base', function ClientSession(conduit)
36
36
  this.data_listener_ids = {};
37
37
 
38
38
  // Postponed requests
39
- this.postponed = new Classes.Develry.Cache();
39
+ this.postponements = new Classes.Develry.Cache();
40
+
41
+ // Postponed requests are invalidated after 3 hours
42
+ this.postponements.max_age = 3 * 60 * 60 * 1000;
43
+
44
+ // Time spent in the queue
45
+ this.queued_time = 0;
40
46
 
41
- // Postponed requests are invalidated after 1 hour
42
- this.postponed.max_age = 60 * 60 * 1000;
47
+ // Any postponements that have happened already
48
+ this.postponement_counter = 0;
49
+
50
+ // The amount of renders that have happened during this session
51
+ this.render_counter = 0;
43
52
 
44
53
  // Increment the alchemy session count
45
54
  alchemy.session_count++;
@@ -103,6 +112,56 @@ Session.setProperty(function idle_time() {
103
112
  return result;
104
113
  });
105
114
 
115
+ /**
116
+ * Is this an active session?
117
+ * When no activity happens for 5 minutes, the session becomes inactive.
118
+ *
119
+ * @author Jelle De Loecker <jelle@elevenways.be>
120
+ * @since 1.3.1
121
+ * @version 1.3.1
122
+ *
123
+ * @type {Boolean}
124
+ */
125
+ Session.setProperty(function is_active() {
126
+
127
+ if (this.idle_time < 5 * 60 * 1000) {
128
+ return true;
129
+ }
130
+
131
+ return false;
132
+ });
133
+
134
+ /**
135
+ * Add the amount of time this client has been in the queue
136
+ *
137
+ * @author Jelle De Loecker <jelle@elevenways.be>
138
+ * @since 1.3.1
139
+ * @version 1.3.1
140
+ *
141
+ * @return {Boolean}
142
+ */
143
+ Session.setMethod(function addFinishedQueueDuration(ms) {
144
+ this.queued_time += ms;
145
+ });
146
+
147
+ /**
148
+ * Has this client already been in the queue?
149
+ *
150
+ * @author Jelle De Loecker <jelle@elevenways.be>
151
+ * @since 1.3.1
152
+ * @version 1.3.1
153
+ *
154
+ * @return {Boolean}
155
+ */
156
+ Session.setMethod(function hasAlreadyQueued() {
157
+
158
+ if (this.queued_time > 0) {
159
+ return true;
160
+ }
161
+
162
+ return false;
163
+ });
164
+
106
165
  /**
107
166
  * Register a conduit
108
167
  *
@@ -134,21 +193,84 @@ Session.setMethod(function createMenuItem(conduit) {
134
193
  /**
135
194
  * Postpone the result
136
195
  *
137
- * @author Jelle De Loecker <jelle@develry.be>
196
+ * @author Jelle De Loecker <jelle@elevenways.be>
138
197
  * @since 0.2.0
139
- * @version 0.4.0
198
+ * @version 1.3.1
140
199
  *
141
200
  * @param {Conduit} conduit The conduit to postpone
201
+ * @param {Object} options
142
202
  *
143
- * @return {String} The postponed id
203
+ * @return {Alchemy.Conduit.Postponement} The postponement
204
+ */
205
+ Session.setMethod(function postpone(conduit, options) {
206
+
207
+ let id = Crypto.uid();
208
+
209
+ let postponement = new Classes.Alchemy.Conduit.Postponement(conduit, id, options);
210
+
211
+ this.postponements.set(id, postponement);
212
+
213
+ this.postponement_counter++;
214
+
215
+ return postponement;
216
+ });
217
+
218
+ /**
219
+ * Increment the render count
220
+ *
221
+ * @author Jelle De Loecker <jelle@elevenways.be>
222
+ * @since 1.3.1
223
+ * @version 1.3.1
224
+ *
225
+ * @return {Number} The amount of renders
144
226
  */
145
- Session.setMethod(function postpone(conduit) {
227
+ Session.setMethod(function incrementRenderCount() {
228
+ return ++this.render_counter;
229
+ });
146
230
 
147
- var key = Crypto.uid();
231
+ /**
232
+ * Get a postponement by its id
233
+ *
234
+ * @author Jelle De Loecker <jelle@elevenways.be>
235
+ * @since 1.3.1
236
+ * @version 1.3.1
237
+ *
238
+ * @param {String} id The postponement id
239
+ *
240
+ * @return {Alchemy.Conduit.Postponement} The postponement
241
+ */
242
+ Session.setMethod(function getPostponement(id) {
243
+ return this.postponements.get(id);
244
+ });
148
245
 
149
- this.postponed.set(key, conduit);
246
+ /**
247
+ * See if a postponement already exists for the given conduit
248
+ *
249
+ * @author Jelle De Loecker <jelle@elevenways.be>
250
+ * @since 1.3.1
251
+ * @version 1.3.1
252
+ *
253
+ * @param {Conduit} conduit
254
+ *
255
+ * @return {Alchemy.Conduit.Postponement} The postponement
256
+ */
257
+ Session.setMethod(function getExistingPostponement(conduit) {
258
+
259
+ if (!this.postponement_counter) {
260
+ return;
261
+ }
262
+
263
+ if (!this.postponements.length) {
264
+ return;
265
+ }
150
266
 
151
- return key;
267
+ let postponement;
268
+
269
+ for (postponement of this.postponements) {
270
+ if (postponement.original_path === conduit.path) {
271
+ return postponement;
272
+ }
273
+ }
152
274
  });
153
275
 
154
276
  /**
@@ -156,7 +278,7 @@ Session.setMethod(function postpone(conduit) {
156
278
  *
157
279
  * @author Jelle De Loecker <jelle@develry.be>
158
280
  * @since 0.2.0
159
- * @version 1.1.0
281
+ * @version 1.3.1
160
282
  *
161
283
  * @param {Boolean} expired True if removed because time ran out
162
284
  */
@@ -180,6 +302,10 @@ Session.setMethod(function removed(expired) {
180
302
  this.menu_item.remove();
181
303
  }
182
304
 
305
+ for (let postponement of this.postponements) {
306
+ postponement.expire();
307
+ }
308
+
183
309
  this.emit('removed', expired);
184
310
  });
185
311
 
package/lib/core/base.js CHANGED
@@ -717,4 +717,10 @@ Base.setMethod(function debugMark(message) {
717
717
  */
718
718
  alchemy.getClassGroup = __Protoblast.getGroup;
719
719
 
720
- // PROTOBLAST END CUT
720
+ // PROTOBLAST END CUT
721
+
722
+ // @TODO: put this somewhere else
723
+ if (Blast.isBrowser) {
724
+ window.Types = Blast.Types;
725
+ window.Classes = Blast.Classes;
726
+ }
@@ -133,7 +133,7 @@ Alchemy.setMethod(function initScene(scene, options) {
133
133
  *
134
134
  * @author Jelle De Loecker <jelle@elevenways.be>
135
135
  * @since 1.3.0
136
- * @version 1.3.0
136
+ * @version 1.3.1
137
137
  *
138
138
  * @param {String} id
139
139
  * @param {String} message
@@ -167,6 +167,8 @@ Alchemy.setMethod(function distinctProblem(id, message, options = {}) {
167
167
 
168
168
  if (now >= wait_until) {
169
169
  do_log = true;
170
+ } else {
171
+ do_log = false;
170
172
  }
171
173
  } else {
172
174
  do_log = false;
@@ -9,7 +9,8 @@ var shared_objects = {},
9
9
  parseArgs = require('minimist'),
10
10
  libpath = require('path'),
11
11
  colors = require('ansi-256-colors'),
12
- fs = require('fs');
12
+ fs = require('fs'),
13
+ os = require('os');
13
14
 
14
15
  /**
15
16
  * The Alchemy class
@@ -60,6 +61,9 @@ global.Alchemy = Function.inherits('Informer', 'Alchemy', function Alchemy() {
60
61
  // Link to failed modules
61
62
  this.modules_error = useErrors;
62
63
 
64
+ // How many CPUs are available?
65
+ this.cpu_core_count = os.cpus().length;
66
+
63
67
  // Try getting the app package.json file
64
68
  try {
65
69
  package_json = require(libpath.resolve(PATH_ROOT, 'package.json'));
@@ -214,12 +218,107 @@ Alchemy.setProperty(function environment() {
214
218
  return alchemy.settings.environment;
215
219
  });
216
220
 
221
+ /**
222
+ * Get the current lag in ms
223
+ *
224
+ * @author Jelle De Loecker <jelle@elevenways.be>
225
+ * @since 1.3.1
226
+ * @version 1.3.1
227
+ *
228
+ * @return {Number}
229
+ */
230
+ Alchemy.setMethod(function lagInMs() {
231
+ return this.toobusy.lag();
232
+ });
233
+
234
+ /**
235
+ * Get the system load as percentage points
236
+ *
237
+ * @author Jelle De Loecker <jelle@elevenways.be>
238
+ * @since 1.3.1
239
+ * @version 1.3.1
240
+ *
241
+ * @return {Number}
242
+ */
243
+ Alchemy.setMethod(function systemLoad() {
244
+
245
+ let load_average = os.loadavg();
246
+
247
+ const percentage = ~~((load_average[0] / this.cpu_core_count) * 100);
248
+
249
+ return percentage;
250
+ });
251
+
252
+ /**
253
+ * Is the server currently too busy, overloaded in some way?
254
+ *
255
+ * @author Jelle De Loecker <jelle@elevenways.be>
256
+ * @since 1.3.1
257
+ * @version 1.3.1
258
+ *
259
+ * @return {Boolean}
260
+ */
261
+ Alchemy.setMethod(function isTooBusy() {
262
+
263
+ // First check if it's too busy because of lag
264
+ if (this.toobusy()) {
265
+ return true;
266
+ }
267
+
268
+ // Then check the load average
269
+ return this.systemLoad() > 90;
270
+ });
271
+
272
+ /**
273
+ * Is the server too busy for requests?
274
+ *
275
+ * @author Jelle De Loecker <jelle@elevenways.be>
276
+ * @since 1.3.1
277
+ * @version 1.3.1
278
+ *
279
+ * @return {Boolean}
280
+ */
281
+ Alchemy.setMethod(function isTooBusyForRequests() {
282
+
283
+ // If a queue already exists, the server's lag might be quite good
284
+ // BECAUSE requests are being queued.
285
+ if (Classes.Alchemy.Conduit.Postponement.queue_length > 5) {
286
+ return true;
287
+ }
288
+
289
+ return this.isTooBusy();
290
+ });
291
+
292
+ /**
293
+ * Is the server too busy for AJAX too?
294
+ *
295
+ * @author Jelle De Loecker <jelle@elevenways.be>
296
+ * @since 1.3.1
297
+ * @version 1.3.1
298
+ *
299
+ * @return {Boolean}
300
+ */
301
+ Alchemy.setMethod(function isTooBusyForAjax() {
302
+
303
+ if (!this.isTooBusyForRequests()) {
304
+ return false;
305
+ }
306
+
307
+ let lag = this.lagInMs();
308
+
309
+ if (lag > (alchemy.settings.toobusy * 3)) {
310
+ return true;
311
+ }
312
+
313
+ return false;
314
+ });
315
+
217
316
  /**
218
317
  * Start janeway
219
318
  *
220
319
  * @author Jelle De Loecker <jelle@develry.be>
221
320
  * @since 0.5.0
222
- * @version 1.2.7
321
+ * @version 1.3.1
223
322
  *
224
323
  * @param {Object} options
225
324
  */
@@ -301,6 +400,17 @@ Alchemy.setMethod(function startJaneway(options) {
301
400
  this.Janeway.session_menu = session_menu;
302
401
  }
303
402
 
403
+ if (this.settings.lag_menu) {
404
+ let lag_menu = this.Janeway.addIndicator('0 ms');
405
+
406
+ setInterval(() => {
407
+ lag_menu.setIcon(this.lagInMs() + ' ms');
408
+ this.Janeway.redraw();
409
+ }, 900).unref();
410
+
411
+ this.Janeway.lag_menu = lag_menu;
412
+ }
413
+
304
414
  });
305
415
 
306
416
  /**
@@ -1344,24 +1344,47 @@ Alchemy.setMethod(function exposeStatic(name, value) {
1344
1344
  /**
1345
1345
  * Expose the default static variables
1346
1346
  *
1347
- * @author Jelle De Loecker <jelle@develry.be>
1347
+ * @author Jelle De Loecker <jelle@elevenways.be>
1348
1348
  * @since 1.1.0
1349
- * @version 1.2.1
1349
+ * @version 1.3.1
1350
1350
  */
1351
1351
  Alchemy.setMethod(function exposeDefaultStaticVariables() {
1352
1352
 
1353
- let model_info = {},
1353
+ let model_info = [],
1354
+ model_code = '',
1354
1355
  hawkejs = alchemy.hawkejs,
1355
1356
  models = Model.getAllChildren(),
1357
+ info,
1356
1358
  i;
1357
1359
 
1358
1360
  for (i = 0; i < models.length; i++) {
1359
- model_info[models[i].model_name] = models[i].getClientConfig();
1361
+ info = models[i].getClientConfig();
1362
+ model_info.push(info);
1363
+ }
1364
+
1365
+ // Sort the models by their ancestor count
1366
+ model_info.sortByPath(1, 'ancestors');
1367
+
1368
+ model_code = `function inheritModel(parent, child) {
1369
+ return Classes.Hawkejs.Model.getClass(child, true, parent);
1370
+ }\n`;
1371
+
1372
+ for (let info of model_info) {
1373
+ model_code += 'inheritModel(' + JSON.stringify(info.parent) + ', ' + JSON.stringify(info.name) + ')\n';
1360
1374
  }
1361
1375
 
1362
1376
  // Expose the model configuration
1363
1377
  hawkejs.exposeStatic('model_info', model_info);
1364
1378
 
1379
+ Blast.require('client_models', {
1380
+ after : 'helper_model/model',
1381
+ resolver : async function getExportedFunction() {
1382
+ return model_code;
1383
+ },
1384
+ client : true,
1385
+ server : false,
1386
+ });
1387
+
1365
1388
  this.exposeRouteData();
1366
1389
 
1367
1390
  // Expose breadcrumb info
@@ -1377,6 +1400,9 @@ Alchemy.setMethod(function exposeDefaultStaticVariables() {
1377
1400
  hawkejs.exposeStatic('enable_websockets', true);
1378
1401
  }
1379
1402
 
1403
+ // Expose the layout settings
1404
+ hawkejs.exposeStatic('alchemy_layout', alchemy.settings.layout);
1405
+
1380
1406
  let app_version = alchemy.package.version;
1381
1407
 
1382
1408
  // The current app version
package/lib/stages.js CHANGED
@@ -13,11 +13,12 @@
13
13
  * @since 0.0.1
14
14
  * @version 1.1.0
15
15
  */
16
- var path = alchemy.modules.path,
16
+ let path = alchemy.modules.path,
17
17
  settings = alchemy.settings,
18
18
  http = alchemy.modules.http,
19
19
  hawkejs = alchemy.hawkejs,
20
- fs = alchemy.use('fs');
20
+ fs = alchemy.use('fs'),
21
+ total_http_requests = 0;
21
22
 
22
23
  if (alchemy.settings.debug) {
23
24
  alchemy.sputnik.on('launching', function onLaunch(stage) {
@@ -38,13 +39,26 @@ if (alchemy.settings.debug) {
38
39
  });
39
40
  }
40
41
 
42
+ /**
43
+ * Add a getter for the total amount of http requests
44
+ *
45
+ * @author Jelle De Loecker <jelle@elevenways.be>
46
+ * @since 1.3.1
47
+ * @version 1.3.1
48
+ *
49
+ * @type {Number}
50
+ */
51
+ Alchemy.setProperty(function http_request_counter() {
52
+ return total_http_requests;
53
+ });
54
+
41
55
  /**
42
56
  * The "http" stage:
43
57
  * Create the server and listen to requests
44
58
  *
45
59
  * @author Jelle De Loecker <jelle@develry.be>
46
60
  * @since 0.0.1
47
- * @version 1.1.0
61
+ * @version 1.3.1
48
62
  */
49
63
  alchemy.sputnik.add(function http() {
50
64
 
@@ -54,6 +68,7 @@ alchemy.sputnik.add(function http() {
54
68
  // Listen for requests
55
69
  alchemy.server.on('request', function onRequest(request, response) {
56
70
  Router.resolve(request, response);
71
+ total_http_requests++;
57
72
  });
58
73
  });
59
74
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "alchemymvc",
3
3
  "description": "MVC framework for Node.js",
4
- "version": "1.3.0",
4
+ "version": "1.3.1",
5
5
  "author": "Jelle De Loecker <jelle@elevenways.be>",
6
6
  "keywords": [
7
7
  "alchemy",
@@ -22,7 +22,7 @@
22
22
  "chokidar" : "~3.5.3",
23
23
  "formidable" : "~2.0.1",
24
24
  "graceful-fs" : "~4.2.9",
25
- "hawkejs" : "~2.2.23",
25
+ "hawkejs" : "~2.3.1",
26
26
  "jsondiffpatch" : "~0.4.1",
27
27
  "mime" : "~3.0.0",
28
28
  "minimist" : "~1.2.5",
@@ -31,7 +31,7 @@
31
31
  "mongodb" : "~3.6.6",
32
32
  "ncp" : "~2.0.0",
33
33
  "postcss" : "~8.4.6",
34
- "protoblast" : "~0.7.27",
34
+ "protoblast" : "~0.8.0",
35
35
  "semver" : "~7.3.5",
36
36
  "socket.io" : "~2.4.0",
37
37
  "@11ways/socket.io-stream" : "~0.9.2",
@@ -45,7 +45,7 @@
45
45
  "index.js"
46
46
  ],
47
47
  "optionalDependencies": {
48
- "janeway" : "~0.3.6",
48
+ "janeway" : "~0.4.0",
49
49
  "less" : "~4.1.1",
50
50
  "sass" : "~1.53.0",
51
51
  "sass-embedded" : "~1.53.0",