alchemymvc 1.3.0 → 1.3.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.
@@ -105,9 +105,9 @@ HttpConduit.enforceProperty(function fingerprint() {
105
105
  /**
106
106
  * Init
107
107
  *
108
- * @author Jelle De Loecker <jelle@kipdola.be>
108
+ * @author Jelle De Loecker <jelle@elevenways.be>
109
109
  * @since 0.3.3
110
- * @version 1.2.0
110
+ * @version 1.3.1
111
111
  *
112
112
  * @param {IncomingMessage} req
113
113
  * @param {ServerResponse} res
@@ -139,6 +139,11 @@ HttpConduit.setMethod(async function initHttp(req, res, router) {
139
139
 
140
140
  this.debugMark(false);
141
141
 
142
+ if (this.shouldBePostponed()) {
143
+ this.postponeAndQueue();
144
+ return;
145
+ }
146
+
142
147
  // Call the middleware, which will call the handler afterwards
143
148
  this.callMiddleware();
144
149
  });
@@ -102,19 +102,34 @@ var SocketConduit = Function.inherits('Alchemy.Conduit', function Socket(socket,
102
102
  /**
103
103
  * Return the client IP address
104
104
  *
105
- * @author Jelle De Loecker <jelle@develry.be>
105
+ * @author Jelle De Loecker <jelle@elevenways.be>
106
106
  * @since 0.2.1
107
- * @version 0.2.1
107
+ * @version 1.3.1
108
108
  */
109
109
  SocketConduit.setProperty(function ip() {
110
110
 
111
- var sock = this.socket;
111
+ let handshake = this.socket?.handshake;
112
112
 
113
- if (!sock) {
113
+ if (!handshake) {
114
114
  return null;
115
115
  }
116
116
 
117
- return sock.conn.remoteAddress || null;
117
+ if (handshake.headers) {
118
+ let forwarded_for = handshake.headers['x-forwarded-for'] || handshake.headers['x-real-ip'];
119
+
120
+ if (forwarded_for) {
121
+
122
+ // Forwarded for can contain multiple ip addresses,
123
+ // return the first one
124
+ if (forwarded_for.indexOf(',') > -1) {
125
+ forwarded_for = forwarded_for.before(',');
126
+ }
127
+
128
+ return forwarded_for;
129
+ }
130
+ }
131
+
132
+ return handshake.address || null;
118
133
  });
119
134
 
120
135
  /**
@@ -130,20 +130,16 @@ Info.setAction(async function appcache(conduit) {
130
130
  *
131
131
  * @author Jelle De Loecker <jelle@elevenways.be>
132
132
  * @since 1.1.0
133
- * @version 1.1.0
133
+ * @version 1.3.1
134
134
  */
135
135
  Info.setAction(function postponed(conduit, id) {
136
136
 
137
137
  var session = conduit.getSession(),
138
- postponed_conduit = session.postponed.get(id);
138
+ postponement = session.getPostponement(id);
139
139
 
140
- if (!postponed_conduit) {
140
+ if (!postponement) {
141
141
  return conduit.notFound();
142
142
  }
143
143
 
144
- postponed_conduit.response = conduit.response;
145
-
146
- postponed_conduit._end(...postponed_conduit._end_arguments);
147
-
148
- session.postponed.remove(id);
144
+ postponement.handleRequest(conduit);
149
145
  });
@@ -77,12 +77,12 @@ Backed.enforceProperty(function backing(new_value, old_value) {
77
77
  *
78
78
  * @author Jelle De Loecker <jelle@elevenways.be>
79
79
  * @since 1.2.1
80
- * @version 1.2.1
80
+ * @version 1.3.1
81
81
  *
82
82
  * @return {Backed}
83
83
  */
84
84
  Backed.setMethod(function clone() {
85
- let result = new Backed(this.backing);
85
+ let result = new this.constructor(this.backing);
86
86
  result.local = new Map(this.local);
87
87
  return result;
88
88
  });
@@ -86,9 +86,9 @@ Router.setMethod(function isLocalUrl(url) {
86
86
  /**
87
87
  * Apply directive to an element
88
88
  *
89
- * @author Jelle De Loecker <jelle@develry.be>
89
+ * @author Jelle De Loecker <jelle@elevenways.be>
90
90
  * @since 1.1.0
91
- * @version 1.2.7
91
+ * @version 1.3.1
92
92
  *
93
93
  * @param {Element} element The element to apply to
94
94
  * @param {String} name The route name
@@ -120,14 +120,23 @@ Router.setMethod(function applyDirective(element, name, options) {
120
120
  if (config.keys && config.keys.length) {
121
121
 
122
122
  let key,
123
- val,
124
- i;
123
+ val;
125
124
 
126
- for (i = 0; i < config.keys.length; i++) {
127
- key = config.keys[i];
125
+ for (key of config.keys) {
128
126
 
129
127
  if (params[key] == null) {
130
- if ((val = element['route_' + key]) == null) {
128
+
129
+ val = element['route_' + key];
130
+
131
+ if (val == null) {
132
+ let variables = element[Hawkejs.VARIABLES];
133
+
134
+ if (variables) {
135
+ val = variables[key];
136
+ }
137
+ }
138
+
139
+ if (val == null) {
131
140
  val = element[key];
132
141
  }
133
142
 
@@ -93,7 +93,7 @@ SchemaField.setProperty(function requires_translating() {
93
93
  *
94
94
  * @author Jelle De Loecker <jelle@develry.be>
95
95
  * @since 0.2.0
96
- * @version 1.2.1
96
+ * @version 1.3.1
97
97
  *
98
98
  * @param {Object} record
99
99
  * @param {String} some_path Some path to a field in the wanted schema
@@ -102,29 +102,23 @@ SchemaField.setProperty(function requires_translating() {
102
102
  */
103
103
  SchemaField.setMethod(function getSubschema(record, some_path) {
104
104
 
105
- var schema = this.options.schema,
106
- external_field_name,
107
- property_name,
108
- record_value,
109
- pieces,
110
- field,
111
- name;
105
+ let schema = this.options.schema;
112
106
 
113
107
  // If schema is a string,
114
108
  // it needs to be extracted from another field's value
115
109
  if (typeof schema == 'string') {
116
110
 
117
111
  // When there are 2 pieces, the second piece is the property name
118
- pieces = schema.split('.');
112
+ let pieces = schema.split('.');
119
113
 
120
114
  // The first piece is the external field name
121
- external_field_name = pieces[0];
115
+ let external_field_name = pieces[0];
122
116
 
123
117
  // The second piece is the property name
124
- property_name = pieces[1] || 'schema';
118
+ let property_name = pieces[1] || 'schema';
125
119
 
126
120
  // Get that other field by its name
127
- field = this.schema.getField(external_field_name);
121
+ let field = this.schema.getField(external_field_name);
128
122
 
129
123
  if (!field) {
130
124
  console.error('Failed to get subschema', external_field_name, 'of', this.schema, some_path);
@@ -139,7 +133,7 @@ SchemaField.setMethod(function getSubschema(record, some_path) {
139
133
  }
140
134
 
141
135
  // Now get the actual external value from the record
142
- record_value = field.getRecordValue(record);
136
+ let record_value = field.getRecordValue(record);
143
137
 
144
138
  // I'm not sure if this will help
145
139
  if (record_value == null) {
@@ -155,6 +149,8 @@ SchemaField.setMethod(function getSubschema(record, some_path) {
155
149
  schema = enum_value.schema;
156
150
  } else if (enum_value.value) {
157
151
  schema = enum_value.value[property_name] || enum_value.value.schema;
152
+ } else {
153
+ console.log('Could not find', schema, 'in', record, 'enum values:', enum_value, 'of field', field)
158
154
  }
159
155
  }
160
156
 
@@ -166,7 +162,7 @@ SchemaField.setMethod(function getSubschema(record, some_path) {
166
162
  *
167
163
  * @author Jelle De Loecker <jelle@develry.be>
168
164
  * @since 0.2.0
169
- * @version 0.4.0
165
+ * @version 1.3.1
170
166
  *
171
167
  * @param {Object} value Value of field, an object in this case
172
168
  * @param {Object} data The data object containing `value`
@@ -174,18 +170,21 @@ SchemaField.setMethod(function getSubschema(record, some_path) {
174
170
  *
175
171
  * @return {Object}
176
172
  */
177
- SchemaField.setMethod(function _toDatasource(value, data, datasource, callback) {
173
+ SchemaField.setMethod(function _toDatasource(value, holder, datasource, callback) {
178
174
 
179
175
  var that = this,
180
176
  sub_schema,
181
177
  record,
182
178
  model,
183
179
  temp;
184
-
185
- record = {};
186
-
187
- // Recreate a record
188
- record[this.schema.name] = data;
180
+
181
+ if (this.schema.name) {
182
+ record = {
183
+ [this.schema.name] : holder,
184
+ };
185
+ } else {
186
+ record = holder;
187
+ }
189
188
 
190
189
  sub_schema = this.getSubschema(record);
191
190
 
@@ -217,7 +216,7 @@ SchemaField.setMethod(function _toDatasource(value, data, datasource, callback)
217
216
  }
218
217
 
219
218
  // Add the newly found data
220
- temp = Object.assign({}, data);
219
+ temp = Object.assign({}, holder);
221
220
  record = {};
222
221
  record[that.schema.name] = temp;
223
222
 
@@ -240,7 +239,7 @@ SchemaField.setMethod(function _toDatasource(value, data, datasource, callback)
240
239
  });
241
240
 
242
241
  /**
243
- * Get some more subschema data
242
+ * Turn datasource data into app data
244
243
  *
245
244
  * @author Jelle De Loecker <jelle@develry.be>
246
245
  * @since 0.2.0
@@ -323,11 +322,19 @@ SchemaField.setMethod(function _toApp(query, options, value, callback) {
323
322
  return;
324
323
  }
325
324
 
326
- // Create new dummy record
327
- let record = {};
325
+ let record;
328
326
 
329
- // Recreate the record
330
- record[this.schema.name] = value;
327
+ if (options.parent_value) {
328
+ record = {
329
+ [this.schema.name] : options.parent_value
330
+ };
331
+ } else {
332
+ record = {
333
+ [this.schema.name] : {
334
+ [this.name] : value,
335
+ }
336
+ };
337
+ }
331
338
 
332
339
  let sub_schema = this.getSubschema(record);
333
340
 
@@ -335,13 +342,20 @@ SchemaField.setMethod(function _toApp(query, options, value, callback) {
335
342
  if (sub_schema) {
336
343
  let tasks = {};
337
344
 
338
- Object.each(value, function eachField(field_value, field_name) {
345
+ Object.each(value, (field_value, field_name) => {
339
346
 
340
- var field = sub_schema.get(field_name);
347
+ let field = sub_schema.get(field_name);
341
348
 
342
349
  if (field != null) {
343
- tasks[field_name] = function doToDatasource(next) {
344
- field.toApp({}, {_root_data: options._root_data}, field_value, next);
350
+
351
+ let sub_options = {
352
+ _root_data: options._root_data,
353
+ parent_field_schema_name: this.name,
354
+ parent_value : value,
355
+ };
356
+
357
+ tasks[field_name] = (next) => {
358
+ field.toApp({}, sub_options, field_value, next);
345
359
  };
346
360
  }
347
361
  });
@@ -1,4 +1,4 @@
1
- var model_info,
1
+ let class_cache = new Map(),
2
2
  cache_stores_in_progress,
3
3
  did_check = Symbol('did_check');
4
4
 
@@ -7,16 +7,11 @@ if (Blast.isBrowser) {
7
7
 
8
8
  Blast.once('hawkejs_init', function gotScene(hawkejs, variables, settings, renderer) {
9
9
 
10
- var model_name,
11
- DocClass,
12
- config,
13
- key;
14
-
15
- model_info = hawkejs.scene.exposed.model_info;
16
-
17
- for (model_name in model_info) {
18
- config = model_info[model_name];
19
- DocClass = Document.getDocumentClass(model_name);
10
+ let DocClass,
11
+ config;
12
+
13
+ for (config of hawkejs.scene.exposed.model_info) {
14
+ DocClass = Document.getDocumentClass(config.name);
20
15
 
21
16
  for (key in config.schema.dict) {
22
17
  DocClass.setFieldGetter(key);
@@ -218,25 +213,18 @@ Document.setStatic(function getClassForUndry(class_name) {
218
213
  *
219
214
  * @author Jelle De Loecker <jelle@develry.be>
220
215
  * @since 1.0.0
221
- * @version 1.1.0
216
+ * @version 1.3.1
222
217
  *
223
218
  * @param {Object|String} model
224
219
  */
225
220
  Document.setStatic(function getDocumentClass(model) {
226
221
 
227
- var doc_constructor,
228
- document_name,
229
- parent_path,
230
- model_name,
231
- DocClass,
232
- doc_path,
233
- config,
234
- key;
235
-
236
222
  if (!model) {
237
223
  throw new Error('Can not get Hawkejs.Document class for non-existing model');
238
224
  }
239
225
 
226
+ let model_name;
227
+
240
228
  if (typeof model == 'function') {
241
229
  model_name = model.name;
242
230
  } else if (typeof model == 'string') {
@@ -246,26 +234,32 @@ Document.setStatic(function getDocumentClass(model) {
246
234
  }
247
235
 
248
236
  // Construct the name of the document class
249
- document_name = model_name;
237
+ let document_name = model_name;
238
+
239
+ if (class_cache.has(document_name)) {
240
+ return class_cache.get(document_name);
241
+ }
250
242
 
251
243
  // Construct the path to this class
252
- doc_path = 'Alchemy.Client.Document.' + document_name;
244
+ let doc_path = 'Alchemy.Client.Document.' + document_name;
253
245
 
254
246
  // Get the class
255
- DocClass = Object.path(Blast.Classes, doc_path);
247
+ let DocClass = Object.path(Blast.Classes, doc_path);
256
248
 
257
249
  if (DocClass == null) {
258
- doc_constructor = Function.create(document_name, function DocumentConstructor(record, options) {
250
+ let doc_constructor = Function.create(document_name, function DocumentConstructor(record, options) {
259
251
  DocumentConstructor.wrapper.super.call(this, record, options);
260
252
  });
261
253
 
262
- if (Blast.isBrowser && window._hawkejs_static_expose) {
263
- let exposed = window._hawkejs_static_expose;
254
+ let parent,
255
+ config;
256
+
257
+ if (Blast.isBrowser) {
264
258
 
265
- config = exposed.model_info;
259
+ let model = Blast.Classes.Alchemy.Client.Model.Model.getClass(model_name);
266
260
 
267
- if (config) {
268
- config = config[model_name];
261
+ if (model && model.super) {
262
+ parent = model.super.name;
269
263
  }
270
264
  } else if (Blast.isNode) {
271
265
  config = alchemy.getModel(model_name, false);
@@ -277,13 +271,17 @@ Document.setStatic(function getDocumentClass(model) {
277
271
  }
278
272
  }
279
273
 
280
- parent_path = 'Alchemy.Client.Document';
274
+ let parent_path = 'Alchemy.Client.Document';
281
275
 
282
276
  if (config && config.parent) {
277
+ parent = config.parent;
278
+ }
279
+
280
+ if (parent && parent != 'Model') {
283
281
  // Make sure the parent class exists
284
- getDocumentClass(config.parent);
282
+ getDocumentClass(parent);
285
283
 
286
- parent_path += '.' + config.parent;
284
+ parent_path += '.' + parent;
287
285
  }
288
286
 
289
287
  DocClass = Function.inherits(parent_path, doc_constructor);
@@ -297,6 +295,8 @@ Document.setStatic(function getDocumentClass(model) {
297
295
  DocClass.Model = Blast.Classes.Hawkejs.Model.getClass(model_name);
298
296
  }
299
297
 
298
+ class_cache.set(document_name, DocClass);
299
+
300
300
  return DocClass;
301
301
  });
302
302
 
@@ -1068,12 +1068,16 @@ Document.setMethod(function informDatasource(options, callback) {
1068
1068
  /**
1069
1069
  * Store the current data as the original record
1070
1070
  *
1071
- * @author Jelle De Loecker <jelle@develry.be>
1071
+ * @author Jelle De Loecker <jelle@elevenways.be>
1072
1072
  * @since 1.0.4
1073
- * @version 1.0.4
1073
+ * @version 1.3.1
1074
1074
  */
1075
1075
  Document.setMethod(function storeCurrentDataAsOriginalRecord() {
1076
- this.$attributes.original_record = JSON.clone(this.$main);
1076
+ try {
1077
+ this.$attributes.original_record = JSON.clone(this.$main);
1078
+ } catch (err) {
1079
+ alchemy.distinctProblem('store_current_data_error', err.message, {error: err});
1080
+ }
1077
1081
  });
1078
1082
 
1079
1083
  /**
@@ -1,21 +1,16 @@
1
- var fallback_datasource,
2
- model_info,
1
+ let class_cache = new Map(),
2
+ fallback_datasource,
3
3
  TABLE = Symbol('table');
4
4
 
5
5
  if (Blast.isBrowser) {
6
6
  Blast.once('hawkejs_init', function gotScene(hawkejs, variables, settings, view) {
7
7
 
8
- var ModelClass,
9
- model_name,
10
- config,
11
- key;
12
-
13
- model_info = hawkejs.scene.exposed.model_info;
8
+ let config;
14
9
 
15
- for (model_name in model_info) {
16
- config = model_info[model_name];
17
- ModelClass = Model.getClass(model_name);
18
- config.schema.setModel(model_name);
10
+ for (config of hawkejs.scene.exposed.model_info) {
11
+ // First get or create the client-side Model class,
12
+ // then set some extra configuration coming from the server-side
13
+ Model.getClass(config.name).setModelConfig(config);
19
14
  }
20
15
  });
21
16
  }
@@ -90,6 +85,30 @@ Model.setStatic(function hasServerAction(action) {
90
85
  return !!config;
91
86
  });
92
87
 
88
+ /**
89
+ * Set the Model configuration
90
+ *
91
+ * @author Jelle De Loecker <jelle@elevenways.be>
92
+ * @since 1.3.1
93
+ * @version 1.3.1
94
+ *
95
+ * @param {Object} config
96
+ */
97
+ Model.setStatic(function setModelConfig(config) {
98
+
99
+ let model_name = this.model_name;
100
+
101
+ config.schema.setModel(model_name);
102
+
103
+ if (!this.prototype.hasOwnProperty('primary_key')) {
104
+ this.setProperty('primary_key', config.primary_key);
105
+ }
106
+
107
+ if (!this.prototype.hasOwnProperty('display_field')) {
108
+ this.setProperty('display_field', config.display_field);
109
+ }
110
+ });
111
+
93
112
  /**
94
113
  * Table name to use in the database.
95
114
  * False if no table should be used.
@@ -201,50 +220,43 @@ Model.prepareStaticProperty('Document', function getDocumentClass() {
201
220
  *
202
221
  * @author Jelle De Loecker <jelle@develry.be>
203
222
  * @since 1.0.0
204
- * @version 1.2.7
223
+ * @version 1.3.1
205
224
  *
206
225
  * @param {String} model_name
207
226
  * @param {Boolean} allow_create
227
+ * @param {String} parent
208
228
  */
209
- Model.setStatic(function getClass(model_name, allow_create ) {
210
-
211
- var model_constructor,
212
- parent_path,
213
- class_name,
214
- class_path,
215
- ModelClass,
216
- config,
217
- key;
229
+ Model.setStatic(function getClass(model_name, allow_create, parent) {
230
+
231
+ if (class_cache.has(model_name)) {
232
+ return class_cache.get(model_name);
233
+ }
218
234
 
219
235
  // Construct the name of the class
220
- class_name = model_name;
236
+ let class_name = model_name;
221
237
 
222
238
  // Construct the path to this class
223
- class_path = 'Alchemy.Client.Model.' + class_name;
239
+ let class_path = 'Alchemy.Client.Model.' + class_name;
224
240
 
225
241
  // Get the class
226
- ModelClass = Object.path(Blast.Classes, class_path);
242
+ let ModelClass = Object.path(Blast.Classes, class_path);
227
243
 
228
244
  if (allow_create == null) {
229
245
  allow_create = true;
230
246
  }
231
247
 
232
248
  if (ModelClass == null && allow_create) {
233
- model_constructor = Function.create(class_name, function ModelConstructor(record, options) {
249
+ let config;
250
+
251
+ let model_constructor = Function.create(class_name, function ModelConstructor(record, options) {
234
252
  ModelConstructor.wrapper.super.call(this, record, options);
235
253
  });
236
254
 
237
255
  // @TODO: inherit from parents
238
- parent_path = 'Alchemy.Client.Model';
239
-
240
- if (Blast.isBrowser && window._hawkejs_static_expose) {
241
- let exposed = window._hawkejs_static_expose;
242
-
243
- config = exposed.model_info;
256
+ let parent_path = 'Alchemy.Client.Model';
244
257
 
245
- if (config) {
246
- config = config[model_name];
247
- }
258
+ if (Blast.isBrowser) {
259
+ // No longer needed
248
260
  } else if (Blast.isNode) {
249
261
  config = alchemy.getModel(model_name, false);
250
262
 
@@ -258,8 +270,12 @@ Model.setStatic(function getClass(model_name, allow_create ) {
258
270
  }
259
271
 
260
272
  if (config && config.parent) {
261
- getClass(config.parent);
262
- parent_path += '.' + config.parent;
273
+ parent = config.parent;
274
+ }
275
+
276
+ if (parent) {
277
+ getClass(parent);
278
+ parent_path += '.' + parent;
263
279
  }
264
280
 
265
281
  ModelClass = Function.inherits(parent_path, model_constructor);
@@ -281,6 +297,8 @@ Model.setStatic(function getClass(model_name, allow_create ) {
281
297
  }
282
298
  }
283
299
 
300
+ class_cache.set(model_name, ModelClass);
301
+
284
302
  return ModelClass;
285
303
  });
286
304
 
@@ -1502,11 +1520,14 @@ Model.setProperty(function model_info() {
1502
1520
  return {};
1503
1521
  }
1504
1522
 
1505
- if (!hawkejs.scene.exposed.model_info[name]) {
1506
- hawkejs.scene.exposed.model_info[name] = {};
1507
- }
1523
+ let config;
1508
1524
 
1509
- data = hawkejs.scene.exposed.model_info[name];
1525
+ for (config of hawkejs.scene.exposed.model_info) {
1526
+ if (config.name === name) {
1527
+ data = config;
1528
+ break;
1529
+ }
1530
+ }
1510
1531
  } else {
1511
1532
 
1512
1533
  let MainClass = Blast.Classes.Alchemy.Model[name];
@@ -1724,7 +1745,7 @@ Model.setMethod(function compose(data, options) {
1724
1745
  *
1725
1746
  * @author Jelle De Loecker <jelle@develry.be>
1726
1747
  * @since 0.2.0
1727
- * @version 1.1.1
1748
+ * @version 1.3.1
1728
1749
  *
1729
1750
  * @param {Object} data The record data to check
1730
1751
  * @param {Object} options
@@ -1750,6 +1771,10 @@ Model.setMethod(function createRecord(data, options, callback) {
1750
1771
  return callback(err);
1751
1772
  }
1752
1773
 
1774
+ if (!that.datasource) {
1775
+ return callback(new Error('Model "' + that.model_name + '" has no datasource'));
1776
+ }
1777
+
1753
1778
  that.datasource.create(that, data, options, function afterCreate(err, result) {
1754
1779
 
1755
1780
  if (err != null) {
@@ -1768,7 +1793,7 @@ Model.setMethod(function createRecord(data, options, callback) {
1768
1793
  *
1769
1794
  * @author Jelle De Loecker <jelle@develry.be>
1770
1795
  * @since 0.2.0
1771
- * @version 1.1.1
1796
+ * @version 1.3.1
1772
1797
  *
1773
1798
  * @param {Object} data The record data to check
1774
1799
  * @param {Object} options
@@ -1795,6 +1820,10 @@ Model.setMethod(function updateRecord(data, options, callback) {
1795
1820
  return callback(err);
1796
1821
  }
1797
1822
 
1823
+ if (!that.datasource) {
1824
+ return callback(new Error('Model "' + that.model_name + '" has no datasource'));
1825
+ }
1826
+
1798
1827
  that.datasource.update(that, data, options, function afterUpdate(err, result) {
1799
1828
 
1800
1829
  if (err != null) {