alchemymvc 1.3.15 → 1.3.16

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.
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @author Jelle De Loecker <jelle@develry.be>
8
8
  * @since 0.1.0
9
- * @version 0.3.0
9
+ * @version 1.3.16
10
10
  */
11
11
  var Sluggable = Function.inherits('Alchemy.Behaviour', function SluggableBehaviour(model, options) {
12
12
 
@@ -19,6 +19,10 @@ var Sluggable = Function.inherits('Alchemy.Behaviour', function SluggableBehavio
19
19
  this.target_field = options.target_field;
20
20
  this.replacement = options.replacement;
21
21
 
22
+ if (!this.source_field) {
23
+ throw new Error('No source field found for sluggable behaviour in "' + this.model.model_name + '"');
24
+ }
25
+
22
26
  if (!options.unique_modifier_fields) {
23
27
  options.unique_modifier_fields = [];
24
28
  } else if (!Array.isArray(options.unique_modifier_fields)) {
@@ -132,6 +132,19 @@ SocketConduit.setProperty(function ip() {
132
132
  return handshake.address || null;
133
133
  });
134
134
 
135
+ /**
136
+ * Is this client still connected?
137
+ *
138
+ * @author Jelle De Loecker <jelle@elevenways.be>
139
+ * @since 1.3.16
140
+ * @version 1.3.16
141
+ *
142
+ * @type {Boolean}
143
+ */
144
+ SocketConduit.setProperty(function is_connected() {
145
+ return this.socket?.connected || false;
146
+ });
147
+
135
148
  /**
136
149
  * Parse the request, get information from the url
137
150
  *
@@ -5,9 +5,9 @@ var MongoClient = alchemy.use('mongodb').MongoClient;
5
5
  *
6
6
  * @author Jelle De Loecker <jelle@develry.be>
7
7
  * @since 0.2.0
8
- * @version 1.1.0
8
+ * @version 1.3.16
9
9
  */
10
- var Mongo = Function.inherits('Alchemy.Datasource.Nosql', function Mongo(name, _options) {
10
+ const Mongo = Function.inherits('Alchemy.Datasource.Nosql', function Mongo(name, _options) {
11
11
 
12
12
  var options,
13
13
  uri;
@@ -50,12 +50,6 @@ var Mongo = Function.inherits('Alchemy.Datasource.Nosql', function Mongo(name, _
50
50
 
51
51
  // Set the connection options
52
52
  this.mongoOptions = {
53
-
54
- // Use the new url parser, because otherwise it logs a warning
55
- useNewUrlParser : true,
56
-
57
- // Enable the new unified topology layer
58
- useUnifiedTopology : true,
59
53
  };
60
54
 
61
55
  // Cache collections in here
@@ -144,7 +138,7 @@ Mongo.setMethod(function normalizeFindOptions(options) {
144
138
  *
145
139
  * @author Jelle De Loecker <jelle@develry.be>
146
140
  * @since 0.2.0
147
- * @version 1.1.0
141
+ * @version 1.3.16
148
142
  */
149
143
  Mongo.decorateMethod(Blast.Decorators.memoize({ignore_arguments: true}), function connect() {
150
144
 
@@ -160,7 +154,7 @@ Mongo.decorateMethod(Blast.Decorators.memoize({ignore_arguments: true}), functio
160
154
  pledge = new Pledge();
161
155
 
162
156
  // Create the connection to the database
163
- MongoClient.connect(that.uri, that.mongoOptions, function connected(err, client) {
157
+ Pledge.done(MongoClient.connect(that.uri, that.mongoOptions), function connected(err, client) {
164
158
 
165
159
  if (err) {
166
160
  that.connection_error = err;
@@ -186,7 +180,7 @@ Mongo.decorateMethod(Blast.Decorators.memoize({ignore_arguments: true}), functio
186
180
  *
187
181
  * @author Jelle De Loecker <jelle@develry.be>
188
182
  * @since 0.2.0
189
- * @version 0.2.0
183
+ * @version 1.3.16
190
184
  *
191
185
  * @param {Function} callback
192
186
  */
@@ -208,7 +202,7 @@ Mongo.setMethod(function collection(name, callback) {
208
202
  return callback(err);
209
203
  }
210
204
 
211
- that.connection.collection(name, function createdCollection(err, collection) {
205
+ Pledge.done(that.connection.collection(name), function createdCollection(err, collection) {
212
206
 
213
207
  if (err) {
214
208
  return callback(err);
@@ -228,7 +222,7 @@ Mongo.setMethod(function collection(name, callback) {
228
222
  *
229
223
  * @author Jelle De Loecker <jelle@develry.be>
230
224
  * @since 0.2.0
231
- * @version 1.2.0
225
+ * @version 1.3.16
232
226
  */
233
227
  Mongo.setMethod(function _read(model, criteria, callback) {
234
228
 
@@ -268,11 +262,6 @@ Mongo.setMethod(function _read(model, criteria, callback) {
268
262
 
269
263
  let aggregate_options = {};
270
264
 
271
- // Limits can still be set as an option though
272
- if (options.limit) {
273
- aggregate_options.limit = options.limit;
274
- }
275
-
276
265
  Function.parallel({
277
266
  available: function getAvailable(next) {
278
267
 
@@ -286,13 +275,13 @@ Mongo.setMethod(function _read(model, criteria, callback) {
286
275
  pipeline.push({$count: 'available'});
287
276
 
288
277
  // Expensive aggregate just to get the available count...
289
- collection.aggregate(pipeline, cloned_options, function gotAggregate(err, cursor) {
278
+ Pledge.done(collection.aggregate(pipeline, cloned_options), function gotAggregate(err, cursor) {
290
279
 
291
280
  if (err) {
292
281
  return next(err);
293
282
  }
294
283
 
295
- cursor.toArray(function gotAvailableArray(err, items) {
284
+ Pledge.done(cursor.toArray(), function gotAvailableArray(err, items) {
296
285
 
297
286
  if (err) {
298
287
  return next(err);
@@ -313,13 +302,22 @@ Mongo.setMethod(function _read(model, criteria, callback) {
313
302
  });
314
303
  },
315
304
  items: function getItems(next) {
316
- collection.aggregate(compiled.pipeline, aggregate_options, function gotAggregate(err, cursor) {
305
+
306
+ let pipeline = JSON.clone(compiled.pipeline);
307
+
308
+ // Limits also have to be set in the pipeline now
309
+ // (We have to do it here, so the `available` count is correct)
310
+ if (options.limit) {
311
+ pipeline.push({$limit: options.limit});
312
+ }
313
+
314
+ Pledge.done(collection.aggregate(pipeline, aggregate_options), function gotAggregate(err, cursor) {
317
315
 
318
316
  if (err) {
319
317
  return next(err);
320
318
  }
321
319
 
322
- cursor.toArray(next);
320
+ Pledge.done(cursor.toArray(), next);
323
321
  });
324
322
  }
325
323
  }, function done(err, data) {
@@ -346,10 +344,10 @@ Mongo.setMethod(function _read(model, criteria, callback) {
346
344
  return next(null, null);
347
345
  }
348
346
 
349
- cursor.count(false, next);
347
+ Pledge.done(collection.countDocuments(compiled), next);
350
348
  },
351
349
  items: function getItems(next) {
352
- cursor.toArray(next);
350
+ Pledge.done(cursor.toArray(), next);
353
351
  }
354
352
  }, function done(err, data) {
355
353
 
@@ -369,7 +367,7 @@ Mongo.setMethod(function _read(model, criteria, callback) {
369
367
  *
370
368
  * @author Jelle De Loecker <jelle@develry.be>
371
369
  * @since 0.2.0
372
- * @version 1.3.6
370
+ * @version 1.3.16
373
371
  */
374
372
  Mongo.setMethod(function _create(model, data, options, callback) {
375
373
 
@@ -379,13 +377,7 @@ Mongo.setMethod(function _create(model, data, options, callback) {
379
377
  return callback(err);
380
378
  }
381
379
 
382
- let method = 'insert';
383
-
384
- if (typeof collection.insertOne == 'function') {
385
- method = 'insertOne';
386
- }
387
-
388
- collection[method](data, {w: 1, fullResult: true}, function afterInsert(err, result) {
380
+ Pledge.done(collection.insertOne(data, {w: 1, fullResult: true}), function afterInsert(err, result) {
389
381
 
390
382
  // Clear the cache
391
383
  model.nukeCache();
@@ -394,6 +386,7 @@ Mongo.setMethod(function _create(model, data, options, callback) {
394
386
  return callback(err, result);
395
387
  }
396
388
 
389
+ // @TODO: fix because of mongodb 6
397
390
  let write_errors = result.message?.documents?.[0]?.writeErrors;
398
391
 
399
392
  if (write_errors) {
@@ -424,7 +417,7 @@ Mongo.setMethod(function _create(model, data, options, callback) {
424
417
  *
425
418
  * @author Jelle De Loecker <jelle@develry.be>
426
419
  * @since 0.2.0
427
- * @version 1.1.0
420
+ * @version 1.3.16
428
421
  */
429
422
  Mongo.setMethod(function _update(model, data, options, callback) {
430
423
 
@@ -507,16 +500,18 @@ Mongo.setMethod(function _update(model, data, options, callback) {
507
500
  console.log('Updating with obj', id, updateObject);
508
501
  }
509
502
 
503
+ let promise;
504
+
510
505
  if (collection.findOneAndUpdate) {
511
- collection.findOneAndUpdate({_id: id}, updateObject, {upsert: true}, afterUpdate);
506
+ promise = collection.findOneAndUpdate({_id: id}, updateObject, {upsert: true});
512
507
  } else if (collection.findAndModify) {
513
- collection.findAndModify({_id: id}, [['_id', 1]], updateObject, {upsert: true}, afterUpdate);
508
+ promise = collection.findAndModify({_id: id}, [['_id', 1]], updateObject, {upsert: true});
514
509
  } else {
515
510
  // If it's not available (like nedb)
516
- collection.update({_id: ''+id}, updateObject, {upsert: true}, afterUpdate);
511
+ promise = collection.update({_id: ''+id}, updateObject, {upsert: true});
517
512
  }
518
513
 
519
- function afterUpdate(err, result) {
514
+ Pledge.done(promise, function afterUpdate(err, result) {
520
515
 
521
516
  // Clear the cache
522
517
  model.nukeCache();
@@ -526,7 +521,7 @@ Mongo.setMethod(function _update(model, data, options, callback) {
526
521
  }
527
522
 
528
523
  callback(null, Object.assign({}, data));
529
- }
524
+ });
530
525
  });
531
526
  });
532
527
 
@@ -536,7 +531,7 @@ Mongo.setMethod(function _update(model, data, options, callback) {
536
531
  * @author Kjell Keisse <kjell@codedor.be>
537
532
  * @author Jelle De Loecker <jelle@develry.be>
538
533
  * @since 0.2.0
539
- * @version 1.0.3
534
+ * @version 1.3.16
540
535
  */
541
536
  Mongo.setMethod(function _remove(model, query, options, callback) {
542
537
 
@@ -546,7 +541,7 @@ Mongo.setMethod(function _remove(model, query, options, callback) {
546
541
  return callback(err);
547
542
  }
548
543
 
549
- collection.findOneAndDelete(query, function _deleted(err, result){
544
+ Pledge.done(collection.findOneAndDelete(query), function _deleted(err, result){
550
545
 
551
546
  //clear cache
552
547
  model.nukeCache();
@@ -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
  *
@@ -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;
@@ -1211,7 +1211,7 @@ Conduit.setMethod(function prepareViewRender() {
1211
1211
  *
1212
1212
  * @author Jelle De Loecker <jelle@develry.be>
1213
1213
  * @since 0.2.0
1214
- * @version 1.1.7
1214
+ * @version 1.3.16
1215
1215
  */
1216
1216
  Conduit.setMethod(function callHandler() {
1217
1217
 
@@ -1226,7 +1226,7 @@ Conduit.setMethod(function callHandler() {
1226
1226
  this.error(405, 'Method Not Allowed', false);
1227
1227
 
1228
1228
  } else {
1229
- if (alchemy.settings.debug) {
1229
+ if (alchemy.settings.debug && this.original_path.indexOf('.js.map') == -1) {
1230
1230
  console.log('Route not found:', this);
1231
1231
  }
1232
1232
 
@@ -1527,7 +1527,7 @@ Conduit.setMethod(function redirect(status, options) {
1527
1527
  *
1528
1528
  * @author Jelle De Loecker <jelle@elevenways.be>
1529
1529
  * @since 0.2.0
1530
- * @version 1.3.4
1530
+ * @version 1.3.16
1531
1531
  *
1532
1532
  * @param {Nulber} status Response statuscode
1533
1533
  * @param {Error} message Optional error to send
@@ -1561,6 +1561,11 @@ Conduit.setMethod(function error(status, message, print_error) {
1561
1561
 
1562
1562
  if (alchemy.settings.environment == 'dev') {
1563
1563
  print_dev = true;
1564
+
1565
+ if (is_400 && this.original_path.indexOf('.js.map') > -1) {
1566
+ print_dev = false;
1567
+ print_error = false;
1568
+ }
1564
1569
  }
1565
1570
 
1566
1571
  if (print_error == null) {
@@ -2309,6 +2314,34 @@ Conduit.setMethod(function getSession(allow_create = true) {
2309
2314
  return session;
2310
2315
  });
2311
2316
 
2317
+ /**
2318
+ * Create a controller of the given type and attach this conduit
2319
+ *
2320
+ * @author Jelle De Loecker <jelle@elevenways.be>
2321
+ * @since 1.3.16
2322
+ * @version 1.3.16
2323
+ *
2324
+ * @param {String} controller_name
2325
+ *
2326
+ * @return {Controller}
2327
+ */
2328
+ Conduit.setMethod(function getController(controller_name) {
2329
+
2330
+ if (!controller_name) {
2331
+ return this.controller;
2332
+ }
2333
+
2334
+ let instance = Controller.get(controller_name);
2335
+
2336
+ if (!instance) {
2337
+ return false;
2338
+ }
2339
+
2340
+ instance.conduit = this;
2341
+
2342
+ return instance;
2343
+ });
2344
+
2312
2345
  /**
2313
2346
  * Register live data bindings via websockets
2314
2347
  *
@@ -2688,7 +2721,7 @@ Conduit.setMethod(function shouldBePostponed() {
2688
2721
  *
2689
2722
  * @author Jelle De Loecker <jelle@develry.be>
2690
2723
  * @since 0.3.0
2691
- * @version 0.4.0
2724
+ * @version 1.3.16
2692
2725
  *
2693
2726
  * @param {String} type
2694
2727
  * @param {Object} data
@@ -2697,18 +2730,18 @@ Alchemy.setMethod(function broadcast(type, data) {
2697
2730
 
2698
2731
  alchemy.sessions.forEach(function eachSession(session, key) {
2699
2732
 
2700
- // Go over every listening scene and submit the data
2701
- Object.each(session.connections, function eachScene(scene, scene_id) {
2733
+ // Go over every listening socket and submit the data
2734
+ Object.each(session.connections, function eachScene(socket_conduit, scene_id) {
2702
2735
 
2703
- if (!scene) {
2736
+ if (!socket_conduit?.is_connected) {
2704
2737
  return;
2705
2738
  }
2706
2739
 
2707
2740
  if (alchemy.settings.debug) {
2708
- log.debug('Broadcasting', type, {data, scene});
2741
+ log.debug('Broadcasting', type, {data, scene: socket_conduit});
2709
2742
  }
2710
2743
 
2711
- scene.submit(type, data);
2744
+ socket_conduit.submit(type, data);
2712
2745
  });
2713
2746
  });
2714
2747
  });
@@ -455,7 +455,7 @@ Datasource.setMethod(function _valueToApp(field, query, options, value, callback
455
455
  *
456
456
  * @author Jelle De Loecker <jelle@develry.be>
457
457
  * @since 0.2.0
458
- * @version 1.1.5
458
+ * @version 1.3.16
459
459
  *
460
460
  * @param {Model} model
461
461
  * @param {Criteria} criteria
@@ -540,24 +540,29 @@ Datasource.setMethod(function read(model, criteria, callback) {
540
540
  return pledge.reject(err);
541
541
  }
542
542
 
543
- let result = {
544
- items : app_results,
545
- available : available
546
- };
543
+ try {
547
544
 
548
- if (hash) {
545
+ let result = {
546
+ items : app_results,
547
+ available : available
548
+ };
549
549
 
550
- // Emit the storing_cache event
551
- model.emit('storing_cache', criteria, result);
550
+ if (hash) {
552
551
 
553
- let cloned = JSON.clone(result);
552
+ // Emit the storing_cache event
553
+ model.emit('storing_cache', criteria, result);
554
554
 
555
- model.emit('stored_cache', criteria, cloned);
555
+ let cloned = JSON.clone(result);
556
556
 
557
- cache_pledge.resolve(cloned);
558
- }
557
+ model.emit('stored_cache', criteria, cloned);
559
558
 
560
- pledge.resolve(result);
559
+ cache_pledge.resolve(cloned);
560
+ }
561
+
562
+ pledge.resolve(result);
563
+ } catch (err) {
564
+ pledge.reject(err);
565
+ }
561
566
  }
562
567
  });
563
568
 
@@ -296,12 +296,16 @@ File.setMethod(function createReadStream(options) {
296
296
  *
297
297
  * @author Jelle De Loecker <jelle@develry.be>
298
298
  * @since 1.1.0
299
- * @version 1.1.8
299
+ * @version 1.3.16
300
300
  */
301
301
  File.setMethod(function read(options) {
302
302
 
303
303
  let pledge = new Pledge();
304
304
 
305
+ if (typeof options == 'string') {
306
+ options = {encoding: options};
307
+ }
308
+
305
309
  if (!options) {
306
310
  options = {flag: 'r'};
307
311
  } else if (!options.flag) {
@@ -845,7 +845,7 @@ Model.setMethod(function ensureIds(list, callback) {
845
845
  *
846
846
  * @author Jelle De Loecker <jelle@develry.be>
847
847
  * @since 0.0.1
848
- * @version 1.2.0
848
+ * @version 1.3.16
849
849
  *
850
850
  * @param {Document} document
851
851
  * @param {Object} options
@@ -943,6 +943,11 @@ Model.setMethod(function saveRecord(document, options, callback) {
943
943
 
944
944
  Object.each(document.$record, function eachEntry(entry, key) {
945
945
 
946
+ // Skip empty entries
947
+ if (!entry) {
948
+ return;
949
+ }
950
+
946
951
  // Skip our own record
947
952
  if (key == that.name) {
948
953
  return;
@@ -957,7 +962,6 @@ Model.setMethod(function saveRecord(document, options, callback) {
957
962
  }
958
963
 
959
964
  // Add the saved _id
960
- //@TODO: this throws an error sometimes.
961
965
  entry[assoc.options.foreignKey] = saved_record[assoc.options.localKey];
962
966
 
963
967
  // Add the task
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * @author Jelle De Loecker <jelle@elevenways.be>
5
5
  * @since 0.2.0
6
- * @version 1.3.1
6
+ * @version 1.3.16
7
7
  */
8
8
  const Route = Function.inherits('Alchemy.Base', function Route(router, paths, options) {
9
9
 
@@ -51,6 +51,10 @@ const Route = Function.inherits('Alchemy.Base', function Route(router, paths, op
51
51
  // Can this route's path be used in the browser's address location?
52
52
  this.visible_location = true;
53
53
 
54
+ // Is this a system route of some kind?
55
+ // (meaning: not for end users)
56
+ this.is_system_route = false;
57
+
54
58
  // If no fnc is given, these will be called
55
59
  this.controller = null;
56
60
  this.action = null;
@@ -767,7 +767,7 @@ RouterClass.setMethod(function setOption(name, value) {
767
767
  *
768
768
  * @author Jelle De Loecker <jelle@elevenways.be>
769
769
  * @since 0.2.0
770
- * @version 1.3.1
770
+ * @version 1.3.16
771
771
  *
772
772
  * @param {Object} args
773
773
  * @param {String} args.name Optional route name
@@ -824,6 +824,10 @@ RouterClass.setMethod(function add(args) {
824
824
  route.visible_location = args.visible_location;
825
825
  }
826
826
 
827
+ if (args.is_system_route != null) {
828
+ route.is_system_route = args.is_system_route;
829
+ }
830
+
827
831
  if (args.permission) {
828
832
  route.setPermission(args.permission);
829
833
  }
@@ -1163,7 +1167,7 @@ RouterClass.setMethod(function getOptions(result) {
1163
1167
  *
1164
1168
  * @author Jelle De Loecker <jelle@elevenways.be>
1165
1169
  * @since 0.2.0
1166
- * @version 1.3.7
1170
+ * @version 1.3.16
1167
1171
  *
1168
1172
  * @param {Object} result Optional object to store sectioned results in
1169
1173
  *
@@ -1212,6 +1216,7 @@ RouterClass.setMethod(function getRoutes(result) {
1212
1216
  has_permission_assignments : route.has_permission_assignments || undefined,
1213
1217
  title : route.title,
1214
1218
  visible_location : route.visible_location,
1219
+ is_system_route : route.is_system_route,
1215
1220
  schema : route.schema,
1216
1221
  param_definitions : route.param_definitions,
1217
1222
  };
@@ -315,11 +315,11 @@ Alchemy.setMethod(function castObjectId(str) {
315
315
  /**
316
316
  * Is the given parameter an object id (string or instance)
317
317
  *
318
- * @author Jelle De Loecker <jelle@develry.be>
318
+ * @author Jelle De Loecker <jelle@elevenways.be>
319
319
  * @since 1.0.4
320
- * @version 1.0.5
320
+ * @version 1.3.16
321
321
  *
322
- * @param {String|ObjectID} obj
322
+ * @param {String|ObjectId} obj
323
323
  *
324
324
  * @return {Boolean}
325
325
  */
@@ -331,9 +331,13 @@ Alchemy.setMethod(function isObjectId(obj) {
331
331
 
332
332
  let type = typeof obj;
333
333
 
334
- if (obj && type === 'object' && obj.constructor && obj.constructor.name === 'ObjectID') {
334
+ if (type === 'string' && obj.isObjectId()) {
335
335
  return true;
336
- } else if (type === 'string' && obj.isObjectId()) {
336
+ }
337
+
338
+ let class_name = obj.constructor?.name;
339
+
340
+ if (class_name == 'ObjectID' || class_name == 'ObjectId') {
337
341
  return true;
338
342
  }
339
343
 
@@ -1051,7 +1051,7 @@ Alchemy.setMethod(function searchModule(startPath, moduleName, recurse) {
1051
1051
  *
1052
1052
  * @author Jelle De Loecker <jelle@develry.be>
1053
1053
  * @since 0.0.1
1054
- * @version 1.2.7
1054
+ * @version 1.3.16
1055
1055
  *
1056
1056
  * @param {String} moduleName
1057
1057
  * @param {Object} options
@@ -1177,7 +1177,20 @@ Alchemy.setMethod(function findModule(moduleName, options) {
1177
1177
 
1178
1178
  // Modules are required by default
1179
1179
  if (options.require) {
1180
- if (package_json && package_json.type == 'module' && !(package_json.main && package_json.module)) {
1180
+
1181
+ let supports_cjs = package_json?.type != 'module';
1182
+
1183
+ // Make sure this does not support CJS.
1184
+ // Some packages have the `module` type, but do have CJS exports too
1185
+ if (!supports_cjs) {
1186
+ let exports = package_json.exports?.['.'];
1187
+
1188
+ if (exports?.require) {
1189
+ supports_cjs = true;
1190
+ }
1191
+ }
1192
+
1193
+ if (!supports_cjs) {
1181
1194
  module = doImport(module_path)
1182
1195
  } else {
1183
1196
  module = require(module_path);
@@ -1248,22 +1261,28 @@ Alchemy.setMethod(function shared(name, type, value) {
1248
1261
  *
1249
1262
  * @author Jelle De Loecker <jelle@develry.be>
1250
1263
  * @since 0.0.1
1251
- * @version 0.4.0
1264
+ * @version 1.3.16
1252
1265
  *
1253
- * @param {String|ObjectID} obj
1266
+ * @param {String|ObjectId} obj
1254
1267
  *
1255
- * @return {ObjectID|undefined}
1268
+ * @return {ObjectId|undefined}
1256
1269
  */
1257
1270
  Alchemy.setMethod(function castObjectId(obj) {
1258
1271
 
1259
- var type = typeof obj;
1272
+ let type = typeof obj;
1260
1273
 
1261
- if (obj && type === 'object' && obj.constructor && obj.constructor.name === 'ObjectID') {
1262
- return obj;
1263
- } else if (type === 'string' && obj.isObjectId()) {
1274
+ if (type === 'string' && obj.isObjectId()) {
1264
1275
  return alchemy.ObjectId(obj);
1265
1276
  }
1266
1277
 
1278
+ if (obj && type === 'object') {
1279
+ let class_name = obj.constructor?.name;
1280
+
1281
+ if (class_name == 'ObjectID' || class_name == 'ObjectId') {
1282
+ return obj;
1283
+ }
1284
+ }
1285
+
1267
1286
  return undefined;
1268
1287
  });
1269
1288
 
@@ -1595,6 +1614,12 @@ Alchemy.setMethod(function parseRequestBody(req, res, callback) {
1595
1614
  return callback(null);
1596
1615
  }
1597
1616
 
1617
+ // Since formidable v3, all the fields are now arrays.
1618
+ // We already had a lot of logic to deal with this,
1619
+ // so we just have to un-array everything
1620
+ form_fields = undoFormidableArray(form_fields);
1621
+ form_files = undoFormidableArray(form_files);
1622
+
1598
1623
  // Fix the field names
1599
1624
  for (key in form_fields) {
1600
1625
  Object.setFormPath(fields, key, form_fields[key]);
@@ -1679,6 +1704,42 @@ Alchemy.setMethod(function parseRequestBody(req, res, callback) {
1679
1704
  });
1680
1705
  });
1681
1706
 
1707
+ /**
1708
+ * Undo formidable's new array handling
1709
+ *
1710
+ * @author Jelle De Loecker <jelle@elevenways.be>
1711
+ * @since 1.3.16
1712
+ * @version 1.3.16
1713
+ *
1714
+ * @param {Object}
1715
+ *
1716
+ * @return {Object}
1717
+ */
1718
+ function undoFormidableArray(data) {
1719
+
1720
+ if (!data) {
1721
+ return {};
1722
+ }
1723
+
1724
+ let result = {},
1725
+ value,
1726
+ key;
1727
+
1728
+ for (key in data) {
1729
+ value = data[key];
1730
+
1731
+ if (key.endsWith('[]')) {
1732
+ key = key.slice(0, -2);
1733
+ } else {
1734
+ value = value[0];
1735
+ }
1736
+
1737
+ result[key] = value;
1738
+ }
1739
+
1740
+ return result;
1741
+ }
1742
+
1682
1743
  /**
1683
1744
  * Export all data
1684
1745
  *
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var mkdirp = alchemy.use('mkdirp'),
3
+ let mkdirp = alchemy.use('mkdirp')?.mkdirp,
4
4
  ncp = alchemy.use('ncp').ncp,
5
5
  fs = alchemy.use('fs'),
6
6
  libpath = alchemy.use('path'),
@@ -496,6 +496,7 @@ Alchemy.setMethod(function getShared(first, second) {
496
496
 
497
497
  /**
498
498
  * Make JSON-Dry handle ObjectIDs when drying
499
+ * (Old class name)
499
500
  *
500
501
  * @author Jelle De Loecker <jelle@develry.be>
501
502
  * @since 0.2.0
@@ -507,16 +508,65 @@ JSON.registerDrier('ObjectID', function dryOI(holder, key, value) {
507
508
 
508
509
  /**
509
510
  * Correctly un-dry ObjectIDs
511
+ * (Old class name)
510
512
  *
511
513
  * @author Jelle De Loecker <jelle@develry.be>
512
514
  * @since 0.2.0
513
515
  * @version 0.2.0
514
516
  */
515
517
  JSON.registerUndrier('ObjectID', function undryOI(holder, key, value) {
516
- return mongo.ObjectID(value);
518
+ return alchemy.castObjectId(value);
517
519
  });
518
520
 
519
- alchemy.ObjectId = mongo.ObjectID;
521
+ /**
522
+ * Make JSON-Dry handle ObjectIds when drying
523
+ *
524
+ * @author Jelle De Loecker <jelle@elevenways.be>
525
+ * @since 1.3.16
526
+ * @version 1.3.16
527
+ */
528
+ JSON.registerDrier('ObjectId', function dryOI(holder, key, value) {
529
+ return ''+value;
530
+ }, {add_path: false});
531
+
532
+ /**
533
+ * Correctly un-dry ObjectIds
534
+ *
535
+ * @author Jelle De Loecker <jelle@elevenways.be>
536
+ * @since 1.3.16
537
+ * @version 1.3.16
538
+ */
539
+ JSON.registerUndrier('ObjectId', function undryOI(holder, key, value) {
540
+ return alchemy.castObjectId(value);
541
+ });
542
+
543
+ /**
544
+ * Monkey-patch checksum support to the ObjectId class
545
+ *
546
+ * @author Jelle De Loecker <jelle@elevenways.be>
547
+ * @since 1.3.16
548
+ * @version 1.3.16
549
+ */
550
+ if (typeof mongo?.ObjectId == 'function') {
551
+ Function.setMethod(mongo.ObjectId, Blast.checksumSymbol, function checksum() {
552
+ return this.toString();
553
+ });
554
+ }
555
+
556
+ /**
557
+ * Create a new ObjectId
558
+ *
559
+ * @author Jelle De Loecker <jelle@elevenways.be>
560
+ * @since 1.3.16
561
+ * @version 1.3.16
562
+ *
563
+ * @param {*} object_id
564
+ *
565
+ * @return {ObjectId}
566
+ */
567
+ Alchemy.setMethod(function ObjectId(object_id) {
568
+ return new mongo.ObjectId(object_id);
569
+ });
520
570
 
521
571
  /**
522
572
  * Get mimetype info of a file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "alchemymvc",
3
3
  "description": "MVC framework for Node.js",
4
- "version": "1.3.15",
4
+ "version": "1.3.16",
5
5
  "author": "Jelle De Loecker <jelle@elevenways.be>",
6
6
  "keywords": [
7
7
  "alchemy",
@@ -14,29 +14,29 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "ansi-256-colors" : "~1.1.0",
17
- "autoprefixer" : "~10.4.2",
18
- "bcrypt" : "~5.0.1",
17
+ "autoprefixer" : "~10.4.16",
18
+ "bcrypt" : "~5.1.1",
19
19
  "body" : "~5.1.0",
20
- "body-parser" : "~1.19.2",
21
- "bson" : "~4.6.1",
20
+ "body-parser" : "~1.20.2",
21
+ "bson" : "~6.1.0",
22
22
  "chokidar" : "~3.5.3",
23
- "formidable" : "~2.1.1",
24
- "graceful-fs" : "~4.2.9",
25
- "hawkejs" : "~2.3.9",
26
- "jsondiffpatch" : "~0.4.1",
23
+ "formidable" : "~3.5.1",
24
+ "graceful-fs" : "~4.2.11",
25
+ "hawkejs" : "~2.3.13",
26
+ "jsondiffpatch" : "~0.5.0",
27
27
  "mime" : "~3.0.0",
28
28
  "minimist" : "~1.2.5",
29
- "mkdirp" : "~1.0.4",
29
+ "mkdirp" : "~3.0.1",
30
30
  "mmmagic" : "~0.5.3",
31
- "mongodb" : "~3.6.6",
31
+ "mongodb" : "~6.1.0",
32
32
  "ncp" : "~2.0.0",
33
- "postcss" : "~8.4.6",
34
- "protoblast" : "~0.8.9",
35
- "semver" : "~7.3.5",
36
- "socket.io" : "~4.6.0",
33
+ "postcss" : "~8.4.31",
34
+ "protoblast" : "~0.8.12",
35
+ "semver" : "~7.5.4",
36
+ "socket.io" : "~4.7.2",
37
37
  "@11ways/socket.io-stream" : "~0.9.2",
38
38
  "sputnik" : "~0.1.0",
39
- "terser" : "~5.18.1",
39
+ "terser" : "~5.21.0",
40
40
  "toobusy-js" : "~0.5.1",
41
41
  "useragent" : "~2.3.0"
42
42
  },
@@ -46,20 +46,20 @@
46
46
  ],
47
47
  "optionalDependencies": {
48
48
  "janeway" : "~0.4.0",
49
- "less" : "~4.1.1",
50
- "sass" : "~1.53.0",
51
- "sass-embedded" : "~1.53.0",
49
+ "less" : "~4.2.0",
50
+ "sass" : "~1.68.0",
51
+ "sass-embedded" : "~1.67.0",
52
52
  "nodent-compiler" : "~3.2.13",
53
- "socket.io-client" : "~4.6.0",
53
+ "socket.io-client" : "~4.7.2",
54
54
  "socket.io-msgpack-parser": "~3.0.2"
55
55
  },
56
56
  "devDependencies": {
57
57
  "codecov" : "~3.8.1",
58
- "istanbul-lib-instrument" : "~4.0.3",
58
+ "istanbul-lib-instrument" : "~6.0.1",
59
59
  "mocha" : "~10.2.0",
60
- "mongo-unit" : "~3.2.0",
60
+ "mongo-unit" : "~3.3.0",
61
61
  "nyc" : "^15.1.0",
62
- "puppeteer" : "~19.0.0",
62
+ "puppeteer" : "~21.3.6",
63
63
  "source-map" : "~0.7.3"
64
64
  },
65
65
  "scripts": {
@@ -71,6 +71,6 @@
71
71
  "main": "lib/bootstrap.js",
72
72
  "license": "MIT",
73
73
  "engines": {
74
- "node" : ">=14.0.0"
74
+ "node" : ">=16.20.1"
75
75
  }
76
76
  }