alchemymvc 1.4.0-alpha.9 → 1.4.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 (76) hide show
  1. package/lib/app/behaviour/revision_behaviour.js +1 -1
  2. package/lib/app/behaviour/sluggable_behaviour.js +74 -3
  3. package/lib/app/conduit/electron_conduit.js +1 -1
  4. package/lib/app/conduit/socket_conduit.js +62 -8
  5. package/lib/app/controller/alchemy_info_controller.js +2 -2
  6. package/lib/app/datasource/mongo_datasource.js +103 -107
  7. package/lib/app/element/al_time.js +126 -0
  8. package/lib/app/helper/cron.js +2 -2
  9. package/lib/app/helper/enum_values.js +9 -0
  10. package/lib/app/helper/router_helper.js +37 -5
  11. package/lib/app/helper/socket_helper.js +71 -14
  12. package/lib/app/helper/syncable.js +253 -30
  13. package/lib/app/helper_datasource/00-nosql_datasource.js +410 -54
  14. package/lib/app/helper_datasource/05-fallback_datasource.js +7 -2
  15. package/lib/app/helper_datasource/idb_datasource.js +82 -64
  16. package/lib/app/helper_datasource/indexed_db.js +41 -33
  17. package/lib/app/helper_datasource/read_operational_context.js +0 -17
  18. package/lib/app/helper_datasource/remote_datasource.js +1 -1
  19. package/lib/app/helper_field/00-objectid_field.js +16 -0
  20. package/lib/app/helper_field/11-date_field.js +13 -0
  21. package/lib/app/helper_field/datetime_field.js +13 -0
  22. package/lib/app/helper_field/enum_field.js +27 -0
  23. package/lib/app/helper_field/geopoint_field.js +61 -0
  24. package/lib/app/helper_field/html_field.js +14 -1
  25. package/lib/app/helper_field/integer_field.js +14 -0
  26. package/lib/app/helper_field/local_date_field.js +13 -0
  27. package/lib/app/helper_field/local_date_time_field.js +13 -0
  28. package/lib/app/helper_field/local_time_field.js +13 -0
  29. package/lib/app/helper_field/password_field.js +28 -2
  30. package/lib/app/helper_field/schema_field.js +88 -4
  31. package/lib/app/helper_field/url_field.js +22 -0
  32. package/lib/app/helper_model/00-base_criteria.js +81 -6
  33. package/lib/app/helper_model/05-criteria_expressions.js +53 -11
  34. package/lib/app/helper_model/10-model_criteria.js +82 -22
  35. package/lib/app/helper_model/document.js +23 -9
  36. package/lib/app/helper_model/field_config.js +9 -2
  37. package/lib/app/helper_model/model.js +15 -11
  38. package/lib/app/model/system_task_history_model.js +11 -1
  39. package/lib/class/conduit.js +116 -13
  40. package/lib/class/controller.js +1 -0
  41. package/lib/class/datasource.js +30 -2
  42. package/lib/class/document.js +40 -12
  43. package/lib/class/field.js +170 -7
  44. package/lib/class/import_stream_parser.js +299 -0
  45. package/lib/class/inode_file.js +2 -2
  46. package/lib/class/migration.js +5 -2
  47. package/lib/class/model.js +22 -151
  48. package/lib/class/operational_context.js +37 -0
  49. package/lib/class/plugin.js +32 -3
  50. package/lib/class/route.js +34 -3
  51. package/lib/class/router.js +38 -34
  52. package/lib/class/schema.js +1 -1
  53. package/lib/class/schema_client.js +172 -14
  54. package/lib/class/session.js +1 -1
  55. package/lib/class/sitemap.js +2 -2
  56. package/lib/class/task.js +30 -1
  57. package/lib/class/task_service.js +83 -14
  58. package/lib/core/alchemy.js +188 -166
  59. package/lib/core/alchemy_functions.js +7 -11
  60. package/lib/core/alchemy_load_functions.js +101 -10
  61. package/lib/core/base.js +2 -2
  62. package/lib/core/client_alchemy.js +9 -1
  63. package/lib/core/middleware.js +83 -25
  64. package/lib/core/setting.js +86 -14
  65. package/lib/scripts/create_constants.js +5 -1
  66. package/lib/scripts/create_settings.js +44 -0
  67. package/lib/scripts/setup_ai_devmode.js +324 -0
  68. package/lib/scripts/setup_devwatch.js +0 -0
  69. package/lib/stages/00-load_core.js +9 -3
  70. package/lib/stages/50-routes.js +6 -1
  71. package/lib/stages/90-server.js +0 -1
  72. package/lib/testing/browser.js +1164 -0
  73. package/lib/testing/harness.js +840 -0
  74. package/package.json +26 -19
  75. package/testing/browser.js +27 -0
  76. package/testing.js +37 -0
