alchemymvc 1.3.15 → 1.3.17

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.
@@ -590,7 +590,7 @@ NoSQL.setStatic(function areComparable(a, b) {
590
590
  *
591
591
  * @author Jelle De Loecker <jelle@develry.be>
592
592
  * @since 1.1.0
593
- * @version 1.3.0
593
+ * @version 1.3.16
594
594
  *
595
595
  * @param {Criteria} criteria
596
596
  * @param {Group} group
@@ -711,13 +711,16 @@ NoSQL.setMethod(function compileCriteria(criteria, group) {
711
711
 
712
712
  let field_entry = {},
713
713
  name = entry.target_path,
714
- queries_property = name.indexOf('.') > -1;
714
+ queries_property = name.indexOf('.') > -1;
715
715
 
716
716
  // Do we need to look into an object itself?
717
- // (Like the "timestamp" property of a date field when stored with units)
718
- if (entry.db_property && !queries_property) {
719
- name += '.' + entry.db_property;
720
- queries_property = true;
717
+ if (entry.db_property) {
718
+
719
+ // Make sure the query doesn't already specifically query this property
720
+ if (name == entry.field.path) {
721
+ name += '.' + entry.db_property;
722
+ queries_property = true;
723
+ }
721
724
  }
722
725
 
723
726
  if (entry.association) {
@@ -824,10 +827,15 @@ NoSQL.setMethod(function compileCriteria(criteria, group) {
824
827
  // (entry.field can be undefined if trying to query a path)
825
828
  if (entry.field && entry.field.is_translatable) {
826
829
 
827
- let prefix = criteria.options.locale;
830
+ let prefix = criteria.options.locale,
831
+ specific_prefix = !!prefix;
832
+
833
+ if (specific_prefix && criteria?.model?.translate === false) {
834
+ specific_prefix = false;
835
+ }
828
836
 
829
837
  // If a prefix is specified, only query that translation
830
- if (prefix) {
838
+ if (specific_prefix) {
831
839
  prefixed_name = name + '.' + prefix;
832
840
  } else {
833
841
  multiple_fields = [];
@@ -5,9 +5,19 @@
5
5
  *
6
6
  * @author Jelle De Loecker <jelle@develry.be>
7
7
  * @since 0.2.0
8
- * @version 1.2.4
8
+ * @version 1.3.16
9
9
  */
10
10
  var SchemaField = Function.inherits('Alchemy.Field', function Schema(schema, name, options) {
11
+
12
+ if (!options) {
13
+ options = {};
14
+ }
15
+
16
+ // If the contents are array-like, the field itself can't be
17
+ if (this.force_array_contents) {
18
+ options.is_array = false;
19
+ }
20
+
11
21
  Schema.super.call(this, schema, name, options);
12
22
 
13
23
  if (options.schema == null || typeof options.schema != 'object') {
@@ -50,9 +60,18 @@ SchemaField.setDeprecatedProperty('fieldSchema', 'field_schema');
50
60
  SchemaField.setDatatype('object');
51
61
 
52
62
  /**
53
- * Get the subschema of this field
63
+ * Is this schema field always an array?
54
64
  *
55
- * @constructor
65
+ * @author Jelle De Loecker <jelle@elevenways.be>
66
+ * @since 1.3.16
67
+ * @version 1.3.16
68
+ *
69
+ * @return {Boolean}
70
+ */
71
+ SchemaField.setProperty('force_array_contents', false);
72
+
73
+ /**
74
+ * Get the subschema of this field
56
75
  *
57
76
  * @author Jelle De Loecker <jelle@develry.be>
58
77
  * @since 0.2.0
@@ -160,7 +179,7 @@ SchemaField.setMethod(function getSubschema(record, some_path) {
160
179
  *
161
180
  * @author Jelle De Loecker <jelle@develry.be>
162
181
  * @since 0.2.0
163
- * @version 1.3.1
182
+ * @version 1.3.16
164
183
  *
165
184
  * @param {Object} value Value of field, an object in this case
166
185
  * @param {Object} data The data object containing `value`
@@ -170,6 +189,38 @@ SchemaField.setMethod(function getSubschema(record, some_path) {
170
189
  */
171
190
  SchemaField.setMethod(function _toDatasource(value, holder, datasource, callback) {
172
191
 
192
+ if (!this.force_array_contents) {
193
+ return this._toDatasourceFromValue(value, holder, datasource, callback);
194
+ }
195
+
196
+ value = Array.cast(value);
197
+
198
+ let tasks = [];
199
+
200
+ for (let entry of value) {
201
+ tasks.push(next => {
202
+ this._toDatasourceFromValue(entry, holder, datasource, next);
203
+ });
204
+ }
205
+
206
+ Function.parallel(tasks, callback);
207
+ });
208
+
209
+ /**
210
+ * Cast all the subschema values using their _toDatasource method
211
+ *
212
+ * @author Jelle De Loecker <jelle@develry.be>
213
+ * @since 0.2.0
214
+ * @version 1.3.1
215
+ *
216
+ * @param {Object} value Value of field, an object in this case
217
+ * @param {Object} data The data object containing `value`
218
+ * @param {Datasource} datasource The destination datasource
219
+ *
220
+ * @return {Object}
221
+ */
222
+ SchemaField.setMethod(function _toDatasourceFromValue(value, holder, datasource, callback) {
223
+
173
224
  var that = this,
174
225
  sub_schema,
175
226
  record,
@@ -241,13 +292,50 @@ SchemaField.setMethod(function _toDatasource(value, holder, datasource, callback
241
292
  *
242
293
  * @author Jelle De Loecker <jelle@develry.be>
243
294
  * @since 0.2.0
244
- * @version 1.2.4
295
+ * @version 1.3.16
245
296
  *
246
297
  * @param {Mixed} value
247
298
  * @param {Function} callback
248
299
  */
249
300
  SchemaField.setMethod(function _toApp(query, options, value, callback) {
250
301
 
302
+ if (!this.force_array_contents) {
303
+ return this._toAppFromValue(query, options, value, callback);
304
+ }
305
+
306
+ value = Array.cast(value);
307
+
308
+ let tasks = [];
309
+
310
+ for (let entry of value) {
311
+ tasks.push(next => {
312
+ this._toAppFromValue(query, options, entry, next);
313
+ });
314
+ }
315
+
316
+ Function.parallel(tasks, (err, result) => {
317
+
318
+ if (err) {
319
+ return callback(err);
320
+ }
321
+
322
+ callback(null, this.cast(result));
323
+ });
324
+
325
+ });
326
+
327
+ /**
328
+ * Turn datasource data into app data
329
+ *
330
+ * @author Jelle De Loecker <jelle@develry.be>
331
+ * @since 0.2.0
332
+ * @version 1.3.16
333
+ *
334
+ * @param {Mixed} value
335
+ * @param {Function} callback
336
+ */
337
+ SchemaField.setMethod(function _toAppFromValue(query, options, value, callback) {
338
+
251
339
  var that = this,
252
340
  recursive,
253
341
  Dummy,
@@ -364,11 +452,11 @@ SchemaField.setMethod(function _toApp(query, options, value, callback) {
364
452
  return callback(err);
365
453
  }
366
454
 
367
- callback(null, that.cast(result));
455
+ callback(null, that.castEntry(result));
368
456
  });
369
457
  }
370
458
 
371
- callback(null, this.cast(value));
459
+ callback(null, this.castEntry(value));
372
460
  });
373
461
 
374
462
  /**
@@ -424,6 +512,17 @@ SchemaField.setMethod(function translateRecord(prefixes, record, allow_empty) {
424
512
  }
425
513
  });
426
514
 
515
+ /**
516
+ * Cast an entry
517
+ *
518
+ * @author Jelle De Loecker <jelle@elevenways.be>
519
+ * @since 1.3.16
520
+ * @version 1.3.16
521
+ */
522
+ SchemaField.setMethod(function castEntry(value, to_datasource) {
523
+ return value;
524
+ });
525
+
427
526
  /**
428
527
  * Cast the value to a document
429
528
  *
@@ -1277,6 +1277,7 @@ Document.setMethod(function hasChanged(name) {
1277
1277
 
1278
1278
  for (key in this.$attributes.original_record) {
1279
1279
  if (!Object.alike(this.$attributes.original_record[key], this[key])) {
1280
+ // @TODO: some special fields always end up being different
1280
1281
  result = true;
1281
1282
  break;
1282
1283
  }
@@ -60,6 +60,19 @@ FieldSet.setStatic(function fromArray(fields) {
60
60
  return set;
61
61
  });
62
62
 
63
+ /**
64
+ * How many fields have been added to this set?
65
+ *
66
+ * @author Jelle De Loecker <jelle@elevenways.be>
67
+ * @since 1.3.16
68
+ * @version 1.3.16
69
+ *
70
+ * @type {Number}
71
+ */
72
+ FieldSet.setProperty(function size() {
73
+ return this.fields.size;
74
+ });
75
+
63
76
  /**
64
77
  * Return an object for json-drying this list
65
78
  *
@@ -516,7 +516,7 @@ Model.setMethod(function getDisplayTitleOrNull(item, fallbacks) {
516
516
  *
517
517
  * @author Jelle De Loecker <jelle@elevenways.be>
518
518
  * @since 0.0.1
519
- * @version 1.3.14
519
+ * @version 1.3.16
520
520
  *
521
521
  * @param {Object} item The record item of this model
522
522
  * @param {String|Array} fallbacks Extra fallbacks to use
@@ -532,7 +532,7 @@ Model.setMethod(function getDisplayTitle(item, fallbacks) {
532
532
  let result = this.getDisplayTitleOrNull(item, fallbacks);
533
533
 
534
534
  if (result == null) {
535
- result = main[this.primary_key] || '';
535
+ result = item[this.primary_key] || '';
536
536
 
537
537
  if (result && typeof result != 'string') {
538
538
  result = '' + result;
@@ -0,0 +1,109 @@
1
+ /**
2
+ * The Alchemy Task History model:
3
+ * keep track of all the tasks that have been run
4
+ *
5
+ * @constructor
6
+ *
7
+ * @author Jelle De Loecker <jelle@elevenways.be>
8
+ * @since 0.5.0
9
+ * @version 0.5.0
10
+ */
11
+ const AlchemyTaskHistory = Function.inherits('Alchemy.Model.App', 'AlchemyTaskHistory');
12
+
13
+ /**
14
+ * Constitute the class wide schema
15
+ *
16
+ * @author Jelle De Loecker <jelle@elevenways.be>
17
+ * @since 0.5.0
18
+ * @version 1.3.17
19
+ */
20
+ AlchemyTaskHistory.constitute(function addTaskFields() {
21
+
22
+ this.belongsTo('AlchemyTask', {
23
+ description : 'The original AlchemyTask document it belonged to',
24
+ });
25
+
26
+ // The timestamp this was scheduled for
27
+ this.addField('scheduled_at', 'Date', {
28
+ description : 'The original date this was scheduled for',
29
+ });
30
+
31
+ this.addField('process_id', 'Integer', {
32
+ description : 'The process ID of the task',
33
+ });
34
+
35
+ // The type of Task that is running
36
+ this.addField('type', 'Enum', {
37
+ values: Classes.Alchemy.Task.Task.getLiveDescendantsMap(),
38
+ });
39
+
40
+ // The payload/settings of the task
41
+ this.addField('settings', 'Schema', {
42
+ description : 'The settings of the task at the time it ran',
43
+ schema: 'type'
44
+ });
45
+
46
+ this.addField('had_error', 'Boolean', {
47
+ description : 'Did this task run into an error?'
48
+ });
49
+
50
+ this.addField('error_message', 'String', {
51
+ description : 'The main error message'
52
+ });
53
+
54
+ this.addField('error_stack', 'Text', {
55
+ description : 'The error stack trace'
56
+ });
57
+
58
+ this.addField('is_running', 'Boolean', {
59
+ description : 'Is this task still running?',
60
+ default : false,
61
+ });
62
+
63
+ this.addField('started_at', 'Datetime', {
64
+ description : 'The datetime this task actually started running',
65
+ });
66
+
67
+ this.addField('ended_at', 'Datetime', {
68
+ description : 'The datetime this task actually ended',
69
+ });
70
+
71
+ this.addIndex('type');
72
+ this.addIndex('is_running');
73
+ });
74
+
75
+ /**
76
+ * Configure the default chimera fieldsets
77
+ *
78
+ * @author Jelle De Loecker <jelle@elevenways.be>
79
+ * @since 1.3.17
80
+ * @version 1.3.17
81
+ */
82
+ AlchemyTaskHistory.constitute(function chimeraConfig() {
83
+
84
+ if (!this.chimera) {
85
+ return;
86
+ }
87
+
88
+ // Get the list group
89
+ let list = this.chimera.getActionFields('list');
90
+
91
+ list.addField('created');
92
+ list.addField('type');
93
+ list.addField('process_id');
94
+ list.addField('scheduled_at');
95
+ list.addField('started_at');
96
+ list.addField('ended_at');
97
+ list.addField('had_error');
98
+
99
+ // Get the edit group
100
+ let edit = this.chimera.getActionFields('edit');
101
+
102
+ edit.addField('type');
103
+ edit.addField('settings');
104
+ edit.addField('had_error');
105
+ edit.addField('error_message');
106
+ edit.addField('error_stack');
107
+ edit.addField('started_at');
108
+ edit.addField('ended_at');
109
+ });
@@ -1,34 +1,157 @@
1
- var all_task_types = alchemy.getClassGroup('task');
2
-
3
1
  /**
4
- * The Alchemy Task Model class
2
+ * The Alchemy Task model:
3
+ *
5
4
  *
6
5
  * @constructor
7
6
  *
8
- * @author Jelle De Loecker <jelle@develry.be>
9
- * @since 0.5.0
10
- * @version 0.5.0
7
+ * @author Jelle De Loecker <jelle@elevenways.be>
8
+ * @since 1.3.17
9
+ * @version 1.3.17
10
+ */
11
+ const AlchemyTask = Function.inherits('Alchemy.Model.App', 'AlchemyTask');
12
+
13
+ /**
14
+ * Constitute the class wide schema
15
+ *
16
+ * @author Jelle De Loecker <jelle@elevenways.be>
17
+ * @since 1.3.17
18
+ * @version 1.3.17
11
19
  */
12
- var AlchemyTask = Function.inherits('Alchemy.Model.App', function AlchemyTask(conduit, options) {
20
+ AlchemyTask.constitute(function addTaskFields() {
21
+
22
+ this.addField('title', 'String', {
23
+ description : 'The title of the task',
24
+ });
25
+
26
+ this.addField('type', 'Enum', {
27
+ values : Classes.Alchemy.Task.Task.getLiveDescendantsMap(),
28
+ description : 'The type of task to run',
29
+ });
30
+
31
+ this.addField('settings', 'Schema', {
32
+ description : 'The settings to use for running the task',
33
+ schema: 'type'
34
+ });
13
35
 
14
- var that = this;
36
+ this.addField('enabled', 'Boolean', {
37
+ description : 'Is this task enabled?',
38
+ default : true,
39
+ });
15
40
 
16
- AlchemyTask.super.call(this, conduit, options);
41
+ this.addField('frequency', 'String', {
42
+ description : 'The frequency this task should run at (CRON syntax)',
43
+ });
44
+
45
+ this.addField('comment', 'Text', {
46
+ description : 'A comment about this task',
47
+ });
48
+
49
+ this.addField('schedule_type', 'Enum', {
50
+ values: {
51
+ user : 'User',
52
+ system_forced : 'System (forced)',
53
+ system_fallback : 'System (fallback)',
54
+ },
55
+ default: 'user',
56
+ });
57
+
58
+ this.addField('forced_schedule_checksum', 'String', {
59
+ description : 'Checksum of the cron syntax and settings, set only for tasks originating from forced schedules',
60
+ });
61
+
62
+ this.addIndex('forced_schedule_checksum', {
63
+ unique : true,
64
+ sparse : true,
65
+ });
17
66
  });
18
67
 
19
68
  /**
20
- * Constitute the class wide schema
69
+ * Configure the default chimera fieldsets
21
70
  *
22
- * @author Jelle De Loecker <jelle@develry.be>
23
- * @since 0.5.0
24
- * @version 0.5.0
71
+ * @author Jelle De Loecker <jelle@elevenways.be>
72
+ * @since 1.3.17
73
+ * @version 1.3.17
25
74
  */
26
- AlchemyTask.constitute(function addTaskFields() {
75
+ AlchemyTask.constitute(function chimeraConfig() {
76
+
77
+ if (!this.chimera) {
78
+ return;
79
+ }
80
+
81
+ // Get the list group
82
+ let list = this.chimera.getActionFields('list');
83
+
84
+ list.addField('title');
85
+ list.addField('type');
86
+ list.addField('enabled');
87
+ list.addField('frequency');
88
+ list.addField('schedule_type');
89
+
90
+ // Get the edit group
91
+ let edit = this.chimera.getActionFields('edit');
92
+
93
+ edit.addField('title');
94
+ edit.addField('frequency')
95
+ edit.addField('enabled');
96
+ edit.addField('type');
97
+ edit.addField('settings');
98
+ edit.addField('comment');
99
+ });
100
+
101
+ /**
102
+ * Do something before saving the record
103
+ *
104
+ * @author Jelle De Loecker <jelle@elevenways.be>
105
+ * @since 1.3.17
106
+ * @version 1.3.17
107
+ *
108
+ * @param {Document.AlchemyTask} doc
109
+ */
110
+ AlchemyTask.setMethod(function beforeSave(doc) {
111
+
112
+ if (!doc.schedule_type) {
113
+ doc.schedule_type = 'user';
114
+ }
115
+
116
+ if (doc.enabled == null) {
117
+ doc.enabled = false;
118
+ }
119
+
120
+ if (!doc.title) {
121
+ let title = '';
122
+
123
+ if (doc.schedule_type == 'system_forced') {
124
+ title += 'System forced: ';
125
+ }
126
+
127
+ if (doc.schedule_type == 'system_fallback') {
128
+ title += 'System fallback: ';
129
+ }
130
+
131
+ title += doc.type;
132
+ doc.title = title;
133
+ }
134
+ });
135
+
136
+ /**
137
+ * Update the schedules after saving
138
+ *
139
+ * @author Jelle De Loecker <jelle@elevenways.be>
140
+ * @since 1.3.17
141
+ * @version 1.3.17
142
+ *
143
+ * @param {Object} main
144
+ * @param {Object} info
145
+ */
146
+ AlchemyTask.setMethod(function afterSave(main, info) {
27
147
 
28
- // The type of Task that is running
29
- this.addField('type', 'Enum', {values: all_task_types});
148
+ if (!main.type) {
149
+ return;
150
+ }
30
151
 
31
- // When the task ended
32
- this.addField('ended', 'Datetime');
152
+ if (!alchemy.task_service.has_loaded) {
153
+ return;
154
+ }
33
155
 
156
+ alchemy.task_service.rescheduleTasksOfType(main.type);
34
157
  });
package/lib/bootstrap.js CHANGED
@@ -312,6 +312,9 @@ Alchemy.setMethod(function start(options, callback) {
312
312
  // Make sure Blast has executed everything that's still waiting
313
313
  Blast.doLoaded();
314
314
 
315
+ // Call the `afterStart` method
316
+ this.ready(() => this.afterStart());
317
+
315
318
  // Schedule the callback
316
319
  return this.ready(callback);
317
320
  });