alchemymvc 1.1.10 → 1.2.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.
@@ -388,15 +388,26 @@ Criteria.setMethod(function clone() {
388
388
  *
389
389
  * @author Jelle De Loecker <jelle@develry.be>
390
390
  * @since 1.1.0
391
- * @version 1.1.0
391
+ * @version 1.2.0
392
392
  *
393
- * @return {Array}
393
+ * @return {String[]}
394
394
  */
395
395
  Criteria.setMethod(function getFieldsToSelect() {
396
396
 
397
- var result = this.options.select.fields || [];
397
+ let result;
398
398
 
399
- return result;
399
+ if (this.options.select.fields && this.options.select.fields.length) {
400
+ result = this.options.select.fields.slice(0);
401
+ }
402
+
403
+ // Fields can sometimes be required for a query (like in a join) but they
404
+ // won't be selected if other fields are explicitly set.
405
+ // So in that case: add these special fields to the projection
406
+ if (result && this.options.select.query_fields && this.options.select.query_fields) {
407
+ result.push(...this.options.select.query_fields);
408
+ }
409
+
410
+ return result || [];
400
411
  });
401
412
 
402
413
  /**
@@ -663,7 +674,7 @@ Criteria.setMethod(function page(page, page_size) {
663
674
  *
664
675
  * @author Jelle De Loecker <jelle@develry.be>
665
676
  * @since 1.1.0
666
- * @version 1.1.3
677
+ * @version 1.2.0
667
678
  *
668
679
  * @param {String|Array} field
669
680
  *
@@ -681,12 +692,6 @@ Criteria.setMethod(function select(field) {
681
692
  }
682
693
  } else {
683
694
 
684
- if (typeof field == 'object') {
685
- if (field instanceof Classes.Alchemy.Criteria.FieldConfig || field.name) {
686
- field = field.name;
687
- }
688
- }
689
-
690
695
  if (this._select) {
691
696
  context = this._select.parse(field);
692
697
  } else {
@@ -1383,7 +1388,7 @@ Select.setMethod(Blast.checksumSymbol, function toChecksum() {
1383
1388
  *
1384
1389
  * @author Jelle De Loecker <jelle@develry.be>
1385
1390
  * @since 1.1.0
1386
- * @version 1.1.0
1391
+ * @version 1.2.0
1387
1392
  *
1388
1393
  * @param {String} name
1389
1394
  *
@@ -1419,9 +1424,39 @@ Select.setMethod(function addAssociation(name) {
1419
1424
  this.associations[name].association_name = name;
1420
1425
  }
1421
1426
 
1427
+ // Get the association data
1428
+ try {
1429
+ let info = this.criteria.model.getAssociation(name);
1430
+
1431
+ if (info) {
1432
+ // Make sure the localkey is added to the resultset
1433
+ this.requireFieldForQuery(info.options.localKey);
1434
+ }
1435
+ } catch (err) {
1436
+ console.warn('Failed to find "' + name + '" association for ' + this.criteria.model.modelName);
1437
+ }
1438
+
1422
1439
  return this.associations[name];
1423
1440
  });
1424
1441
 
1442
+ /**
1443
+ * Require a field for query purposes
1444
+ *
1445
+ * @author Jelle De Loecker <jelle@develry.be>
1446
+ * @since 1.2.0
1447
+ * @version 1.2.0
1448
+ *
1449
+ * @param {String} path
1450
+ */
1451
+ Select.setMethod(function requireFieldForQuery(path) {
1452
+
1453
+ if (!this.query_fields) {
1454
+ this.query_fields = [];
1455
+ }
1456
+
1457
+ this.query_fields.push(path);
1458
+ });
1459
+
1425
1460
  /**
1426
1461
  * Add a field
1427
1462
  *
@@ -1445,17 +1480,33 @@ Select.setMethod(function addField(path) {
1445
1480
  *
1446
1481
  * @author Jelle De Loecker <jelle@develry.be>
1447
1482
  * @since 1.1.0
1448
- * @version 1.1.0
1483
+ * @version 1.2.0
1449
1484
  *
1450
- * @param {String} path
1485
+ * @param {String|Object} path
1451
1486
  *
1452
1487
  * @return {Criteria|Null} A criteria object if the context has changed
1453
1488
  */
1454
1489
  Select.setMethod(function parse(path) {
1455
1490
 
1456
- var context,
1491
+ let context,
1457
1492
  select = this,
1458
- parsed = Criteria.parsePath(path, this.criteria);
1493
+ parsed;
1494
+
1495
+ if (typeof path == 'object' && path && path.name) {
1496
+
1497
+ if (path.path) {
1498
+ path = path.path;
1499
+ } else {
1500
+ let obj = path;
1501
+ path = obj.name;
1502
+
1503
+ if (obj.association) {
1504
+ path = obj.association + '.' + path;
1505
+ }
1506
+ }
1507
+ }
1508
+
1509
+ parsed = Criteria.parsePath(path, this.criteria);
1459
1510
 
1460
1511
  // Associations were found,
1461
1512
  // like "Comment._id" or "Comment.User"
@@ -1470,7 +1521,7 @@ Select.setMethod(function parse(path) {
1470
1521
  continue;
1471
1522
  }
1472
1523
 
1473
- select = this.addAssociation(name);
1524
+ select = select.addAssociation(name);
1474
1525
  }
1475
1526
  }
1476
1527
 
@@ -1489,7 +1540,7 @@ Select.setMethod(function parse(path) {
1489
1540
  *
1490
1541
  * @author Jelle De Loecker <jelle@develry.be>
1491
1542
  * @since 1.1.0
1492
- * @version 1.1.0
1543
+ * @version 1.2.0
1493
1544
  *
1494
1545
  * @param {Criteria} criteria
1495
1546
  *
@@ -1507,6 +1558,10 @@ Select.setMethod(function cloneForCriteria(criteria) {
1507
1558
  clone.fields = this.fields.slice(0);
1508
1559
  }
1509
1560
 
1561
+ if (this.query_fields && this.query_fields.length) {
1562
+ clone.query_fields = this.query_fields.slice(0);
1563
+ }
1564
+
1510
1565
  if (this.associations) {
1511
1566
  let key;
1512
1567
 
@@ -0,0 +1,92 @@
1
+ /**
2
+ * The DataProvider class
3
+ *
4
+ * @author Jelle De Loecker <jelle@elevenways.be>
5
+ * @since 1.2.2
6
+ * @version 1.2.2
7
+ */
8
+ const DataProvider = Function.inherits('Alchemy.Base', 'Alchemy.DataProvider', function DataProvider(config) {
9
+ this.config = config || {};
10
+ });
11
+
12
+
13
+ /**
14
+ * Undry
15
+ *
16
+ * @author Jelle De Loecker <jelle@elevenways.be>
17
+ * @since 1.2.2
18
+ * @version 1.2.2
19
+ *
20
+ * @param {Object} obj
21
+ * @param {Boolean|String} cloned
22
+ *
23
+ * @return {DataProvider}
24
+ */
25
+ DataProvider.setStatic(function unDry(obj) {
26
+ let result = new this(obj.config);
27
+ return result;
28
+ });
29
+
30
+ /**
31
+ * Configure a new config
32
+ *
33
+ * @author Jelle De Loecker <jelle@elevenways.be>
34
+ * @since 1.2.2
35
+ * @version 1.2.2
36
+ */
37
+ DataProvider.setStatic(function addConfig(name, default_value) {
38
+ this.setProperty(name, function getValue() {
39
+
40
+ if (this.config[name] != null) {
41
+ return this.config[name];
42
+ }
43
+
44
+ return default_value;
45
+ }, function setValue(value) {
46
+ return this.config[name] = value;
47
+ });
48
+ });
49
+
50
+ /**
51
+ * The wanted page size
52
+ *
53
+ * @author Jelle De Loecker <jelle@elevenways.be>
54
+ * @since 1.2.2
55
+ * @version 1.2.2
56
+ */
57
+ DataProvider.addConfig('page_size');
58
+
59
+ /**
60
+ * Get all the data
61
+ *
62
+ * @author Jelle De Loecker <jelle@elevenways.be>
63
+ * @since 1.2.2
64
+ * @version 1.2.2
65
+ */
66
+ DataProvider.setAbstractMethod('getAll');
67
+
68
+ /**
69
+ * Get the data for the given page (pages are 1-index based)
70
+ *
71
+ * @author Jelle De Loecker <jelle@elevenways.be>
72
+ * @since 1.2.2
73
+ * @version 1.2.2
74
+ */
75
+ DataProvider.setAbstractMethod('getPage');
76
+
77
+ /**
78
+ * Return an object for json-drying this document
79
+ *
80
+ * @author Jelle De Loecker <jelle@elevenways.be>
81
+ * @since 1.2.2
82
+ * @version 1.2.2
83
+ *
84
+ * @return {Object}
85
+ */
86
+ DataProvider.setMethod(function toDry() {
87
+ return {
88
+ value : {
89
+ config : this.config,
90
+ }
91
+ };
92
+ });
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * @author Jelle De Loecker <jelle@elevenways.be>
9
9
  * @since 1.1.3
10
- * @version 1.1.7
10
+ * @version 1.2.0
11
11
  *
12
12
  * @param {string} path
13
13
  * @param {Object} options
@@ -20,12 +20,18 @@ const FieldConfig = Fn.inherits('Alchemy.Base', 'Alchemy.Criteria', function Fie
20
20
  // The full path to the value
21
21
  this.path = null;
22
22
 
23
+ // The local path to the value (subfields)
24
+ this.local_path = null;
25
+
23
26
  // The pieces of the path
24
27
  this.pieces = null;
25
28
 
26
29
  // The association this belongs to
27
30
  this.association = null;
28
31
 
32
+ // The main model this field is in
33
+ this.model = null;
34
+
29
35
  this.options = options || {};
30
36
 
31
37
  if (path) {
@@ -69,6 +75,94 @@ FieldConfig.setProperty(function title() {
69
75
  return title;
70
76
  });
71
77
 
78
+ /**
79
+ * Set the main model context this field is used for
80
+ * (Actual field can be in an association of this model)
81
+ *
82
+ * @author Jelle De Loecker <jelle@elevenways.be>
83
+ * @since 1.2.2
84
+ * @version 1.2.2
85
+ *
86
+ * @param {String|Model} model
87
+ */
88
+ FieldConfig.setMethod(function setContextModel(model) {
89
+
90
+ if (typeof model == 'string') {
91
+ this.model = model;
92
+ this._model = alchemy.getModel(model);
93
+ } else {
94
+ this._model = model;
95
+ this.model = model.model_name;
96
+ }
97
+ });
98
+
99
+ /**
100
+ * Get the context model this field is used for
101
+ *
102
+ * @author Jelle De Loecker <jelle@elevenways.be>
103
+ * @since 1.2.2
104
+ * @version 1.2.2
105
+ */
106
+ FieldConfig.setMethod(function getContextModel() {
107
+
108
+ if (this._model) {
109
+ return this._model;
110
+ }
111
+
112
+ if (!this.model) {
113
+ return;
114
+ }
115
+
116
+ this._model = alchemy.getModel(this.model);
117
+ return this._model;
118
+ });
119
+
120
+ /**
121
+ * Get the actual model this field is in
122
+ *
123
+ * @author Jelle De Loecker <jelle@elevenways.be>
124
+ * @since 1.2.2
125
+ * @version 1.2.2
126
+ */
127
+ FieldConfig.setMethod(function getModel() {
128
+
129
+ let model = this.getContextModel();
130
+
131
+ if (!model) {
132
+ return;
133
+ }
134
+
135
+ if (this.association) {
136
+ let config = model.getAssociation(this.association);
137
+
138
+ if (config) {
139
+ model = alchemy.getModel(config.modelName);
140
+ } else {
141
+ model = null;
142
+ }
143
+ }
144
+
145
+ return model;
146
+ });
147
+
148
+ /**
149
+ * Get the field definition
150
+ *
151
+ * @author Jelle De Loecker <jelle@elevenways.be>
152
+ * @since 1.2.2
153
+ * @version 1.2.2
154
+ */
155
+ FieldConfig.setMethod(function getFieldDefinition() {
156
+
157
+ let model = this.getModel();
158
+
159
+ if (!model) {
160
+ return;
161
+ }
162
+
163
+ return model.getField(this.local_path);
164
+ });
165
+
72
166
  /**
73
167
  * Return an object for json-drying this list
74
168
  *
@@ -89,7 +183,7 @@ FieldConfig.setMethod(function toDry() {
89
183
  *
90
184
  * @author Jelle De Loecker <jelle@elevenways.be>
91
185
  * @since 1.1.3
92
- * @version 1.1.4
186
+ * @version 1.2.0
93
187
  *
94
188
  * @return {Object}
95
189
  */
@@ -97,6 +191,7 @@ FieldConfig.setMethod(function toJSON() {
97
191
  return {
98
192
  name : this.name,
99
193
  path : this.path,
194
+ local_path : this.local_path,
100
195
  association : this.association,
101
196
  options : this.options,
102
197
  };
@@ -107,7 +202,7 @@ FieldConfig.setMethod(function toJSON() {
107
202
  *
108
203
  * @author Jelle De Loecker <jelle@elevenways.be>
109
204
  * @since 1.1.3
110
- * @version 1.1.3
205
+ * @version 1.2.0
111
206
  *
112
207
  * @param {string} path
113
208
  */
@@ -120,6 +215,7 @@ FieldConfig.setMethod(function parsePath(path) {
120
215
  } else if (path.indexOf('.') == -1) {
121
216
  this.name = path;
122
217
  this.path = path;
218
+ this.local_path = path;
123
219
  this.pieces = [path];
124
220
  return;
125
221
  } else {
@@ -132,13 +228,27 @@ FieldConfig.setMethod(function parsePath(path) {
132
228
 
133
229
  this.path = path;
134
230
  this.pieces = pieces;
231
+ this.local_path = '';
135
232
 
136
233
  for (i = 0; i < pieces.length; i++) {
137
234
  piece = pieces[i];
138
235
 
139
- if (i == 0 && piece[0].isUpperCase()) {
140
- this.association = piece;
236
+ if (piece[0].isUpperCase()) {
237
+
238
+ if (this.association) {
239
+ this.association += '.';
240
+ } else {
241
+ this.association = '';
242
+ }
243
+
244
+ this.association += piece;
141
245
  continue;
246
+ } else {
247
+ if (this.local_path) {
248
+ this.local_path += '.';
249
+ }
250
+
251
+ this.local_path += piece;
142
252
  }
143
253
  }
144
254
 
@@ -143,6 +143,28 @@ FieldSet.setMethod(function toJSON() {
143
143
  return result;
144
144
  });
145
145
 
146
+ /**
147
+ * Get an instance of the model
148
+ *
149
+ * @author Jelle De Loecker <jelle@elevenways.be>
150
+ * @since 1.2.2
151
+ * @version 1.2.2
152
+ *
153
+ * @return {Model}
154
+ */
155
+ FieldSet.setMethod(function getModel() {
156
+
157
+ if (!this.model) {
158
+ return;
159
+ }
160
+
161
+ if (!this._model) {
162
+ this._model = alchemy.getModel(this.model);
163
+ }
164
+
165
+ return this._model;
166
+ });
167
+
146
168
  /**
147
169
  * Iterator method
148
170
  *
@@ -165,7 +187,7 @@ FieldSet.setMethod(Symbol.iterator, function* iterate() {
165
187
  *
166
188
  * @author Jelle De Loecker <jelle@elevenways.be>
167
189
  * @since 1.1.3
168
- * @version 1.1.3
190
+ * @version 1.2.2
169
191
  *
170
192
  * @param {String} name The name of the field to add
171
193
  * @param {Object} options
@@ -176,6 +198,10 @@ FieldSet.setMethod(function addField(name, options) {
176
198
 
177
199
  let config = new Classes.Alchemy.Criteria.FieldConfig(name, options);
178
200
 
201
+ if (this.model && !config.getContextModel()) {
202
+ config.setContextModel(this.model);
203
+ }
204
+
179
205
  this.fields.set(name, config);
180
206
 
181
207
  return config;
@@ -403,7 +403,7 @@ Model.setMethod(function getFindOptions(options) {
403
403
  *
404
404
  * @author Jelle De Loecker <jelle@develry.be>
405
405
  * @since 1.1.0
406
- * @version 1.1.0
406
+ * @version 1.2.0
407
407
  *
408
408
  * @param {String} alias
409
409
  *
@@ -415,7 +415,30 @@ Model.setMethod(function getAssociation(alias) {
415
415
  throw new Error('Unable to find ' + this.constructor.name + ' schema associations');
416
416
  }
417
417
 
418
- let config = this.schema.associations[alias];
418
+ let config;
419
+
420
+ // @TODO: Test nested association getting
421
+ if (alias && alias.indexOf('.') > -1) {
422
+ let pieces = alias.split('.'),
423
+ context = this,
424
+ piece,
425
+ temp;
426
+
427
+ for (piece of pieces) {
428
+ temp = context.getAssociation(piece);
429
+
430
+ if (!temp) {
431
+ break;
432
+ }
433
+
434
+ context = this.getModel(temp.modelName);
435
+ }
436
+
437
+ // The config s the last association gotten
438
+ config = temp;
439
+ } else {
440
+ config = this.schema.associations[alias];
441
+ }
419
442
 
420
443
  if (!config) {
421
444
  throw new Error('Unable to find ' + JSON.stringify(alias) + ' association in ' + this.constructor.name + ' model');
@@ -1401,15 +1424,30 @@ Model.setProperty(function associations() {
1401
1424
  *
1402
1425
  * @author Jelle De Loecker <jelle@develry.be>
1403
1426
  * @since 1.0.0
1404
- * @version 1.1.0
1427
+ * @version 1.2.0
1405
1428
  *
1406
- * @param {String} name The name of the field
1429
+ * @param {string} path The path to the field
1407
1430
  *
1408
1431
  * @return {Object}
1409
1432
  */
1410
- Model.setMethod(function getField(name) {
1411
- return this.schema.getField(name);
1412
- }, false);
1433
+ Model.setMethod(function getField(path) {
1434
+
1435
+ if (path.indexOf('.') > -1) {
1436
+ let config = new Classes.Alchemy.Criteria.FieldConfig(path);
1437
+
1438
+ // If part of the path is an association, look for that now
1439
+ if (config.association) {
1440
+ let association = this.getAssociation(config.association);
1441
+ let model = this.getModel(association.modelName);
1442
+ return model.getField(config.local_path);
1443
+ }
1444
+
1445
+ // If not, just use the local_path
1446
+ path = config.local_path;
1447
+ }
1448
+
1449
+ return this.schema.getField(path);
1450
+ });
1413
1451
 
1414
1452
  // Make this class easily available
1415
1453
  Hawkejs.Model = Model;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The Remote DataProvider class
3
+ *
4
+ * @author Jelle De Loecker <jelle@elevenways.be>
5
+ * @since 1.2.2
6
+ * @version 1.2.2
7
+ */
8
+ const RemoteDataProvider = Function.inherits('Alchemy.DataProvider', 'Remote');
9
+
10
+ /**
11
+ * The url to load
12
+ *
13
+ * @author Jelle De Loecker <jelle@elevenways.be>
14
+ * @since 1.2.2
15
+ * @version 1.2.2
16
+ */
17
+ RemoteDataProvider.addConfig('source');
18
+
19
+ /**
20
+ * Get all the data
21
+ *
22
+ * @author Jelle De Loecker <jelle@elevenways.be>
23
+ * @since 1.2.2
24
+ * @version 1.2.2
25
+ */
26
+ RemoteDataProvider.setMethod(async function getAll() {
27
+
28
+ if (!this.source) {
29
+ return [];
30
+ }
31
+
32
+ let result = await Blast.fetch(this.source);
33
+
34
+ return result;
35
+ });
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The Alchemy Migration Model class
3
+ *
4
+ * @constructor
5
+ *
6
+ * @author Jelle De Loecker <jelle@elevenways.be>
7
+ * @since 1.2.0
8
+ * @version 1.2.0
9
+ */
10
+ const AlchemyMigration = Function.inherits('Alchemy.Model.App', 'AlchemyMigration');
11
+
12
+ /**
13
+ * Constitute the class wide schema
14
+ *
15
+ * @author Jelle De Loecker <jelle@elevenways.be>
16
+ * @since 1.2.0
17
+ * @version 1.2.0
18
+ */
19
+ AlchemyMigration.constitute(function addTaskFields() {
20
+
21
+ // The name of the migration
22
+ this.addField('name', 'String');
23
+
24
+ // The full path of the file
25
+ this.addField('path', 'String');
26
+
27
+ // When the migration ended
28
+ this.addField('ended', 'Datetime');
29
+
30
+ // Was there an error?
31
+ this.addField('error', 'String');
32
+
33
+ });
package/lib/bootstrap.js CHANGED
@@ -3,6 +3,22 @@
3
3
  var libpath = require('path'),
4
4
  starting;
5
5
 
6
+ /**
7
+ * Calling dynamic `import()` from certain places in the codebase actually
8
+ * causes segfaults. This is a workaround for that.
9
+ *
10
+ * @author Jelle De Loecker <jelle@elevenways.be>
11
+ * @since 1.2.2
12
+ * @version 1.2.2
13
+ *
14
+ * @param {String} path
15
+ *
16
+ * @return {Promise}
17
+ */
18
+ global.doImport = function doImport(path) {
19
+ return import(path);
20
+ };
21
+
6
22
  /**
7
23
  * Extend prototypes
8
24
  */
@@ -61,6 +77,15 @@ require('./core/base');
61
77
  */
62
78
  require('./core/client_base');
63
79
 
80
+ /**
81
+ * The migration class
82
+ *
83
+ * @author Jelle De Loecker <jelle@elevenways.be>
84
+ * @since 1.2.0
85
+ * @version 1.2.0
86
+ */
87
+ require('./class/migration');
88
+
64
89
  var hawkejs_options = {
65
90
  server : false,
66
91
  make_commonjs: true,
@@ -165,7 +165,7 @@ Datasource.setMethod(function getSchema(schema) {
165
165
  *
166
166
  * @author Jelle De Loecker <jelle@develry.be>
167
167
  * @since 0.2.0
168
- * @version 1.1.0
168
+ * @version 1.2.0
169
169
  *
170
170
  * @param {Schema|Model} schema
171
171
  * @param {Object} data
@@ -187,7 +187,7 @@ Datasource.setMethod(function toDatasource(schema, data, callback) {
187
187
  }
188
188
 
189
189
  if (!schema) {
190
- log.todo('Schema not found: not normalizing data');
190
+ log.todo('Schema not found: not normalizing data', data);
191
191
  pledge = Pledge.resolve(data);
192
192
  pledge.done(callback);
193
193
  return pledge;
@@ -23,12 +23,17 @@ Function.getNamespace('Alchemy.Element').setStatic('default_element_prefix', 'al
23
23
  *
24
24
  * @author Jelle De Loecker <jelle@elevenways.be>
25
25
  * @since 1.1.3
26
- * @version 1.1.3
26
+ * @version 1.2.1
27
27
  *
28
28
  * @return {RURL}
29
29
  */
30
30
  Element.setMethod(function getCurrentUrl() {
31
31
  if (Blast.isBrowser) {
32
+
33
+ if (hawkejs.scene.opening_url && hawkejs.scene.opening_url.url) {
34
+ return Blast.Classes.RURL.parse(hawkejs.scene.opening_url.url);
35
+ }
36
+
32
37
  return Blast.Classes.RURL.parse(window.location);
33
38
  } else if (this.hawkejs_renderer && this.hawkejs_renderer.variables && this.hawkejs_renderer.variables.__url) {
34
39
  return this.hawkejs_renderer.variables.__url.clone();