alchemymvc 1.2.1 → 1.2.4

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.
@@ -305,7 +305,7 @@ Document.setStatic(function getDocumentClass(model) {
305
305
  *
306
306
  * @author Jelle De Loecker <jelle@develry.be>
307
307
  * @since 1.0.0
308
- * @version 1.1.0
308
+ * @version 1.2.4
309
309
  *
310
310
  * @param {Object} obj
311
311
  * @param {Boolean|String} cloned
@@ -349,6 +349,25 @@ Document.setStatic(function unDry(obj, cloned) {
349
349
  obj.$options.model = null;
350
350
  }
351
351
 
352
+ if (Blast.isBrowser && obj.$options?.private_fields) {
353
+ let model = alchemy.getModel(obj.$model_name),
354
+ field;
355
+
356
+ for (field of obj.$options.private_fields) {
357
+
358
+ // @TODO: don't let documents undry before schema is ready?
359
+ if (!model.schema) {
360
+ continue;
361
+ }
362
+
363
+ if (model.schema.has(field.name)) {
364
+ continue;
365
+ }
366
+
367
+ model.schema.addField(field.name, field.constructor.type_name, field.options);
368
+ }
369
+ }
370
+
352
371
  DocClass.call(result, obj.$record, obj.$options);
353
372
 
354
373
  if (cloned || Blast.isNode || obj[Blast.Classes.IndexedDb.from_cache_symbol]) {
@@ -621,7 +640,7 @@ Document.setMethod(function toDry() {
621
640
  *
622
641
  * @author Jelle De Loecker <jelle@develry.be>
623
642
  * @since 1.0.4
624
- * @version 1.1.0
643
+ * @version 1.2.4
625
644
  *
626
645
  * @param {Object} record
627
646
  * @param {Object} options
@@ -662,13 +681,32 @@ Document.setMethod(function setDataRecord(record, options) {
662
681
  this.$record = record;
663
682
 
664
683
  if (Blast.isNode && this.constructor.namespace.indexOf('Alchemy.Document') == -1) {
665
- let field,
684
+ let delete_field = false,
685
+ added_private_field_info = false,
686
+ field,
666
687
  key;
667
688
 
668
689
  for (key in this.$main) {
669
690
  field = this.$model.schema.get(key);
670
691
 
671
- if (!field || field.is_private) {
692
+ if (!field) {
693
+ delete_field = true;
694
+ } else if (field.is_private) {
695
+ delete_field = true;
696
+
697
+ if (options.keep_private_fields) {
698
+ delete_field = false;
699
+
700
+ if (!added_private_field_info) {
701
+ added_private_field_info = true;
702
+
703
+ let fields = this.$model.schema.getPrivateFields();
704
+ options.private_fields = JSON.clone(fields, 'toHawkejs');
705
+ }
706
+ }
707
+ }
708
+
709
+ if (delete_field) {
672
710
  delete this.$main[key];
673
711
  }
674
712
  }
@@ -143,14 +143,37 @@ DocumentList.setMethod(function createIterator() {
143
143
  *
144
144
  * @author Jelle De Loecker <jelle@develry.be>
145
145
  * @since 1.0.0
146
- * @version 1.1.3
146
+ * @version 1.2.4
147
147
  *
148
148
  * @return {Object}
149
149
  */
150
150
  DocumentList.setMethod(function toDry() {
151
+
152
+ let options;
153
+
154
+ if (this.options) {
155
+ let val,
156
+ key;
157
+
158
+ options = {};
159
+
160
+ for (key in this.options) {
161
+ val = this.options[key];
162
+
163
+ if (key == 'options') {
164
+ val = Object.assign({}, val);
165
+ val.assoc_cache = undefined;
166
+ } else if (key == 'assoc_cache') {
167
+ continue;
168
+ }
169
+
170
+ options[key] = val;
171
+ }
172
+ }
173
+
151
174
  return {
152
175
  value: {
153
- options : this.options,
176
+ options : options,
154
177
  records : this.records,
155
178
  available : this.available,
156
179
  }
@@ -29,6 +29,9 @@ const FieldConfig = Fn.inherits('Alchemy.Base', 'Alchemy.Criteria', function Fie
29
29
  // The association this belongs to
30
30
  this.association = null;
31
31
 
32
+ // The main model this field is in
33
+ this.model = null;
34
+
32
35
  this.options = options || {};
33
36
 
34
37
  if (path) {
@@ -41,15 +44,24 @@ const FieldConfig = Fn.inherits('Alchemy.Base', 'Alchemy.Criteria', function Fie
41
44
  *
42
45
  * @author Jelle De Loecker <jelle@elevenways.be>
43
46
  * @since 1.1.3
44
- * @version 1.1.3
47
+ * @version 1.2.3
45
48
  *
46
49
  * @param {Object} obj
47
50
  *
48
51
  * @return {Alchemy.Form.FieldConfig}
49
52
  */
50
53
  FieldConfig.setStatic(function unDry(obj) {
51
- let set = new FieldConfig(obj.path, obj.options);
52
- return set;
54
+ let result = new FieldConfig(obj.path, obj.options);
55
+
56
+ if (obj.association) {
57
+ result.association = obj.association;
58
+ }
59
+
60
+ if (obj.model) {
61
+ result.model = obj.model;
62
+ }
63
+
64
+ return result;
53
65
  });
54
66
 
55
67
  /**
@@ -72,6 +84,94 @@ FieldConfig.setProperty(function title() {
72
84
  return title;
73
85
  });
74
86
 
87
+ /**
88
+ * Set the main model context this field is used for
89
+ * (Actual field can be in an association of this model)
90
+ *
91
+ * @author Jelle De Loecker <jelle@elevenways.be>
92
+ * @since 1.2.2
93
+ * @version 1.2.2
94
+ *
95
+ * @param {String|Model} model
96
+ */
97
+ FieldConfig.setMethod(function setContextModel(model) {
98
+
99
+ if (typeof model == 'string') {
100
+ this.model = model;
101
+ this._model = alchemy.getModel(model);
102
+ } else {
103
+ this._model = model;
104
+ this.model = model.model_name;
105
+ }
106
+ });
107
+
108
+ /**
109
+ * Get the context model this field is used for
110
+ *
111
+ * @author Jelle De Loecker <jelle@elevenways.be>
112
+ * @since 1.2.2
113
+ * @version 1.2.2
114
+ */
115
+ FieldConfig.setMethod(function getContextModel() {
116
+
117
+ if (this._model) {
118
+ return this._model;
119
+ }
120
+
121
+ if (!this.model) {
122
+ return;
123
+ }
124
+
125
+ this._model = alchemy.getModel(this.model);
126
+ return this._model;
127
+ });
128
+
129
+ /**
130
+ * Get the actual model this field is in
131
+ *
132
+ * @author Jelle De Loecker <jelle@elevenways.be>
133
+ * @since 1.2.2
134
+ * @version 1.2.2
135
+ */
136
+ FieldConfig.setMethod(function getModel() {
137
+
138
+ let model = this.getContextModel();
139
+
140
+ if (!model) {
141
+ return;
142
+ }
143
+
144
+ if (this.association) {
145
+ let config = model.getAssociation(this.association);
146
+
147
+ if (config) {
148
+ model = alchemy.getModel(config.modelName);
149
+ } else {
150
+ model = null;
151
+ }
152
+ }
153
+
154
+ return model;
155
+ });
156
+
157
+ /**
158
+ * Get the field definition
159
+ *
160
+ * @author Jelle De Loecker <jelle@elevenways.be>
161
+ * @since 1.2.2
162
+ * @version 1.2.2
163
+ */
164
+ FieldConfig.setMethod(function getFieldDefinition() {
165
+
166
+ let model = this.getModel();
167
+
168
+ if (!model) {
169
+ return;
170
+ }
171
+
172
+ return model.getField(this.local_path);
173
+ });
174
+
75
175
  /**
76
176
  * Return an object for json-drying this list
77
177
  *
@@ -103,6 +203,7 @@ FieldConfig.setMethod(function toJSON() {
103
203
  local_path : this.local_path,
104
204
  association : this.association,
105
205
  options : this.options,
206
+ model : this.model,
106
207
  };
107
208
  });
108
209
 
@@ -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;
@@ -1103,13 +1103,20 @@ Model.setMethod(function addAssociatedDataToRecord(criteria, item, callback) {
1103
1103
 
1104
1104
  aliases[alias] = function aliasRecordTask(nextAlias) {
1105
1105
 
1106
- var key;
1106
+ let pledge,
1107
+ key;
1107
1108
 
1108
1109
  if (options.create_references !== false) {
1109
1110
  key = alchemy.checksum(assoc_crit);
1110
1111
 
1111
- if (key != null && options.assoc_cache[key]) {
1112
- return nextAlias(null, options.assoc_cache[key]);
1112
+ if (key != null) {
1113
+ if (options.assoc_cache[key]) {
1114
+ Pledge.done(options.assoc_cache[key], nextAlias);
1115
+ return;
1116
+ } else {
1117
+ pledge = new Pledge();
1118
+ options.assoc_cache[key] = pledge;
1119
+ }
1113
1120
  }
1114
1121
  }
1115
1122
 
@@ -1125,6 +1132,11 @@ Model.setMethod(function addAssociatedDataToRecord(criteria, item, callback) {
1125
1132
  }
1126
1133
 
1127
1134
  if (err != null) {
1135
+
1136
+ if (pledge) {
1137
+ pledge.reject(err);
1138
+ }
1139
+
1128
1140
  return nextAlias(err);
1129
1141
  }
1130
1142
 
@@ -1140,6 +1152,10 @@ Model.setMethod(function addAssociatedDataToRecord(criteria, item, callback) {
1140
1152
 
1141
1153
  if (key) {
1142
1154
  options.assoc_cache[key] = result;
1155
+
1156
+ if (pledge) {
1157
+ pledge.resolve(result);
1158
+ }
1143
1159
  }
1144
1160
 
1145
1161
  nextAlias(null, result);
@@ -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
+ });
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
  */
@@ -386,7 +386,7 @@ Document.setMethod(function toDry() {
386
386
  *
387
387
  * @author Jelle De Loecker <jelle@develry.be>
388
388
  * @since 0.2.0
389
- * @version 1.1.0
389
+ * @version 1.2.4
390
390
  *
391
391
  * @param {WeakMap} wm
392
392
  *
@@ -412,7 +412,10 @@ Document.setMethod(function toHawkejs(wm) {
412
412
  // Sometimes we get an EMPTY $record value,
413
413
  // this is probably because it's already in the process of being clones
414
414
  if (!Object.isEmpty(record)) {
415
- result.setDataRecord(record);
415
+ // Get clean options
416
+ let options = JSON.clone(this.getCleanOptions(), 'toHawkejs', wm);
417
+
418
+ result.setDataRecord(record, options);
416
419
  } else {
417
420
  record.$record = record;
418
421
  }
@@ -425,6 +428,24 @@ Document.setMethod(function toHawkejs(wm) {
425
428
  return result;
426
429
  });
427
430
 
431
+ /**
432
+ * Keep private fields when sending to browser
433
+ *
434
+ * @author Jelle De Loecker <jelle@elevenways.be>
435
+ * @since 1.2.4
436
+ * @version 1.2.4
437
+ */
438
+ Document.setMethod(function keepPrivateFields(value) {
439
+
440
+ if (arguments.length == 0) {
441
+ value = true;
442
+ } else {
443
+ value = !!value;
444
+ }
445
+
446
+ this.$options.keep_private_fields = value;
447
+ });
448
+
428
449
  /**
429
450
  * Return the basic record for JSON
430
451
  *
@@ -536,27 +557,24 @@ Document.setMethod(function remove(callback) {
536
557
  /**
537
558
  * Add associated data to this record
538
559
  *
539
- * @author Jelle De Loecker <jelle@develry.be>
540
- * @since 0.2.0
541
- * @version 1.1.0
560
+ * @author Jelle De Loecker <jelle@elevenways.be>
561
+ * @since 1.2.3
562
+ * @version 1.2.3
542
563
  *
543
- * @param {Criteria} criteria
544
- * @param {Function} callback
564
+ * @param {Criteria|String} criteria
545
565
  *
546
- * @return {Pledge}
566
+ * @return {Criteria}
547
567
  */
548
- Document.setMethod(['populate', 'addAssociatedData'], function addAssociatedData(criteria, callback) {
568
+ Document.setMethod(function preparePopulationCriteria(criteria) {
549
569
 
550
- var that = this,
570
+ let type = typeof criteria,
551
571
  model = this.$model,
552
- type = typeof criteria,
553
572
  selects;
554
573
 
555
574
  if (type == 'string') {
556
575
  selects = [criteria];
557
576
  criteria = {};
558
577
  } else if (type == 'function') {
559
- callback = criteria;
560
578
  criteria = {};
561
579
  } else if (Array.isArray(criteria)) {
562
580
  selects = criteria;
@@ -583,7 +601,31 @@ Document.setMethod(['populate', 'addAssociatedData'], function addAssociatedData
583
601
  }
584
602
  }
585
603
 
586
- return model.addAssociatedDataToRecord(criteria, this.$record, callback);
604
+ return criteria;
605
+ });
606
+
607
+ /**
608
+ * Add associated data to this record
609
+ *
610
+ * @author Jelle De Loecker <jelle@develry.be>
611
+ * @since 0.2.0
612
+ * @version 1.2.3
613
+ *
614
+ * @param {Criteria} criteria
615
+ * @param {Function} callback
616
+ *
617
+ * @return {Pledge}
618
+ */
619
+ Document.setMethod(['populate', 'addAssociatedData'], function addAssociatedData(criteria, callback) {
620
+
621
+ if (typeof criteria == 'function') {
622
+ callback = criteria;
623
+ criteria = null;
624
+ }
625
+
626
+ criteria = this.preparePopulationCriteria(criteria);
627
+
628
+ return this.$model.addAssociatedDataToRecord(criteria, this.$record, callback);
587
629
  });
588
630
 
589
631
  /**
@@ -63,13 +63,23 @@ DocumentList.setMethod(function clone() {
63
63
  *
64
64
  * @author Jelle De Loecker <jelle@develry.be>
65
65
  * @since 1.1.0
66
- * @version 1.1.0
66
+ * @version 1.2.3
67
67
  *
68
68
  * @param {Criteria} criteria
69
69
  *
70
70
  * @return {Pledge}
71
71
  */
72
72
  DocumentList.setMethod(['populate', 'addAssociatedData'], function addAssociatedData(criteria) {
73
+
74
+ if (!this.records?.length) {
75
+ return;
76
+ }
77
+
78
+ let doc = this.records[0];
79
+
80
+ // Use the first doc to prepare the population criteria
81
+ criteria = doc.preparePopulationCriteria(criteria);
82
+
73
83
  return Function.forEach.parallel(this.records, function eachDoc(doc, key, next) {
74
84
  doc.populate(criteria).done(next);
75
85
  });
@@ -105,4 +115,26 @@ DocumentList.setMethod(function findNextBatch(callback) {
105
115
  }
106
116
 
107
117
  this.options.model.find('all', options, callback);
118
+ });
119
+
120
+ /**
121
+ * Keep private fields when sending to browser
122
+ *
123
+ * @author Jelle De Loecker <jelle@elevenways.be>
124
+ * @since 1.2.4
125
+ * @version 1.2.4
126
+ */
127
+ DocumentList.setMethod(function keepPrivateFields(value) {
128
+
129
+ if (arguments.length == 0) {
130
+ value = true;
131
+ } else {
132
+ value = !!value;
133
+ }
134
+
135
+ let record;
136
+
137
+ for (record of this) {
138
+ record.keepPrivateFields(value);
139
+ }
108
140
  });
@@ -5,9 +5,7 @@
5
5
  * @since 1.0.0
6
6
  * @version 1.0.0
7
7
  */
8
- var Element = Function.inherits('Hawkejs.Element', 'Alchemy.Element', function Element() {
9
- return Element.super.call(this);
10
- });
8
+ const Element = Function.inherits('Hawkejs.Element', 'Alchemy.Element', 'Element');
11
9
 
12
10
  /**
13
11
  * The default element prefix (when element contains no hyphen) is "al"
@@ -111,4 +109,20 @@ Element.setMethod(function getModel(model_name, options) {
111
109
  this._model_instances[model_name] = instance;
112
110
 
113
111
  return instance;
112
+ });
113
+
114
+ /**
115
+ * Default translation implementation
116
+ *
117
+ * @author Jelle De Loecker <jelle@elevenways.be>
118
+ * @since 1.2.4
119
+ * @version 1.2.4
120
+ *
121
+ * @param {String} key
122
+ * @param {Object} parameters
123
+ *
124
+ * @return {String}
125
+ */
126
+ Element.setMethod(function __(key, parameters) {
127
+ return key;
114
128
  });
@@ -105,12 +105,17 @@ Field.setStatic(function createPathEvaluator(path) {
105
105
  *
106
106
  * @author Jelle De Loecker <jelle@develry.be>
107
107
  * @since 1.0.0
108
- * @version 1.0.0
108
+ * @version 1.2.4
109
109
  *
110
110
  * @type {Boolean}
111
111
  */
112
112
  Field.setProperty(function is_private() {
113
- return !!this.options.is_private;
113
+
114
+ if (this.options.is_private || this.options.private) {
115
+ return true;
116
+ }
117
+
118
+ return false;
114
119
  });
115
120
 
116
121
  /**
@@ -362,7 +362,7 @@ Model.setStatic(function checkPathValue(value, name, field_name, conduit) {
362
362
  *
363
363
  * @author Jelle De Loecker <jelle@develry.be>
364
364
  * @since 0.2.0
365
- * @version 1.0.7
365
+ * @version 1.2.4
366
366
  *
367
367
  * @return {Alchemy.Field}
368
368
  */
@@ -380,12 +380,10 @@ Model.setStatic(function addField(name, type, options) {
380
380
  // Add it to the Document class
381
381
  this.Document.setFieldGetter(name);
382
382
 
383
- // Add the field to the client document too, if it's not private
384
- if (!field.is_private) {
385
- // False means it should not be set on the server implementation
386
- // (because that's where it's coming from)
387
- this.ClientDocument.setFieldGetter(name, null, null, false);
388
- }
383
+ // False means it should not be set on the server implementation
384
+ // (because that's where it's coming from)
385
+ // Yes, this also sets private fields on the server-side client document.
386
+ this.ClientDocument.setFieldGetter(name, null, null, false);
389
387
  }
390
388
 
391
389
  return field;
@@ -409,18 +407,16 @@ Model.setStatic(function addBehaviour(behaviour_name, options) {
409
407
  *
410
408
  * @author Jelle De Loecker <jelle@develry.be>
411
409
  * @since 0.2.0
412
- * @version 0.2.0
410
+ * @version 1.2.4
413
411
  */
414
412
  Model.setStatic(function addAssociation(type, alias, model_name, options) {
415
413
  var data = this.schema.addAssociation(type, alias, model_name, options);
416
414
  this.Document.setAliasGetter(data.alias);
417
415
 
418
- // Add the alias to the client document too, if it's not private
419
- if (!options || !options.is_private) {
420
- // False means it should not be set on the server implementation
421
- // (because that's where it's coming from)
422
- this.ClientDocument.setAliasGetter(name);
423
- }
416
+ // False means it should not be set on the server implementation
417
+ // (because that's where it's coming from)
418
+ // Yes, this also sets private fields on the server-side client document.
419
+ this.ClientDocument.setAliasGetter(name);
424
420
  });
425
421
 
426
422
  /**