@@ -0,0 +1,126 @@
1
+ const DATETIME = Symbol('datetime');
2
+
3
+ /**
4
+ * The custom al-time element
5
+ *
6
+ * @author Jelle De Loecker <jelle@elevenways.be>
7
+ * @since 1.4.0
8
+ * @version 1.4.0
9
+ */
10
+ const AlTime = Function.inherits('Alchemy.Element', 'AlTime');
11
+
12
+ /**
13
+ * The preferred format
14
+ *
15
+ * @author Jelle De Loecker <jelle@elevenways.be>
16
+ * @since 0.3.0
17
+ * @version 0.3.0
18
+ */
19
+ AlTime.setAttribute('format', null, function setFormat(format) {
20
+ this._populate({value: this.datetime, format: format});
21
+ return format;
22
+ });
23
+
24
+ /**
25
+ * Get/set the datetime value
26
+ *
27
+ * @author Jelle De Loecker <jelle@elevenways.be>
28
+ * @since 0.3.0
29
+ * @version 0.3.0
30
+ */
31
+ AlTime.setAttribute('datetime', null, function setDatetime(value) {
32
+ this._populate({value: value});
33
+ return this[DATETIME];
34
+ });
35
+
36
+ /**
37
+ * Set the value with a function call
38
+ *
39
+ * @author Jelle De Loecker <jelle@elevenways.be>
40
+ * @since 0.3.0
41
+ * @version 0.3.0
42
+ */
43
+ AlTime.setMethod(function setDatetime(value) {
44
+ this.datetime = value;
45
+ });
46
+
47
+ /**
48
+ * Populate the element
49
+ *
50
+ * @author Jelle De Loecker <jelle@elevenways.be>
51
+ * @since 0.3.0
52
+ * @version 0.3.0
53
+ *
54
+ * @return {Object[]}
55
+ */
56
+ AlTime.setMethod(function populate() {
57
+ let iso_string = this._populate({value: this.value, format: this.format});
58
+ this[DATETIME] = iso_string;
59
+ return iso_string;
60
+ });
61
+
62
+ /**
63
+ * Populate the element
64
+ *
65
+ * @author Jelle De Loecker <jelle@elevenways.be>
66
+ * @since 0.3.0
67
+ * @version 0.3.0
68
+ */
69
+ AlTime.setMethod(function _populate(input) {
70
+
71
+ if (!input) {
72
+ input = {};
73
+ }
74
+
75
+ let {value, format} = input;
76
+
77
+ if (value == null) {
78
+ value = this[DATETIME];
79
+ }
80
+
81
+ if (format == null) {
82
+ format = this.format;
83
+ }
84
+
85
+ let iso_date,
86
+ date;
87
+
88
+ if (value) {
89
+ date = Date.create(value);
90
+ iso_date = date.toISOString();
91
+ } else {
92
+ iso_date = '';
93
+ }
94
+
95
+ value = iso_date;
96
+
97
+ if (Blast.isServer) {
98
+ return value;
99
+ }
100
+
101
+ let target_element = this.children?.[0];
102
+
103
+ if (!target_element) {
104
+ target_element = this.createElement('time');
105
+ this.append(target_element);
106
+ }
107
+
108
+ let tag_name = target_element.tagName;
109
+ let formatted;
110
+
111
+ if (format) {
112
+ formatted = date.format(format);
113
+ } else {
114
+ formatted = date + '';
115
+ }
116
+
117
+ if (tag_name == 'INPUT') {
118
+ target_element.value = formatted;
119
+ } else {
120
+ if (tag_name == 'TIME') {
121
+ target_element.setAttribute('datetime', iso_date);
122
+ }
123
+
124
+ target_element.textContent = formatted;
125
+ }
126
+ });
@@ -284,7 +284,7 @@ function splitAndCleanup(input, separator) {
284
284
  * @author Santhosh Kumar <brsanthu@gmail.com>
285
285
  * @author Jelle De Loecker <jelle@elevenways.be>
286
286
  * @since 1.3.17
287
- * @version 1.3.17
287
+ * @version 1.4.1
288
288
  */
289
289
  CronExpression.setMethod(function parse() {
290
290
 
@@ -296,7 +296,7 @@ CronExpression.setMethod(function parse() {
296
296
  let has_seconds = this.has_seconds;
297
297
 
298
298
  if (Cron.PREDEFINED_EXPRESSIONS[internal_expression]) {
299
- internal_expression = Cron.PREDEFINED_EXPRESSIONS[expression];
299
+ internal_expression = Cron.PREDEFINED_EXPRESSIONS[internal_expression];
300
300
  has_seconds = false;
301
301
  }
302
302
 
@@ -70,17 +70,23 @@ EnumMap.setMethod(function set(name, value) {
70
70
  result = {
71
71
  name : value.name,
72
72
  title : value.title,
73
+ short_title : value.short_title,
73
74
  };
74
75
  } else {
75
76
  result = {
76
77
  name : value.name,
77
78
  title : value.title || value.name,
79
+ short_title : value.short_title,
78
80
  };
79
81
 
80
82
  if (value.icon) {
81
83
  result.icon = value.icon;
82
84
  }
83
85
 
86
+ if (value.icon_style) {
87
+ result.icon_style = value.icon_style;
88
+ }
89
+
84
90
  if (value.color) {
85
91
  result.color = value.color;
86
92
  }
@@ -96,6 +102,9 @@ EnumMap.setMethod(function set(name, value) {
96
102
  }
97
103
 
98
104
  result.number = this.local.size + 1;
105
+
106
+ // "value" does not mean the value that is stored in the database
107
+ // it can be the function that houses info
99
108
  result.value = value;
100
109
  result.is_enumified = true;
101
110
 
@@ -29,14 +29,26 @@ var Route = Function.inherits('Alchemy.Helper.Router', function Route(renderer)
29
29
  *
30
30
  * @author Jelle De Loecker <jelle@elevenways.be>
31
31
  * @since 1.1.0
32
- * @version 1.2.5
32
+ * @version 1.4.0
33
33
  *
34
34
  * @type {RURL}
35
35
  */
36
36
  Router.setProperty(function current_url() {
37
- if (this.view_render?.variables?.__url) {
38
- return this.view_render.variables.__url.clone();
39
- } else if (Blast.isBrowser) {
37
+
38
+ let renderer = this.view_render;
39
+
40
+ if (renderer?.variables) {
41
+ try {
42
+ let url = renderer.variables.get('__url');
43
+ if (url) {
44
+ return url.clone();
45
+ }
46
+ } catch (err) {
47
+ // Ignore
48
+ }
49
+ }
50
+
51
+ if (Blast.isBrowser) {
40
52
 
41
53
  if (hawkejs?.scene?.opening_url?.url) {
42
54
  return Blast.Classes.RURL.parse(hawkejs.scene.opening_url.url);
@@ -270,7 +282,27 @@ Router.setMethod(function applyDirective(element, name, options) {
270
282
  }
271
283
 
272
284
  if (method_attribute && config.methods?.[0]) {
273
- element.setAttribute(method_attribute, config.methods[0]);
285
+ let existing_method = element.getAttribute(method_attribute);
286
+
287
+ // Assume the user knows what they're doing when there is an existing method
288
+ if (!existing_method) {
289
+ let found_method = false;
290
+
291
+ // There is no method, look for the best one (we prefer a post)
292
+ for (let method of config.methods) {
293
+ method = method.toLowerCase();
294
+
295
+ if (method == 'post' || method == 'put') {
296
+ element.setAttribute(method_attribute, method);
297
+ found_method = true;
298
+ break;
299
+ }
300
+ }
301
+
302
+ if (!found_method) {
303
+ element.setAttribute(method_attribute, config.methods[0]);
304
+ }
305
+ }
274
306
  }
275
307
 
276
308
  if (disable_ajax) {
@@ -16,7 +16,7 @@ function isStream(obj) {
16
16
  *
17
17
  * @author Jelle De Loecker <jelle@elevenways.be>
18
18
  * @since 0.2.0
19
- * @version 1.3.10
19
+ * @version 1.4.0
20
20
  *
21
21
  * @param {string} type
22
22
  */
@@ -43,12 +43,21 @@ var Linkup = Blast.Collection.Function.inherits('Informer', function ClientLinku
43
43
  // The initial submitted data
44
44
  this.initialData = data;
45
45
 
46
+ // Has this linkup been destroyed?
47
+ this.destroyed = false;
48
+
46
49
  // Make the linkup store itself
47
50
  client.linkups[this.id] = this;
48
51
 
49
52
  // The parent server
50
53
  this.client = client;
51
54
 
55
+ // Listen for socket disconnection
56
+ this._onClientClose = () => {
57
+ this._markDisconnected();
58
+ };
59
+ client.on('close', this._onClientClose);
60
+
52
61
  if (server_object) {
53
62
  this.submit('ready');
54
63
  } else {
@@ -146,23 +155,51 @@ Linkup.setMethod(function createStream() {
146
155
  *
147
156
  * @author Jelle De Loecker <jelle@elevenways.be>
148
157
  * @since 0.2.0
149
- * @version 1.3.10
158
+ * @version 1.4.0
150
159
  */
151
160
  Linkup.setMethod(function destroy() {
161
+ if (this.destroyed) {
162
+ return;
163
+ }
164
+
152
165
  this.submit('__destroy__');
153
166
  this._destroy();
154
167
  this.removeAllListeners();
155
168
  });
156
169
 
170
+ /**
171
+ * Mark the linkup as disconnected due to socket close
172
+ *
173
+ * @author Jelle De Loecker <jelle@elevenways.be>
174
+ * @since 1.4.0
175
+ * @version 1.4.0
176
+ */
177
+ Linkup.setMethod(function _markDisconnected() {
178
+ if (this.destroyed) {
179
+ return;
180
+ }
181
+
182
+ // Emit the close event so listeners can handle disconnect
183
+ this.emit('close');
184
+ });
185
+
157
186
  /**
158
187
  * Make sure the linkup is removed
159
188
  *
160
189
  * @author Jelle De Loecker <jelle@elevenways.be>
161
190
  * @since 1.3.10
162
- * @version 1.3.10
191
+ * @version 1.4.0
163
192
  */
164
193
  Linkup.setMethod(function _destroy() {
194
+ this.destroyed = true;
165
195
  delete this.client.linkups[this.id];
196
+
197
+ // Remove the close listener from the client
198
+ if (this._onClientClose) {
199
+ this.client.removeListener('close', this._onClientClose);
200
+ this._onClientClose = null;
201
+ }
202
+
166
203
  this.emit('destroyed');
167
204
  });
168
205
 
@@ -352,7 +389,7 @@ Client.setMethod(function createStream() {
352
389
  *
353
390
  * @author Jelle De Loecker <jelle@elevenways.be>
354
391
  * @since 0.2.0
355
- * @version 1.3.12
392
+ * @version 1.4.0
356
393
  *
357
394
  * @param {Function} callback
358
395
  */
@@ -487,7 +524,8 @@ Client.setMethod(function connect(address, data, callback) {
487
524
  });
488
525
 
489
526
  server.on('error', function(err) {
490
- console.log('Socket error:', err);
527
+ log.error('Socket error:', err);
528
+ that.emit('error', err);
491
529
  });
492
530
 
493
531
  // Listen for cookies
@@ -547,16 +585,22 @@ Client.setMethod(function connect(address, data, callback) {
547
585
  }
548
586
  }
549
587
 
550
- try {
551
- if (packet.data && typeof packet.data == 'object') {
552
- packet.data = JSON.undry(packet.data);
553
- }
554
- } catch (err) {
555
- console.log('ERROR UNDRYING PACKET:', err, packet);
556
- return;
588
+ try {
589
+ if (packet.data && typeof packet.data == 'object') {
590
+ packet.data = JSON.undry(packet.data);
557
591
  }
592
+ } catch (err) {
593
+ log.error('Error undrying response data:', err);
558
594
 
559
- if (packet.noData) {
595
+ // Still call the callback with an error instead of silently dropping
596
+ let undry_error = new Error('Failed to deserialize response data: ' + err.message);
597
+ undry_error.cause = err;
598
+ that.callbacks[packet.respond_to](undry_error, null);
599
+ delete that.callbacks[packet.respond_to];
600
+ return;
601
+ }
602
+
603
+ if (packet.noData) {
560
604
  that.callbacks[packet.respond_to](packet.err, packet.stream);
561
605
  } else if (packet.stream) {
562
606
  that.callbacks[packet.respond_to](packet.err, packet.data, packet.stream);
@@ -578,7 +622,20 @@ Client.setMethod(function connect(address, data, callback) {
578
622
  packet.data = JSON.undry(packet.data);
579
623
  }
580
624
  } catch (err) {
581
- console.log('ERROR UNDRYING PACKET:', err, packet);
625
+ log.error('Error undrying packet data:', err);
626
+
627
+ // If the sender expects a response, send back an error
628
+ if (packet.respond) {
629
+ let response_packet = {
630
+ err: new Error('Failed to deserialize packet data: ' + err.message),
631
+ respond_to: packet.id,
632
+ data: null
633
+ };
634
+ server.emit('response', response_packet);
635
+ }
636
+
637
+ // Emit error event so callers can be aware
638
+ that.emit('packet_error', err, packet);
582
639
  return;
583
640
  }
584
641