alchemymvc 1.3.21 → 1.4.0-alpha.1

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.
Files changed (155) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +3 -3
  3. package/lib/app/behaviour/publishable_behaviour.js +5 -5
  4. package/lib/app/behaviour/revision_behaviour.js +10 -10
  5. package/lib/app/behaviour/sluggable_behaviour.js +14 -14
  6. package/lib/app/conduit/electron_conduit.js +9 -9
  7. package/lib/app/conduit/http_conduit.js +13 -13
  8. package/lib/app/conduit/loopback_conduit.js +15 -15
  9. package/lib/app/conduit/socket_conduit.js +43 -43
  10. package/lib/app/config/routes.js +26 -0
  11. package/lib/app/controller/00-default_app_controller.js +21 -0
  12. package/lib/app/controller/alchemy_info_controller.js +12 -12
  13. package/lib/app/datasource/mongo_datasource.js +16 -16
  14. package/lib/app/element/00-default_app_element.js +19 -0
  15. package/lib/app/element/time_ago.js +5 -5
  16. package/lib/app/helper/00-default_app_helper.js +11 -0
  17. package/lib/app/helper/alchemy_helper.js +22 -22
  18. package/lib/app/helper/backed_map.js +1 -1
  19. package/lib/app/helper/breadcrumb.js +10 -10
  20. package/lib/app/helper/client_collection.js +3 -3
  21. package/lib/app/helper/cron.js +29 -29
  22. package/lib/app/helper/enum_values.js +6 -6
  23. package/lib/app/helper/pagination_helper.js +36 -36
  24. package/lib/app/helper/router_helper.js +35 -35
  25. package/lib/app/helper/socket_helper.js +57 -57
  26. package/lib/app/helper/syncable.js +84 -59
  27. package/lib/app/helper_component/paginate_component.js +9 -9
  28. package/lib/app/helper_controller/component.js +1 -1
  29. package/lib/app/helper_controller/conduit.js +31 -31
  30. package/lib/app/helper_controller/controller.js +54 -39
  31. package/lib/app/helper_datasource/00-nosql_datasource.js +624 -70
  32. package/lib/app/helper_datasource/05-fallback_datasource.js +10 -10
  33. package/lib/app/helper_datasource/idb_datasource.js +6 -6
  34. package/lib/app/helper_datasource/indexed_db.js +22 -22
  35. package/lib/app/helper_datasource/remote_datasource.js +5 -5
  36. package/lib/app/helper_error/http_error.js +4 -4
  37. package/lib/app/helper_error/model_error.js +2 -2
  38. package/lib/app/helper_error/validation_error.js +12 -12
  39. package/lib/app/helper_field/00-objectid_field.js +7 -7
  40. package/lib/app/helper_field/05-string_field.js +16 -12
  41. package/lib/app/helper_field/06-text_field.js +2 -4
  42. package/lib/app/helper_field/10-number_field.js +9 -12
  43. package/lib/app/helper_field/11-date_field.js +15 -15
  44. package/lib/app/helper_field/15-local_temporal_field.js +10 -10
  45. package/lib/app/helper_field/20-decimal_field.js +8 -9
  46. package/lib/app/helper_field/belongsto_field.js +1 -1
  47. package/lib/app/helper_field/big_int_field.js +8 -8
  48. package/lib/app/helper_field/boolean_field.js +9 -11
  49. package/lib/app/helper_field/datetime_field.js +3 -3
  50. package/lib/app/helper_field/enum_field.js +13 -8
  51. package/lib/app/helper_field/fixed_decimal_field.js +6 -7
  52. package/lib/app/helper_field/geopoint_field.js +9 -10
  53. package/lib/app/helper_field/habtm_field.js +3 -3
  54. package/lib/app/helper_field/hasoneparent_field.js +1 -1
  55. package/lib/app/helper_field/html_field.js +2 -4
  56. package/lib/app/helper_field/integer_field.js +8 -11
  57. package/lib/app/helper_field/local_date_field.js +5 -5
  58. package/lib/app/helper_field/local_date_time_field.js +5 -5
  59. package/lib/app/helper_field/local_time_field.js +5 -5
  60. package/lib/app/helper_field/mixed_field.js +5 -5
  61. package/lib/app/helper_field/object_field.js +8 -8
  62. package/lib/app/helper_field/password_field.js +3 -3
  63. package/lib/app/helper_field/regexp_field.js +7 -9
  64. package/lib/app/helper_field/schema_field.js +91 -88
  65. package/lib/app/helper_field/settings_field.js +92 -0
  66. package/lib/app/helper_field/time_field.js +6 -6
  67. package/lib/app/helper_field/url_field.js +2 -4
  68. package/lib/app/helper_model/00-base_criteria.js +662 -0
  69. package/lib/app/helper_model/05-criteria_expressions.js +605 -0
  70. package/lib/app/helper_model/10-model_criteria.js +1182 -0
  71. package/lib/app/helper_model/data_provider.js +2 -2
  72. package/lib/app/helper_model/document.js +103 -92
  73. package/lib/app/helper_model/document_list.js +14 -14
  74. package/lib/app/helper_model/field_config.js +11 -11
  75. package/lib/app/helper_model/field_set.js +17 -17
  76. package/lib/app/helper_model/model.js +203 -124
  77. package/lib/app/helper_model/remote_data_provider.js +2 -2
  78. package/lib/app/helper_validator/00_validator.js +16 -16
  79. package/lib/app/helper_validator/not_empty_validator.js +9 -9
  80. package/lib/app/model/00-default_app_model.js +18 -0
  81. package/lib/app/model/05-system_model.js +27 -0
  82. package/lib/app/model/{alchemy_migration_model.js → system_migration_model.js} +4 -4
  83. package/lib/app/model/system_setting_model.js +154 -0
  84. package/lib/app/model/{alchemy_task_history_model.js → system_task_history_model.js} +7 -7
  85. package/lib/app/model/{alchemy_task_model.js → system_task_model.js} +11 -11
  86. package/lib/bootstrap.js +22 -312
  87. package/lib/class/accumulator.js +5 -5
  88. package/lib/class/behaviour.js +5 -5
  89. package/lib/class/component.js +3 -3
  90. package/lib/class/conduit.js +203 -163
  91. package/lib/class/controller.js +42 -42
  92. package/lib/class/datasource.js +74 -79
  93. package/lib/class/document.js +74 -95
  94. package/lib/class/document_list.js +5 -5
  95. package/lib/class/element.js +17 -17
  96. package/lib/class/error.js +3 -3
  97. package/lib/class/field.js +169 -91
  98. package/lib/class/field_value.js +6 -6
  99. package/lib/class/helper.js +3 -3
  100. package/lib/class/inode.js +17 -17
  101. package/lib/class/inode_dir.js +12 -12
  102. package/lib/class/inode_file.js +50 -25
  103. package/lib/class/inode_list.js +4 -4
  104. package/lib/class/migration.js +4 -4
  105. package/lib/class/model.js +182 -168
  106. package/lib/class/path_definition.js +22 -22
  107. package/lib/class/path_evaluator.js +5 -5
  108. package/lib/class/path_param_definition.js +7 -7
  109. package/lib/class/plugin.js +312 -0
  110. package/lib/class/postponement.js +29 -29
  111. package/lib/class/reciprocal.js +8 -8
  112. package/lib/class/route.js +33 -33
  113. package/lib/class/router.js +73 -73
  114. package/lib/class/schema.js +21 -21
  115. package/lib/class/schema_client.js +73 -67
  116. package/lib/class/session.js +63 -29
  117. package/lib/class/session_scene.js +4 -4
  118. package/lib/class/sitemap.js +16 -16
  119. package/lib/class/task.js +39 -39
  120. package/lib/class/task_service.js +43 -47
  121. package/lib/{init → core}/alchemy.js +413 -374
  122. package/lib/{init/functions.js → core/alchemy_functions.js} +171 -108
  123. package/lib/core/alchemy_load_functions.js +715 -0
  124. package/lib/core/base.js +50 -62
  125. package/lib/core/client_alchemy.js +144 -152
  126. package/lib/core/client_base.js +39 -52
  127. package/lib/core/discovery.js +16 -18
  128. package/lib/core/middleware.js +54 -43
  129. package/lib/core/{routing.js → prefix.js} +14 -16
  130. package/lib/core/setting.js +1684 -0
  131. package/lib/core/stage.js +758 -0
  132. package/lib/scripts/create_constants.js +119 -0
  133. package/lib/{init/languages.js → scripts/create_languages.js} +5 -5
  134. package/lib/scripts/create_settings.js +449 -0
  135. package/lib/scripts/create_shared_constants.js +95 -0
  136. package/lib/scripts/create_stages.js +55 -0
  137. package/lib/scripts/init_alchemy.js +51 -0
  138. package/lib/{init/requirements.js → scripts/preload_modules.js} +15 -2
  139. package/lib/scripts/setup_devwatch.js +238 -0
  140. package/lib/stages/00-load_core.js +342 -0
  141. package/lib/stages/05-load_app.js +57 -0
  142. package/lib/stages/10-datasource.js +61 -0
  143. package/lib/stages/15-tasks.js +27 -0
  144. package/lib/stages/20-settings.js +68 -0
  145. package/lib/stages/50-routes.js +218 -0
  146. package/lib/stages/90-server.js +347 -0
  147. package/package.json +5 -7
  148. package/lib/app/helper_model/criteria.js +0 -2294
  149. package/lib/app/helper_model/db_query.js +0 -1488
  150. package/lib/app/routes.js +0 -11
  151. package/lib/core/socket.js +0 -171
  152. package/lib/init/constants.js +0 -158
  153. package/lib/init/devwatch.js +0 -238
  154. package/lib/init/load_functions.js +0 -973
  155. package/lib/stages.js +0 -513
@@ -1,2294 +0,0 @@
1
- const AUGMENTED = Symbol('AUGMENTED');
2
-
3
- /**
4
- * The Criteria class
5
- *
6
- * @author Jelle De Loecker <jelle@elevenways.be>
7
- * @since 1.1.0
8
- * @version 1.3.4
9
- *
10
- * @param {Model} model
11
- */
12
- var Criteria = Function.inherits('Alchemy.Base', 'Alchemy.Criteria', function Criteria(model) {
13
-
14
- // The current active group
15
- this.group = null;
16
-
17
- // The model
18
- this.model = model;
19
-
20
- // All the created expressions
21
- this.all_expressions = [];
22
-
23
- // Create the actual group (is always AND)
24
- this.createGroup('and');
25
-
26
- // Store the root group
27
- this.root_group = this.group;
28
-
29
- // Other options
30
- this.options = {
31
- select : new Select(this),
32
- document : true,
33
- document_list : true
34
- };
35
- });
36
-
37
- /**
38
- * Is the given argument a Criteria instance?
39
- *
40
- * @author Jelle De Loecker <jelle@develry.be>
41
- * @since 1.1.0
42
- * @version 1.1.0
43
- *
44
- * @param {Object} instance
45
- *
46
- * @return {Boolean}
47
- */
48
- Criteria.setStatic(function isCriteria(instance) {
49
-
50
- if (!instance || typeof instance != 'object') {
51
- return false;
52
- }
53
-
54
- if (instance instanceof Criteria) {
55
- return true;
56
- }
57
-
58
- return false;
59
- });
60
-
61
- /**
62
- * Make sure to get a criteria
63
- *
64
- * @author Jelle De Loecker <jelle@elevenways.be>
65
- * @since 1.2.5
66
- * @version 1.3.4
67
- *
68
- * @param {Object} obj The thing that should be a criteria
69
- * @param {Model} model The model that it probably belongs to
70
- *
71
- * @return {Criteria}
72
- */
73
- Criteria.setStatic(function cast(obj, model) {
74
-
75
- if (Criteria.isCriteria(obj)) {
76
- return obj;
77
- }
78
-
79
- let instance = new Criteria(model);
80
-
81
- if (obj) {
82
- instance.applyOldOptions(obj);
83
- }
84
-
85
- return instance;
86
- });
87
-
88
- /**
89
- * Undry the given object
90
- *
91
- * @author Jelle De Loecker <jelle@develry.be>
92
- * @since 1.1.0
93
- * @version 1.2.3
94
- *
95
- * @param {Object} data
96
- *
97
- * @return {Criteria}
98
- */
99
- Criteria.setStatic(function unDry(data) {
100
-
101
- var criteria = new Criteria();
102
-
103
- if (data.model) {
104
- try {
105
- criteria.model = alchemy.getModel(data.model);
106
- } catch (err) {
107
- // Ignore
108
- console.warn('Failed to find "' + data.model + '" model');
109
- }
110
- }
111
-
112
- // Revive the group instance
113
- criteria.group = Group.revive(data.group, criteria);
114
-
115
- if (!data.options) {
116
- data.options = {};
117
- }
118
-
119
- // Revive the select
120
- data.options.select = Select.revive(data.options.select, criteria);
121
-
122
- criteria.options = data.options || {};
123
-
124
- return criteria;
125
- });
126
-
127
- /**
128
- * Add a new value expression type
129
- *
130
- * @author Jelle De Loecker <jelle@develry.be>
131
- * @since 1.1.0
132
- * @version 1.1.0
133
- *
134
- * @param {String} name The name of the expression
135
- * @param {Boolean} cast Cast the value [true]
136
- *
137
- * @return {Criteria}
138
- */
139
- Criteria.setStatic(function addValueExpression(name, cast) {
140
- this.setMethod(name, function valueExpressionAdder(value) {
141
-
142
- var entry;
143
-
144
- this._ensureExpression(name);
145
- entry = this._active.addItem(name, value);
146
-
147
- if (cast != null) {
148
- entry.cast = cast;
149
- }
150
-
151
- return this;
152
- });
153
- });
154
-
155
- /**
156
- * Add a new field expression type
157
- *
158
- * @author Jelle De Loecker <jelle@develry.be>
159
- * @since 1.1.0
160
- * @version 1.1.0
161
- *
162
- * @param {String} name The name of the expression
163
- *
164
- * @return {Criteria}
165
- */
166
- Criteria.setStatic(function addFieldExpression(name) {
167
- this.setMethod(name, function fieldExpressionAdder(value) {
168
-
169
- var entry;
170
-
171
- this._ensureExpression(name);
172
- entry = this._active.addItem(name, value);
173
- entry.field_expression = true;
174
-
175
- return this;
176
- });
177
- });
178
-
179
- /**
180
- * Create an augmented of this instance
181
- *
182
- * @author Jelle De Loecker <jelle@develry.be>
183
- * @since 1.1.0
184
- * @version 1.1.0
185
- *
186
- * @param {String} path
187
- * @param {Criteria} criteria
188
- *
189
- * @return {Object}
190
- */
191
- Criteria.setStatic(function parsePath(path, criteria) {
192
-
193
- var target_path,
194
- result = {},
195
- pieces,
196
- piece,
197
- alias,
198
- i;
199
-
200
- if (path.indexOf('.') > -1) {
201
- pieces = path.split('.');
202
- } else {
203
- pieces = [path];
204
- }
205
-
206
- for (i = 0; i < pieces.length; i++) {
207
- piece = pieces[i];
208
- alias = null;
209
-
210
- if (criteria && criteria.model) {
211
- if (criteria.model.associations[piece]) {
212
- alias = piece;
213
- }
214
- }
215
-
216
- if (!alias && piece[0].isUpperCase()) {
217
- alias = piece;
218
- }
219
-
220
- if (alias) {
221
- if (!result.association) {
222
- result.association = [];
223
- }
224
-
225
- result.association.push(alias);
226
- continue;
227
- }
228
-
229
- target_path = pieces.slice(i).join('.');
230
- break;
231
- }
232
-
233
- if (target_path) {
234
- result.target_path = target_path;
235
- }
236
-
237
- return result;
238
- });
239
-
240
- /**
241
- * Create a reference to the datasource
242
- *
243
- * @author Jelle De Loecker <jelle@develry.be>
244
- * @since 1.1.0
245
- * @version 1.1.0
246
- *
247
- * @type {Datasource}
248
- */
249
- Criteria.setProperty(function datasource() {
250
-
251
- if (this._datasource) {
252
- return this._datasource;
253
- }
254
-
255
- if (this.model) {
256
- return this.model.datasource;
257
- }
258
- }, function setDatasource(ds) {
259
- this._datasource = ds;
260
- return this._datasource;
261
- });
262
-
263
- /**
264
- * The recursiveness of this criteria
265
- *
266
- * @author Jelle De Loecker <jelle@develry.be>
267
- * @since 1.1.0
268
- * @version 1.1.0
269
- *
270
- * @type {Number}
271
- */
272
- Criteria.setProperty(function recursive_level() {
273
-
274
- if (this.options.recursive) {
275
- return this.options.recursive;
276
- }
277
-
278
- return 0;
279
- });
280
-
281
- /**
282
- * Return the elements to checksum in place of this object
283
- *
284
- * @author Jelle De Loecker <jelle@develry.be>
285
- * @since 1.1.0
286
- * @version 1.2.3
287
- */
288
- Criteria.setMethod(Blast.checksumSymbol, function toChecksum() {
289
-
290
- var name = this.model ? this.model.name : undefined,
291
- result = [name],
292
- options = {},
293
- key;
294
-
295
- for (key in this.options) {
296
-
297
- if (key == 'init_model' || key == 'init_record' || key == 'assoc_cache') {
298
- continue;
299
- }
300
-
301
- options[key] = this.options[key];
302
- }
303
-
304
- result.push(options);
305
- result.push(this.group);
306
-
307
- return result;
308
- });
309
-
310
- /**
311
- * Allow the criteria to be used in a for wait loop
312
- *
313
- * @author Jelle De Loecker <jelle@elevenways.be>
314
- * @since 1.1.0
315
- * @version 1.1.0
316
- */
317
- Criteria.setMethod(Symbol.asyncIterator, function asyncIterator() {
318
-
319
- const that = this,
320
- model = this.model;
321
-
322
- if (!model) {
323
- throw new Error('Unable to iterate over a criteria without a model');
324
- }
325
-
326
- // Clone it
327
- let criteria = this.clone();
328
-
329
- // Set the limit to 1
330
- criteria.limit(1);
331
-
332
- // Create the iterator context
333
- let context = {
334
- index : 0,
335
- next : async function next() {
336
-
337
- criteria.skip(this.index++);
338
-
339
- let record = await model.find('first', criteria);
340
-
341
- if (!record) {
342
- return {done: true};
343
- }
344
-
345
- return {value: record, done: false};
346
- }
347
- };
348
-
349
- return context;
350
- });
351
-
352
- /**
353
- * Return object for jsonifying
354
- *
355
- * @author Jelle De Loecker <jelle@develry.be>
356
- * @since 1.1.0
357
- * @version 1.2.3
358
- *
359
- * @return {Object}
360
- */
361
- Criteria.setMethod(function toJSON() {
362
-
363
- let result = {},
364
- options;
365
-
366
- if (this.model && this.model.name) {
367
- result.model = this.model.name;
368
- }
369
-
370
- if (this.options) {
371
- let key;
372
- options = {};
373
-
374
- for (key in this.options) {
375
- if (key == 'assoc_cache' || key == 'init_record') {
376
- continue;
377
- }
378
-
379
- options[key] = this.options[key];
380
- }
381
- }
382
-
383
- result.group = this.group;
384
- result.options = options;
385
-
386
- return result;
387
- });
388
-
389
- /**
390
- * Dry this Criteria instance
391
- *
392
- * @author Jelle De Loecker <jelle@develry.be>
393
- * @since 1.1.0
394
- * @version 1.1.0
395
- *
396
- * @return {Object}
397
- */
398
- Criteria.setMethod(function toDry() {
399
-
400
- // Augments cannot be dried, it's ALWAYS the original instance
401
- var value = this.toJSON();
402
-
403
- return {
404
- value : value
405
- };
406
- });
407
-
408
- /**
409
- * Clone this instance
410
- *
411
- * @author Jelle De Loecker <jelle@develry.be>
412
- * @since 1.1.0
413
- * @version 1.1.0
414
- *
415
- * @return {Criteria}
416
- */
417
- Criteria.setMethod(function clone() {
418
-
419
- var data = JSON.toDryObject(this),
420
- result;
421
-
422
- data.model = null;
423
- result = JSON.undry(data);
424
-
425
- result.model = this.model;
426
-
427
- return result;
428
- });
429
-
430
- /**
431
- * Get the main fields to select
432
- *
433
- * @author Jelle De Loecker <jelle@develry.be>
434
- * @since 1.1.0
435
- * @version 1.2.3
436
- *
437
- * @return {String[]}
438
- */
439
- Criteria.setMethod(function getFieldsToSelect() {
440
-
441
- let result;
442
-
443
- if (this.options?.select?.fields?.length) {
444
- result = this.options.select.fields.slice(0);
445
- }
446
-
447
- // Fields can sometimes be required for a query (like in a join) but they
448
- // won't be selected if other fields are explicitly set.
449
- // So in that case: add these special fields to the projection
450
- if (result && this.options?.select?.query_fields) {
451
- result.push(...this.options.select.query_fields);
452
- }
453
-
454
- return result || [];
455
- });
456
-
457
- /**
458
- * Get the association selects, if any
459
- *
460
- * @author Jelle De Loecker <jelle@develry.be>
461
- * @since 1.1.0
462
- * @version 1.1.0
463
- *
464
- * @return {Object}
465
- */
466
- Criteria.setMethod(function getAssociationsToSelect() {
467
-
468
- var result = this.options.select.associations;
469
-
470
- return result;
471
- });
472
-
473
- /**
474
- * Should the given association be queried?
475
- *
476
- * @author Jelle De Loecker <jelle@develry.be>
477
- * @since 1.1.0
478
- * @version 1.1.0
479
- *
480
- * @param {String} name
481
- *
482
- * @return {Boolean}
483
- */
484
- Criteria.setMethod(function shouldQueryAssociation(name) {
485
-
486
- var result = false;
487
-
488
- // If there are explicit associations selected,
489
- // then this one has to be in it!
490
- if (this.getAssociationsToSelect()) {
491
- result = this.options.select.shouldQueryAssociation(name);
492
- } else {
493
- // There are no explicit associations, look at the recursive level
494
- result = this.recursive_level > 0;
495
- }
496
-
497
- return result;
498
- });
499
-
500
- /**
501
- * Get association configuration in the current active model
502
- * or in the options
503
- *
504
- * @author Jelle De Loecker <jelle@develry.be>
505
- * @since 1.1.0
506
- * @version 1.1.0
507
- *
508
- * @param {String} alias
509
- *
510
- * @return {Object}
511
- */
512
- Criteria.setMethod(function getAssociationConfiguration(alias) {
513
-
514
- if (this.options.associations && this.options.associations[alias]) {
515
- return this.options.associations[alias];
516
- }
517
-
518
- return this.model.getAssociation(alias);
519
- });
520
-
521
- /**
522
- * Get a new criteria for adding associated data
523
- *
524
- * @author Jelle De Loecker <jelle@develry.be>
525
- * @since 1.1.0
526
- * @version 1.2.3
527
- *
528
- * @param {String} name
529
- * @param {Object} item
530
- *
531
- * @return {Criteria}
532
- */
533
- Criteria.setMethod(function getCriteriaForAssociation(name, item) {
534
-
535
- if (!this.model) {
536
- throw new Error('Unable to create criteria for association "' + name + '" without originating model instance');
537
- }
538
-
539
- let assoc_model = this.model.getAliasModel(name),
540
- data = item[this.model.name];
541
-
542
- // @TODO: For deadlock reasons we don't query self-referencing links!
543
- // (Implemented in Schema fields, BelongsTo and such could still pose problems!)
544
- if (assoc_model.name == this.options.init_model) {
545
- return;
546
- }
547
-
548
- let association = this.getAssociationConfiguration(name);
549
-
550
- let value = data[association.options.localKey];
551
-
552
- // If no valid value is found for the associated key, do nothing
553
- if (value == null) {
554
- return;
555
- }
556
-
557
- let assoc_crit = assoc_model.find(),
558
- assoc_key = association.options.foreignKey,
559
- options = this.options,
560
- select;
561
-
562
- if (Array.isArray(value)) {
563
- assoc_crit.where(assoc_key).in(value);
564
- } else {
565
- assoc_crit.where(assoc_key).equals(value);
566
- }
567
-
568
- assoc_crit.setOption('assoc_key', assoc_key);
569
- assoc_crit.setOption('assoc_value', value);
570
-
571
- // Make the assoc_cache if it doesn't exist yet
572
- if (options.create_references !== false && !options.assoc_cache) {
573
- options.assoc_cache = {};
574
- }
575
-
576
- // Add the assoc_cache
577
- if (options.assoc_cache) {
578
- assoc_crit.setOption('assoc_cache', options.assoc_cache);
579
- }
580
-
581
- // Take over the locale option
582
- if (options.locale) {
583
- assoc_crit.setOption('locale', options.locale);
584
- }
585
-
586
- // The debug object, if there is one
587
- if (options._debugObject) {
588
- assoc_crit.setOption('_debugObject', options._debugObject);
589
- }
590
-
591
- // Don't get the available count
592
- assoc_crit.setOption('available', false);
593
-
594
- if (options.select.associations && options.select.associations[name]) {
595
- select = options.select.associations[name];
596
- assoc_crit.options.select = select.cloneForCriteria(assoc_crit);
597
- }
598
-
599
- // Sort the results
600
- // @TODO: add sorts
601
- // if (query.sort && query.sort[alias]) {
602
- // assocOpts.sort = query.sort[alias];
603
- // }
604
-
605
- if (Number.isSafeInteger(options.recursive) && options.recursive > 0) {
606
- assoc_crit.recursive(options.recursive - 1);
607
- } else {
608
- // Disable recursiveness for the next level
609
- assoc_crit.recursive(0);
610
- }
611
-
612
- // Add the model name from where we're adding associated data
613
- assoc_crit.setOption('init_model', options.init_model || this.model.name);
614
- assoc_crit.setOption('init_record', options.init_record || item);
615
-
616
- assoc_crit.setOption('from_alias', options.for_alias);
617
- assoc_crit.setOption('from_model', options.for_model);
618
-
619
- assoc_crit.setOption('for_alias', name);
620
- assoc_crit.setOption('for_model', assoc_model.name);
621
-
622
- // Honor the original document option
623
- assoc_crit.setOption('document', options.document);
624
-
625
- if (options.debug) {
626
- assoc_crit.setOption('debug', true);
627
- console.log('Associated criteria:', assoc_model.name, assoc_crit);
628
- }
629
-
630
- return assoc_crit;
631
- });
632
-
633
- /**
634
- * Create an augmented of this instance
635
- *
636
- * @author Jelle De Loecker <jelle@develry.be>
637
- * @since 1.1.0
638
- * @version 1.1.0
639
- *
640
- * @param {String} type
641
- *
642
- * @return {Criteria}
643
- */
644
- Criteria.setMethod(function augment(type) {
645
-
646
- var context = this,
647
- result;
648
-
649
- if (context[AUGMENTED]) {
650
- // NO don't do this, we want chaining!
651
- //context = context[AUGMENTED];
652
- }
653
-
654
- // Create the new object with this instance as the prototype
655
- result = Object.create(context);
656
-
657
- // Remembed which object we augmented
658
- result[AUGMENTED] = context;
659
-
660
- return result;
661
- });
662
-
663
- /**
664
- * Limit the amount of records to get
665
- *
666
- * @author Jelle De Loecker <jelle@develry.be>
667
- * @since 1.1.0
668
- * @version 1.2.7
669
- *
670
- * @param {Number} amount
671
- *
672
- * @return {Criteria}
673
- */
674
- Criteria.setMethod(function limit(amount) {
675
-
676
- if (typeof amount != 'number') {
677
- amount = parseInt(amount);
678
- }
679
-
680
- this.options.limit = amount;
681
- return this;
682
- });
683
-
684
- /**
685
- * Skip an amount of records
686
- *
687
- * @author Jelle De Loecker <jelle@develry.be>
688
- * @since 1.1.0
689
- * @version 1.2.7
690
- *
691
- * @param {Number} amount
692
- *
693
- * @return {Criteria}
694
- */
695
- Criteria.setMethod(function skip(amount) {
696
-
697
- if (typeof amount != 'number') {
698
- amount = parseInt(amount);
699
- }
700
-
701
- this.options.skip = amount;
702
- return this;
703
- });
704
-
705
- /**
706
- * Get a specific page
707
- *
708
- * @author Jelle De Loecker <jelle@develry.be>
709
- * @since 1.1.3
710
- * @version 1.2.7
711
- *
712
- * @param {Number} page A 1-indexed page number
713
- * @param {Number} page_size
714
- *
715
- * @return {Criteria}
716
- */
717
- Criteria.setMethod(function page(page, page_size) {
718
-
719
- if (typeof page != 'number') {
720
- page = parseInt(page);
721
- }
722
-
723
- if (page_size && typeof page_size != 'number') {
724
- page_size = parseInt(page_size);
725
- }
726
-
727
- if (!page) {
728
- throw new Error('A page number is required');
729
- }
730
-
731
- if (!page_size || !isFinite(page_size)) {
732
- page_size = 10;
733
- }
734
-
735
- let skip = (page - 1) * page_size;
736
-
737
- this.options.page = page;
738
- this.options.page_size = page_size;
739
-
740
- this.skip(skip);
741
- return this.limit(page_size);
742
- });
743
-
744
- /**
745
- * Select a specific field or association
746
- *
747
- * @author Jelle De Loecker <jelle@develry.be>
748
- * @since 1.1.0
749
- * @version 1.2.0
750
- *
751
- * @param {String|Array} field
752
- *
753
- * @return {Criteria}
754
- */
755
- Criteria.setMethod(function select(field) {
756
-
757
- var context;
758
-
759
- if (Object.isIterable(field)) {
760
- let entry;
761
-
762
- for (entry of field) {
763
- context = this.select(entry);
764
- }
765
- } else {
766
-
767
- if (this._select) {
768
- context = this._select.parse(field);
769
- } else {
770
- context = this.options.select.parse(field);
771
- }
772
- }
773
-
774
- // Selects don't always change the context
775
- if (context) {
776
- return context;
777
- }
778
-
779
- return this;
780
- });
781
-
782
- /**
783
- * Add a specific association
784
- *
785
- * @author Jelle De Loecker <jelle@develry.be>
786
- * @since 1.1.0
787
- * @version 1.1.0
788
- *
789
- * @param {String} alias
790
- *
791
- * @return {Criteria}
792
- */
793
- Criteria.setMethod(['contain', 'populate'], function populate(alias) {
794
-
795
- if (Array.isArray(alias)) {
796
- let i;
797
-
798
- for (i = 0; i < alias.length; i++) {
799
- this.populate(alias[i]);
800
- }
801
- } else {
802
- let select = this._select || this.options.select;
803
-
804
- select.addAssociation(alias);
805
- }
806
-
807
- return this;
808
- });
809
-
810
- /**
811
- * How deep can we go?
812
- *
813
- * @author Jelle De Loecker <jelle@develry.be>
814
- * @since 1.1.0
815
- * @version 1.1.0
816
- *
817
- * @param {Number} amount
818
- *
819
- * @return {Criteria}
820
- */
821
- Criteria.setMethod(function recursive(amount) {
822
- this.options.recursive = amount;
823
- return this;
824
- });
825
-
826
-
827
- /**
828
- * Set the sort
829
- *
830
- * @author Jelle De Loecker <jelle@develry.be>
831
- * @since 1.1.0
832
- * @version 1.1.4
833
- *
834
- * @param {Array} value
835
- *
836
- * @return {Criteria}
837
- */
838
- Criteria.setMethod(function sort(value) {
839
-
840
- var result;
841
-
842
- if (value) {
843
- result = [];
844
-
845
- // Parse strings
846
- if (typeof value == 'string') {
847
- // When it contains a space, we expect something
848
- // like "_id asc"
849
- if (value.indexOf(' ') > -1) {
850
- result.push(value.split(' '));
851
- } else {
852
- // Sort ascending by default
853
- result.push([value, 1]);
854
- }
855
- } else if (Array.isArray(value)) {
856
- if (Array.isArray(value[0])) {
857
- result = value;
858
- } else {
859
- result.push(value);
860
- }
861
- } else {
862
- let keys = Object.keys(value),
863
- key;
864
-
865
- if (keys.length == 2 && ~keys.indexOf('dir') && ~keys.indexOf('field')) {
866
-
867
- if (value.field && value.dir) {
868
- result.push([value.field, value.dir]);
869
- }
870
-
871
- } else {
872
- for (key in value) {
873
- result.push([key, value[key]]);
874
- }
875
- }
876
- }
877
-
878
- let entry,
879
- i;
880
-
881
- for (i = 0; i < result.length; i++) {
882
- entry = result[i];
883
-
884
- if (typeof entry[1] == 'string') {
885
- entry[1] = entry[1].toLowerCase();
886
-
887
- if (entry[1] == 'asc') {
888
- entry[1] = 1;
889
- } else if (entry[1] == 'desc') {
890
- entry[1] = -1;
891
- } else {
892
- throw new Error('Unable to parse sort specification "' + entry[1] + '"');
893
- }
894
- }
895
- }
896
-
897
- // @TODO: implement better handling of ModelName.field sort stuff
898
- // (Because at this moment, it's just ignored!)
899
- for (entry of result) {
900
- if (entry[0].indexOf('.') > -1) {
901
- let pieces = entry[0].split('.'),
902
- char = pieces[0][0];
903
-
904
- if (char == char.toUpperCase()) {
905
- pieces = pieces.slice(1);
906
- }
907
-
908
- entry[0] = pieces.join('.');
909
- }
910
- }
911
-
912
- } else {
913
- result = null;
914
- }
915
-
916
- this.options.sort = result;
917
-
918
- return this;
919
- });
920
-
921
- /**
922
- * Set a specific option
923
- *
924
- * @author Jelle De Loecker <jelle@develry.be>
925
- * @since 1.1.0
926
- * @version 1.1.0
927
- *
928
- * @param {String} key
929
- * @param {String} value
930
- *
931
- * @return {Criteria}
932
- */
933
- Criteria.setMethod(function setOption(key, value) {
934
- this.options[key] = value;
935
- return this;
936
- });
937
-
938
- /**
939
- * Create a new group
940
- *
941
- * @author Jelle De Loecker <jelle@develry.be>
942
- * @since 1.1.0
943
- * @version 1.1.0
944
- *
945
- * @param {String} type
946
- */
947
- Criteria.setMethod(function createGroup(type) {
948
-
949
- var context = this,
950
- group;
951
-
952
- if (this.group) {
953
- let old_group = context.group;
954
- context = context.augment('group');
955
-
956
- group = new Group(context, type);
957
- old_group.addItem(group);
958
-
959
- context.group = group;
960
- } else {
961
- group = new Group(this, type);
962
- this.group = group;
963
- }
964
-
965
- return context;
966
- });
967
-
968
- /**
969
- * Close the current active group
970
- *
971
- * @author Jelle De Loecker <jelle@develry.be>
972
- * @since 1.1.0
973
- * @version 1.1.0
974
- *
975
- * @return {Criteria}
976
- */
977
- Criteria.setMethod(function closeGroup() {
978
-
979
- var context = this;
980
-
981
- if (this.group && this.group.current_group) {
982
- context = this.group.current_group.criteria;
983
- }
984
-
985
- return context;
986
- });
987
-
988
- /**
989
- * Add a new expression
990
- *
991
- * @author Jelle De Loecker <jelle@develry.be>
992
- * @since 1.1.0
993
- * @version 1.1.0
994
- *
995
- * @param {Expression} expression
996
- */
997
- Criteria.setMethod(function addNewExpression(expression) {
998
-
999
- // Make sure there is a criteria link
1000
- if (!expression.criteria) {
1001
- expression.criteria = this;
1002
- }
1003
-
1004
- // Move the expression to the currently active group by default
1005
- expression.moveToGroup(this.group);
1006
-
1007
- this.all_expressions.push(expression);
1008
- });
1009
-
1010
- /**
1011
- * Target a field
1012
- *
1013
- * @author Jelle De Loecker <jelle@develry.be>
1014
- * @since 1.1.0
1015
- * @version 1.1.0
1016
- *
1017
- * @param {String} name
1018
- * @param {*} value
1019
- */
1020
- Criteria.setMethod(function where(name, value) {
1021
-
1022
- var context = this.augment('field'),
1023
- expression = new FieldExpression(context, name);
1024
-
1025
- this.addNewExpression(expression);
1026
-
1027
- context._active = expression;
1028
-
1029
- if (arguments.length == 2) {
1030
- context = context.equals(value);
1031
- }
1032
-
1033
- return context;
1034
- });
1035
-
1036
- /**
1037
- * Create an or group
1038
- *
1039
- * @author Jelle De Loecker <jelle@develry.be>
1040
- * @since 1.1.0
1041
- * @version 1.1.0
1042
- *
1043
- * @param {String} type Type of group: or, and, not, ...
1044
- * @param {String} value
1045
- */
1046
- Criteria.setMethod(function _applyGroup(type, value) {
1047
-
1048
- var context = this,
1049
- group,
1050
- temp;
1051
-
1052
- if (context._active) {
1053
- temp = context._active.moveToGroup(type);
1054
- context = temp;
1055
- } else {
1056
- context = context.createGroup(type);
1057
- }
1058
-
1059
- if (arguments.length > 1) {
1060
- let method = context._last_method;
1061
-
1062
- if (!method) {
1063
- throw new Error('Unable to call ' + type + '() with a value when no previous method is called');
1064
- }
1065
-
1066
- // Create new expression with the same field
1067
- context = context.where(context._active.target_path);
1068
-
1069
- // Apply the method on it
1070
- context[method](value);
1071
- } else if (context._active) {
1072
- // Create new expression with the same field
1073
- context = context.where(context._active.target_path);
1074
- }
1075
-
1076
- return context;
1077
- });
1078
-
1079
- /**
1080
- * Create an or group
1081
- *
1082
- * @author Jelle De Loecker <jelle@develry.be>
1083
- * @since 1.1.0
1084
- * @version 1.1.0
1085
- *
1086
- * @param {String} value
1087
- */
1088
- Criteria.setMethod(function or(value) {
1089
-
1090
- var result;
1091
-
1092
- if (arguments.length) {
1093
- return this._applyGroup('or', value);
1094
- }
1095
-
1096
- return this._applyGroup('or');
1097
- });
1098
-
1099
- /**
1100
- * Create an and group
1101
- *
1102
- * @author Jelle De Loecker <jelle@develry.be>
1103
- * @since 1.1.0
1104
- * @version 1.1.0
1105
- *
1106
- * @param {String} value
1107
- */
1108
- Criteria.setMethod(function and(value) {
1109
-
1110
- var result;
1111
-
1112
- if (arguments.length) {
1113
- return this._applyGroup('and', value);
1114
- }
1115
-
1116
- return this._applyGroup('and');
1117
- });
1118
-
1119
- /**
1120
- * Alias for `ne`: Not equals check
1121
- *
1122
- * @author Jelle De Loecker <jelle@elevenways.be>
1123
- * @since 1.3.21
1124
- * @version 1.3.2
1125
- *
1126
- * @param {*} value
1127
- */
1128
- Criteria.setMethod(function notEquals(value) {
1129
- return this.ne(value);
1130
- });
1131
-
1132
- Criteria.addValueExpression('equals');
1133
- Criteria.addValueExpression('gte');
1134
- Criteria.addValueExpression('gt');
1135
- Criteria.addValueExpression('lte');
1136
- Criteria.addValueExpression('lt');
1137
- Criteria.addValueExpression('in');
1138
-
1139
- // Add as value expressions of which we don't cast values
1140
- Criteria.addValueExpression('contains', false);
1141
- Criteria.addValueExpression('not', false);
1142
- Criteria.addValueExpression('ne', false);
1143
-
1144
- Criteria.addFieldExpression('exists');
1145
- Criteria.addFieldExpression('isNull');
1146
- Criteria.addFieldExpression('isEmpty');
1147
-
1148
- /**
1149
- * Make sure there is an active expression
1150
- *
1151
- * @author Jelle De Loecker <jelle@develry.be>
1152
- * @since 1.1.0
1153
- * @version 1.1.0
1154
- *
1155
- * @param {String} method
1156
- */
1157
- Criteria.setMethod(function _ensureExpression(method) {
1158
-
1159
- if (!this._active) {
1160
- throw new Error(method + '() must be called when an expression is active');
1161
- }
1162
-
1163
- this._last_method = method;
1164
- });
1165
-
1166
- /**
1167
- * Normalize the criteria by filling in some values on datasources without joins
1168
- *
1169
- * @author Jelle De Loecker <jelle@develry.be>
1170
- * @since 1.1.0
1171
- * @version 1.1.0
1172
- *
1173
- * @return {Pledge}
1174
- */
1175
- Criteria.setMethod(function normalize() {
1176
-
1177
- if (!this.model) {
1178
- return Pledge.reject(new Error('Unable to normalize criteria without model instance'));
1179
- }
1180
-
1181
- let that = this,
1182
- tasks = [],
1183
- i;
1184
-
1185
- for (i = 0; i < this.all_expressions.length; i++) {
1186
- let expression = this.all_expressions[i];
1187
-
1188
- if (!expression) {
1189
- continue;
1190
- }
1191
-
1192
- // Do we need to normalize association values?
1193
- if (expression.requires_association_normalization) {
1194
- tasks.push(function doNormalize(next) {
1195
- expression.normalizeAssociationValues().done(next);
1196
- });
1197
-
1198
- continue;
1199
- }
1200
-
1201
- let pledge = expression.normalize();
1202
-
1203
- if (pledge) {
1204
- tasks.push(pledge);
1205
- }
1206
- }
1207
-
1208
- return Function.parallel(4, tasks);
1209
- });
1210
-
1211
- /**
1212
- * Compile to MongoDB-like query
1213
- *
1214
- * @author Jelle De Loecker <jelle@develry.be>
1215
- * @since 1.1.0
1216
- * @version 1.1.0
1217
- *
1218
- * @return {Object}
1219
- */
1220
- Criteria.setMethod(function compile() {
1221
-
1222
- if (!this.datasource) {
1223
- throw new Error('Unable to compile criteria without a datasource target');
1224
- }
1225
-
1226
- return this.datasource.compileCriteria(this);
1227
- });
1228
-
1229
- /**
1230
- * Parse an old, mongodb specific options object
1231
- *
1232
- * @author Jelle De Loecker <jelle@develry.be>
1233
- * @since 1.1.0
1234
- * @version 1.1.0
1235
- *
1236
- * @param {Object} options
1237
- *
1238
- * @return {Criteria}
1239
- */
1240
- Criteria.setMethod(function applyOldOptions(options) {
1241
-
1242
- if (!options || Object.isEmpty(options)) {
1243
- return this;
1244
- }
1245
-
1246
- let entry,
1247
- key;
1248
-
1249
- for (key in options) {
1250
- entry = options[key];
1251
-
1252
- switch (key) {
1253
- case 'sort' : this.sort(entry); break;
1254
- case 'limit' : this.limit(entry); break;
1255
- case 'fields' : this.select(entry); break;
1256
- case 'select' : this.select(entry); break;
1257
- case 'recursive' : this.recursive(entry); break;
1258
- case 'offset' : this.skip(entry); break;
1259
- case 'populate' : this.populate(entry); break;
1260
-
1261
- case 'conditions':
1262
- this.applyOldConditions(entry);
1263
- break;
1264
-
1265
- default:
1266
- this.setOption(key, entry);
1267
- }
1268
- }
1269
-
1270
- return this;
1271
- });
1272
-
1273
- /**
1274
- * Apply old-style mongodb conditions
1275
- *
1276
- * @author Jelle De Loecker <jelle@develry.be>
1277
- * @since 1.1.0
1278
- * @version 1.1.0
1279
- *
1280
- * @param {Object} conditions
1281
- *
1282
- * @return {Criteria}
1283
- */
1284
- Criteria.setMethod(function applyOldConditions(conditions) {
1285
-
1286
- if (!conditions || Object.isEmpty(conditions)) {
1287
- return this;
1288
- }
1289
-
1290
- let context = this,
1291
- value,
1292
- group,
1293
- key,
1294
- i;
1295
-
1296
- if (Array.isArray(conditions)) {
1297
- for (i = 0; i < conditions.length; i++) {
1298
- context.applyOldConditions(conditions[i]);
1299
- }
1300
-
1301
- return context;
1302
- }
1303
-
1304
- i = -1;
1305
-
1306
- for (key in conditions) {
1307
- value = conditions[key];
1308
- i++;
1309
-
1310
- if (key[0] == '$') {
1311
-
1312
- // Make sure no field is currently active,
1313
- // else it'll be added to the $and or $or group
1314
- context._active = null;
1315
-
1316
- if (key == '$or') {
1317
- group = context.or();
1318
- } else if (key == '$and') {
1319
-
1320
- group = context.and();
1321
- } else {
1322
- throw new Error('Unable to parse "' + key + '" group');
1323
- }
1324
-
1325
- group.applyOldConditions(value);
1326
-
1327
- } else {
1328
-
1329
- if (i) {
1330
- // If an object contains multiple items,
1331
- // that's always treated as an $and group
1332
- context = context.and();
1333
- }
1334
-
1335
- context = context.where(key);
1336
-
1337
- if (Array.isArray(value)) {
1338
- context = context.in(value);
1339
- continue;
1340
- } else if (value && typeof value == 'object') {
1341
- let had_dollar,
1342
- method,
1343
- key;
1344
-
1345
- for (key in value) {
1346
- if (key[0] == '$') {
1347
- had_dollar = true;
1348
- method = key.slice(1);
1349
-
1350
- if (typeof context[method] != 'function') {
1351
- if (method == 'regex') {
1352
- let regex;
1353
-
1354
- if (typeof value[key] == 'string') {
1355
- regex = RegExp.interpret(RegExp.escape(value[key]), value.$options);
1356
- } else {
1357
- regex = RegExp.interpret(value[key], value.$options);
1358
- }
1359
-
1360
- context.contains(regex);
1361
- break;
1362
- } else {
1363
- throw new Error('Unable to parse "' + key + '" expression');
1364
- }
1365
- } else {
1366
- context[method](value[key]);
1367
- }
1368
- }
1369
- }
1370
-
1371
- if (had_dollar) {
1372
- continue;
1373
- }
1374
- }
1375
-
1376
- context.equals(value);
1377
- }
1378
- }
1379
- });
1380
-
1381
- /**
1382
- * The Criteria Select class
1383
- *
1384
- * @author Jelle De Loecker <jelle@develry.be>
1385
- * @since 1.1.0
1386
- * @version 1.1.0
1387
- */
1388
- var Select = Function.inherits('Alchemy.Base', 'Alchemy.Criteria', function Select(criteria) {
1389
- // The parent criteria instance
1390
- this.criteria = criteria;
1391
- });
1392
-
1393
- /**
1394
- * Revive the given object
1395
- *
1396
- * @author Jelle De Loecker <jelle@develry.be>
1397
- * @since 1.1.0
1398
- * @version 1.2.3
1399
- *
1400
- * @return {Select}
1401
- */
1402
- Select.setStatic(function revive(data, criteria) {
1403
-
1404
- if (!data) {
1405
- return;
1406
- }
1407
-
1408
- let result = new Select(criteria),
1409
- key;
1410
-
1411
- result.fields = data.fields;
1412
-
1413
- if (data.associations) {
1414
- result.associations = {};
1415
-
1416
- for (key in data.associations) {
1417
- result.associations[key] = Select.revive(data.associations[key], criteria);
1418
- }
1419
- }
1420
-
1421
- if (data.association_name) {
1422
- result.association_name = data.association_name;
1423
- }
1424
-
1425
- return result;
1426
- });
1427
-
1428
- /**
1429
- * Return object to jsonify
1430
- *
1431
- * @author Jelle De Loecker <jelle@develry.be>
1432
- * @since 1.1.0
1433
- * @version 1.1.0
1434
- *
1435
- * @return {Object}
1436
- */
1437
- Select.setMethod(function toJSON() {
1438
- return {
1439
- association_name : this.association_name,
1440
- associations : this.associations,
1441
- fields : this.fields
1442
- };
1443
- });
1444
-
1445
- /**
1446
- * Return the elements to checksum in place of this object
1447
- *
1448
- * @author Jelle De Loecker <jelle@develry.be>
1449
- * @since 1.1.0
1450
- * @version 1.1.0
1451
- */
1452
- Select.setMethod(Blast.checksumSymbol, function toChecksum() {
1453
-
1454
- var result = [];
1455
-
1456
- if (this.associations) {
1457
- result.push(this.associations);
1458
- }
1459
-
1460
- if (this.fields) {
1461
- result.push(this.fields);
1462
- }
1463
-
1464
- if (this.association_name) {
1465
- result.push(this.association_name);
1466
- }
1467
-
1468
- if (!result.length) {
1469
- return null;
1470
- }
1471
-
1472
- return result;
1473
- });
1474
-
1475
- /**
1476
- * Add an association
1477
- *
1478
- * @author Jelle De Loecker <jelle@elevenways.be>
1479
- * @since 1.1.0
1480
- * @version 1.3.4
1481
- *
1482
- * @param {String} name
1483
- *
1484
- * @return {Select} This creates a new Select instance
1485
- */
1486
- Select.setMethod(function addAssociation(name) {
1487
-
1488
- if (!this.criteria?.model) {
1489
- throw new Error('Unable to select an association: this Criteria has no model info');
1490
- }
1491
-
1492
- var pieces;
1493
-
1494
- if (!this.associations) {
1495
- this.associations = {};
1496
- }
1497
-
1498
- if (Array.isArray(name)) {
1499
- pieces = name;
1500
- } else if (name.indexOf('.') > -1) {
1501
- pieces = name.split('.');
1502
- }
1503
-
1504
- if (pieces && pieces.length) {
1505
- let context = this;
1506
-
1507
- while (pieces.length) {
1508
- name = pieces.shift();
1509
- context = context.addAssociation(name);
1510
- }
1511
-
1512
- return context;
1513
- }
1514
-
1515
- if (!this.associations[name]) {
1516
- this.associations[name] = new Select(this.criteria);
1517
- this.associations[name].association_name = name;
1518
- }
1519
-
1520
- // Get the association data
1521
- try {
1522
- let info = this.criteria.model.getAssociation(name);
1523
-
1524
- if (info) {
1525
- // Make sure the localkey is added to the resultset
1526
- this.requireFieldForQuery(info.options.localKey);
1527
- }
1528
- } catch (err) {
1529
- console.warn('Failed to find "' + name + '" association for ' + this.criteria.model.model_name);
1530
- }
1531
-
1532
- return this.associations[name];
1533
- });
1534
-
1535
- /**
1536
- * Require a field for query purposes
1537
- *
1538
- * @author Jelle De Loecker <jelle@develry.be>
1539
- * @since 1.2.0
1540
- * @version 1.2.0
1541
- *
1542
- * @param {String} path
1543
- */
1544
- Select.setMethod(function requireFieldForQuery(path) {
1545
-
1546
- if (!this.query_fields) {
1547
- this.query_fields = [];
1548
- }
1549
-
1550
- this.query_fields.push(path);
1551
- });
1552
-
1553
- /**
1554
- * Add a field
1555
- *
1556
- * @author Jelle De Loecker <jelle@develry.be>
1557
- * @since 1.1.0
1558
- * @version 1.1.0
1559
- *
1560
- * @param {String} path
1561
- */
1562
- Select.setMethod(function addField(path) {
1563
-
1564
- if (!this.fields) {
1565
- this.fields = [];
1566
- }
1567
-
1568
- this.fields.push(path);
1569
- });
1570
-
1571
- /**
1572
- * Parse a path meant to add as a selection
1573
- *
1574
- * @author Jelle De Loecker <jelle@develry.be>
1575
- * @since 1.1.0
1576
- * @version 1.2.0
1577
- *
1578
- * @param {String|Object} path
1579
- *
1580
- * @return {Criteria|Null} A criteria object if the context has changed
1581
- */
1582
- Select.setMethod(function parse(path) {
1583
-
1584
- let context,
1585
- select = this,
1586
- parsed;
1587
-
1588
- if (typeof path == 'object' && path && path.name) {
1589
-
1590
- if (path.path) {
1591
- path = path.path;
1592
- } else {
1593
- let obj = path;
1594
- path = obj.name;
1595
-
1596
- if (obj.association) {
1597
- path = obj.association + '.' + path;
1598
- }
1599
- }
1600
- }
1601
-
1602
- parsed = Criteria.parsePath(path, this.criteria);
1603
-
1604
- // Associations were found,
1605
- // like "Comment._id" or "Comment.User"
1606
- if (parsed.association) {
1607
- let name,
1608
- i;
1609
-
1610
- for (i = 0; i < parsed.association.length; i++) {
1611
- name = parsed.association[i];
1612
-
1613
- if (this.model && this.model.name == name) {
1614
- continue;
1615
- }
1616
-
1617
- select = select.addAssociation(name);
1618
- }
1619
- }
1620
-
1621
- if (parsed.target_path) {
1622
- select.addField(parsed.target_path);
1623
- } else if (parsed.association) {
1624
- // When only an association was given, then the context changes
1625
- context = this.criteria.augment('select');
1626
- context._select = select;
1627
- return context;
1628
- }
1629
- });
1630
-
1631
- /**
1632
- * Clone this select for the given criteria
1633
- *
1634
- * @author Jelle De Loecker <jelle@develry.be>
1635
- * @since 1.1.0
1636
- * @version 1.2.0
1637
- *
1638
- * @param {Criteria} criteria
1639
- *
1640
- * @return {Select}
1641
- */
1642
- Select.setMethod(function cloneForCriteria(criteria) {
1643
-
1644
- var clone = new Select(criteria);
1645
-
1646
- if (this.association_name) {
1647
- clone.association_name = this.association_name;
1648
- }
1649
-
1650
- if (this.fields && this.fields.length) {
1651
- clone.fields = this.fields.slice(0);
1652
- }
1653
-
1654
- if (this.query_fields && this.query_fields.length) {
1655
- clone.query_fields = this.query_fields.slice(0);
1656
- }
1657
-
1658
- if (this.associations) {
1659
- let key;
1660
-
1661
- clone.associations = {};
1662
-
1663
- for (key in this.associations) {
1664
- clone.associations[key] = this.associations[key].cloneForCriteria(criteria);
1665
- }
1666
- }
1667
-
1668
- return clone;
1669
- });
1670
-
1671
- /**
1672
- * Should the given association be queried according to this select?
1673
- * (The Criteria instance can also have a recursive level set)
1674
- *
1675
- * @author Jelle De Loecker <jelle@develry.be>
1676
- * @since 1.1.0
1677
- * @version 1.1.0
1678
- *
1679
- * @param {String} name
1680
- *
1681
- * @return {Boolean}
1682
- */
1683
- Select.setMethod(function shouldQueryAssociation(name) {
1684
-
1685
- if (this.associations) {
1686
- return !!this.associations[name];
1687
- }
1688
-
1689
- return false;
1690
- });
1691
-
1692
- /**
1693
- * The Base Criteria Expression class
1694
- *
1695
- * @author Jelle De Loecker <jelle@develry.be>
1696
- * @since 1.1.0
1697
- * @version 1.1.0
1698
- */
1699
- var Expression = Function.inherits('Alchemy.Base', 'Alchemy.Criteria.Expression', function Expression(criteria) {
1700
-
1701
- // The parent criteria instance
1702
- this.criteria = criteria;
1703
-
1704
- // The current group it's in
1705
- this.current_group = null;
1706
-
1707
- // The items this contains
1708
- this.items = [];
1709
- });
1710
-
1711
- /**
1712
- * Revive an object
1713
- *
1714
- * @author Jelle De Loecker <jelle@develry.be>
1715
- * @since 1.1.0
1716
- * @version 1.1.0
1717
- *
1718
- * @return {Expression}
1719
- */
1720
- Expression.setStatic(function revive(data, criteria) {
1721
-
1722
- if (data._type == 'group') {
1723
- return Group.revive(data, criteria);
1724
- } else if (data._type == 'field') {
1725
- return FieldExpression.revive(data, criteria);
1726
- } else {
1727
- throw new Error('Unable to revive "' + data._type + '" expression');
1728
- }
1729
-
1730
- });
1731
-
1732
- /**
1733
- * Does this expression do something with an association,
1734
- * and do we need to normalize this on the current given datasource?
1735
- *
1736
- * @author Jelle De Loecker <jelle@develry.be>
1737
- * @since 1.1.0
1738
- * @version 1.1.0
1739
- *
1740
- * @type {Boolean}
1741
- */
1742
- Expression.setProperty(function requires_association_normalization() {
1743
-
1744
- if (!this.association) {
1745
- return false;
1746
- }
1747
-
1748
- if (!this.datasource.supports('querying_associations')) {
1749
- return true;
1750
- }
1751
-
1752
- return false;
1753
- });
1754
-
1755
- /**
1756
- * Create a reference to the datasource
1757
- *
1758
- * @author Jelle De Loecker <jelle@develry.be>
1759
- * @since 1.1.0
1760
- * @version 1.1.0
1761
- *
1762
- * @type {Datasource}
1763
- */
1764
- Expression.setProperty(function datasource() {
1765
- if (this.criteria) {
1766
- return this.criteria.datasource;
1767
- }
1768
- });
1769
-
1770
- /**
1771
- * Create a reference to the model
1772
- *
1773
- * @author Jelle De Loecker <jelle@develry.be>
1774
- * @since 1.1.0
1775
- * @version 1.1.0
1776
- *
1777
- * @type {Model}
1778
- */
1779
- Expression.setProperty(function model() {
1780
- if (this.criteria) {
1781
- return this.criteria.model;
1782
- }
1783
- });
1784
-
1785
- /**
1786
- * Create a reference to the field instance
1787
- *
1788
- * @author Jelle De Loecker <jelle@develry.be>
1789
- * @since 1.1.0
1790
- * @version 1.1.0
1791
- *
1792
- * @type {FieldType}
1793
- */
1794
- Expression.setProperty(function field() {
1795
- if (this.criteria && this.criteria.model) {
1796
- return this.criteria.model.schema.getField(this.target_path);
1797
- }
1798
- });
1799
-
1800
- /**
1801
- * Return the elements to checksum in place of this object
1802
- *
1803
- * @author Jelle De Loecker <jelle@develry.be>
1804
- * @since 1.1.0
1805
- * @version 1.1.0
1806
- */
1807
- Expression.setMethod(Blast.checksumSymbol, function toChecksum() {
1808
- return this.items;
1809
- });
1810
-
1811
- /**
1812
- * Get a clone without any Criteria or Group links
1813
- *
1814
- * @author Jelle De Loecker <jelle@develry.be>
1815
- * @since 1.1.0
1816
- * @version 1.1.0
1817
- *
1818
- * @return {Expression}
1819
- */
1820
- Expression.setMethod(function getCleanClone() {
1821
-
1822
- var result = new this.constructor();
1823
-
1824
- // Add the cloned array
1825
- result.items = JSON.clone(this.items);
1826
-
1827
- if (this.association) {
1828
- result.association = this.association;
1829
- }
1830
-
1831
- if (this.target_path) {
1832
- result.target_path = this.target_path;
1833
- }
1834
-
1835
- if (this.group_type) {
1836
- result.group_type = this.group_type;
1837
- }
1838
-
1839
- if (this.db_property) {
1840
- result.db_property = this.db_property;
1841
- }
1842
-
1843
- return result;
1844
- });
1845
-
1846
- /**
1847
- * Move the expression to the given group (object)
1848
- * or the given group type (and/or)
1849
- *
1850
- * @author Jelle De Loecker <jelle@develry.be>
1851
- * @since 1.1.0
1852
- * @version 1.1.0
1853
- *
1854
- * @param {Object|String} group
1855
- */
1856
- Expression.setMethod(function moveToGroup(group) {
1857
-
1858
- var context = this.criteria;
1859
-
1860
- // Move to a specific type?
1861
- if (typeof group == 'string') {
1862
-
1863
- // Is it already part of the same type of group?
1864
- // Then we can ignore this call
1865
- if (this.current_group && this.current_group.group_type == group) {
1866
- return context;
1867
- }
1868
-
1869
- // Create a new group of the given type
1870
- context = context.createGroup(group);
1871
- group = context.group;
1872
- }
1873
-
1874
- if (this.current_group) {
1875
- this.current_group.removeItem(this);
1876
- }
1877
-
1878
- this.current_group = group;
1879
- group.addItem(this);
1880
-
1881
- return context;
1882
- });
1883
-
1884
- /**
1885
- * Remove this expression from the criteria
1886
- *
1887
- * @author Jelle De Loecker <jelle@develry.be>
1888
- * @since 1.1.0
1889
- * @version 1.1.0
1890
- */
1891
- Expression.setMethod(function remove() {
1892
-
1893
- if (arguments.length) {
1894
- throw new Error('Expression#remove() can only remove itself');
1895
- }
1896
-
1897
- let index = this.criteria.all_expressions.indexOf(this);
1898
-
1899
- if (index > -1) {
1900
- this.criteria.all_expressions.splice(index, 1);
1901
- }
1902
-
1903
- if (this.current_group) {
1904
- this.current_group.removeItem(this);
1905
- }
1906
- });
1907
-
1908
- /**
1909
- * Add an item
1910
- *
1911
- * @author Jelle De Loecker <jelle@develry.be>
1912
- * @since 1.1.0
1913
- * @version 1.1.0
1914
- *
1915
- * @param {Object} item
1916
- */
1917
- Expression.setMethod(function addItem(item) {
1918
- this.items.push(item);
1919
- return item;
1920
- });
1921
-
1922
- /**
1923
- * Remove an item from this expression
1924
- *
1925
- * @author Jelle De Loecker <jelle@develry.be>
1926
- * @since 1.1.0
1927
- * @version 1.1.0
1928
- *
1929
- * @param {Object} item
1930
- */
1931
- Expression.setMethod(function removeItem(item) {
1932
-
1933
- var index = this.items.indexOf(item);
1934
-
1935
- // If this expression was found in the current group, remove it
1936
- if (index > -1) {
1937
- this.items.splice(index, 1);
1938
- return true;
1939
- }
1940
-
1941
- return false;
1942
- });
1943
-
1944
- /**
1945
- * The Field Expression class
1946
- *
1947
- * @author Jelle De Loecker <jelle@develry.be>
1948
- * @since 1.1.0
1949
- * @version 1.1.0
1950
- */
1951
- var FieldExpression = Function.inherits('Alchemy.Criteria.Expression', function Field(criteria, field) {
1952
-
1953
- Field.super.call(this, criteria);
1954
-
1955
- if (arguments.length) {
1956
- // Set the target
1957
- this.setTargetPath(field);
1958
- }
1959
- });
1960
-
1961
- /**
1962
- * Revive an object
1963
- *
1964
- * @author Jelle De Loecker <jelle@develry.be>
1965
- * @since 1.1.0
1966
- * @version 1.1.0
1967
- *
1968
- * @return {FieldExpression}
1969
- */
1970
- FieldExpression.setStatic(function revive(data, criteria) {
1971
-
1972
- var result = new FieldExpression();
1973
-
1974
- result.criteria = criteria;
1975
-
1976
- criteria.all_expressions.push(result);
1977
-
1978
- if (data.association) {
1979
- result.association = data.association;
1980
- }
1981
-
1982
- if (data.target_path) {
1983
- result.target_path = data.target_path;
1984
- }
1985
-
1986
- if (data.items) {
1987
- result.items = data.items;
1988
- }
1989
-
1990
- if (data.db_property) {
1991
- result.db_property = data.db_property;
1992
- }
1993
-
1994
- return result;
1995
- });
1996
-
1997
- /**
1998
- * Return object to jsonify
1999
- *
2000
- * @author Jelle De Loecker <jelle@develry.be>
2001
- * @since 1.1.0
2002
- * @version 1.1.0
2003
- *
2004
- * @return {Object}
2005
- */
2006
- FieldExpression.setMethod(function toJSON() {
2007
- return {
2008
- _type : 'field',
2009
- association : this.association,
2010
- target_path : this.target_path,
2011
- items : this.items,
2012
- db_property : this.db_property
2013
- };
2014
- });
2015
-
2016
- /**
2017
- * Return the elements to checksum in place of this object
2018
- *
2019
- * @author Jelle De Loecker <jelle@develry.be>
2020
- * @since 1.1.0
2021
- * @version 1.1.0
2022
- */
2023
- FieldExpression.setMethod(Blast.checksumSymbol, function toChecksum() {
2024
- return [this.target_path, this.items];
2025
- });
2026
-
2027
- /**
2028
- * Set the target
2029
- *
2030
- * @author Jelle De Loecker <jelle@develry.be>
2031
- * @since 1.1.0
2032
- * @version 1.1.0
2033
- *
2034
- * @param {String} path
2035
- */
2036
- FieldExpression.setMethod(function setTargetPath(path) {
2037
-
2038
- var pieces;
2039
-
2040
- if (typeof path == 'string') {
2041
- if (path.indexOf('.') > -1) {
2042
- pieces = path.split('.');
2043
- }
2044
- } else if (Array.isArray(path)) {
2045
- pieces = path;
2046
- } else {
2047
- throw new Error('Field#setTargetPath(path) requires a string or an array');
2048
- }
2049
-
2050
- if (!pieces) {
2051
- this.target_path = path;
2052
- } else {
2053
- let first = pieces[0];
2054
-
2055
- // @TODO: better check if the first part is an association
2056
- if (first[0].isUpperCase()) {
2057
- this.association = first;
2058
- pieces.shift();
2059
- }
2060
-
2061
- this.target_path = pieces.join('.');
2062
- }
2063
-
2064
- });
2065
-
2066
- /**
2067
- * Add a new condition item
2068
- *
2069
- * @author Jelle De Loecker <jelle@develry.be>
2070
- * @since 1.1.0
2071
- * @version 1.1.5
2072
- *
2073
- * @param {Object|String} group
2074
- *
2075
- * @return {Object}
2076
- */
2077
- FieldExpression.setMethod(function addItem(type, value) {
2078
-
2079
- var entry = {
2080
- type : type
2081
- };
2082
-
2083
- if (arguments.length > 1) {
2084
-
2085
- if (typeof value == 'object' && value && value instanceof Classes.Alchemy.Base) {
2086
- throw new Classes.Alchemy.Error.Model('"' + value.constructor.getClassPath() + '" instance was given as a "' + this.target_path + '" condition');
2087
- }
2088
-
2089
- entry.value = value;
2090
- }
2091
-
2092
- this.items.push(entry);
2093
-
2094
- return entry;
2095
- });
2096
-
2097
- /**
2098
- * Normalize the values (like casting strings to objectids)
2099
- *
2100
- * @author Jelle De Loecker <jelle@develry.be>
2101
- * @since 1.1.0
2102
- * @version 1.1.0
2103
- *
2104
- * @return {Pledge|Null}
2105
- */
2106
- FieldExpression.setMethod(function normalize() {
2107
-
2108
- var field = this.field;
2109
-
2110
- if (!field) {
2111
- return;
2112
- }
2113
-
2114
- let item,
2115
- i;
2116
-
2117
- for (i = 0; i < this.items.length; i++) {
2118
- item = this.items[i];
2119
-
2120
- // Skip field_expression items
2121
- // (like $exists: true and such)
2122
- // or items where `cast` is explicitly false
2123
- if (item.field_expression || item.cast === false) {
2124
- continue;
2125
- }
2126
-
2127
- if (item.value != null) {
2128
- if (Array.isArray(item.value)) {
2129
- let result = [],
2130
- i;
2131
-
2132
- for (i = 0; i < item.value.length; i++) {
2133
- result[i] = field.castCondition(item.value[i], this);
2134
- }
2135
-
2136
- item.value = result;
2137
- } else {
2138
- item.value = field.castCondition(item.value, this);
2139
- }
2140
- }
2141
- }
2142
- });
2143
-
2144
- /**
2145
- * Normalize association values by separately querying them
2146
- * and adding the found values to the criteria
2147
- *
2148
- * @WARNING: this can be extremely resource intensive when using
2149
- * on big collections.
2150
- *
2151
- * @author Jelle De Loecker <jelle@develry.be>
2152
- * @since 1.1.0
2153
- * @version 1.1.0
2154
- *
2155
- * @return {Pledge}
2156
- */
2157
- FieldExpression.setMethod(function normalizeAssociationValues() {
2158
-
2159
- if (!this.association) {
2160
- return Pledge.reject(new Error('Unable to normalize a field without an association'));
2161
- }
2162
-
2163
- let that = this,
2164
- association = this.model.getAssociation(this.association),
2165
- assoc_model = this.model.getModel(association.modelName),
2166
- assoc_crit = new Classes.Alchemy.Criteria(),
2167
- pledge = new Pledge(),
2168
- clone = this.getCleanClone(),
2169
- item,
2170
- i;
2171
-
2172
- // Unset the association
2173
- clone.association = null;
2174
-
2175
- // Add the clone
2176
- assoc_crit.addNewExpression(clone);
2177
-
2178
- // Only select the wanted field
2179
- assoc_crit.select(association.options.foreignKey);
2180
-
2181
- assoc_model.find('all', {criteria: assoc_crit, document: false}, function gotAssocItems(err, items) {
2182
-
2183
- if (err) {
2184
- return pledge.reject(err);
2185
- }
2186
-
2187
- let values = [],
2188
- record,
2189
- i;
2190
-
2191
- for (i = 0; i < items.length; i++) {
2192
- values.push(items[i][association.options.foreignKey]);
2193
- }
2194
-
2195
- if (!values.length) {
2196
- // @TODO: make this more elegant
2197
- values.push('_impossible_');
2198
- }
2199
-
2200
- // Remove the expression from the criteria
2201
- that.remove();
2202
-
2203
- // And add this new one
2204
- that.criteria.where(association.options.localKey).in(values);
2205
-
2206
- pledge.resolve();
2207
- });
2208
-
2209
- return pledge;
2210
- });
2211
-
2212
- /**
2213
- * The Group Expression class
2214
- *
2215
- * @author Jelle De Loecker <jelle@develry.be>
2216
- * @since 1.1.0
2217
- * @version 1.1.0
2218
- */
2219
- var Group = Function.inherits('Alchemy.Criteria.Expression', function Group(criteria, type) {
2220
-
2221
- Group.super.call(this, criteria);
2222
-
2223
- // The group type
2224
- this.group_type = type;
2225
- });
2226
-
2227
- /**
2228
- * Revive an object
2229
- *
2230
- * @author Jelle De Loecker <jelle@develry.be>
2231
- * @since 1.1.0
2232
- * @version 1.1.0
2233
- *
2234
- * @return {Group}
2235
- */
2236
- Group.setStatic(function revive(data, criteria) {
2237
-
2238
- var result = new Group(criteria),
2239
- i;
2240
-
2241
- result.group_type = data.group_type;
2242
-
2243
- if (data.items) {
2244
- result.items = [];
2245
-
2246
- for (i = 0; i < data.items.length; i++) {
2247
- result.items.push(Expression.revive(data.items[i], criteria));
2248
- }
2249
- }
2250
-
2251
- return result;
2252
- });
2253
-
2254
- /**
2255
- * Return object to jsonify
2256
- *
2257
- * @author Jelle De Loecker <jelle@develry.be>
2258
- * @since 1.1.0
2259
- * @version 1.1.0
2260
- *
2261
- * @return {Object}
2262
- */
2263
- Group.setMethod(function toJSON() {
2264
- return {
2265
- _type : 'group',
2266
- association : this.association,
2267
- group_type : this.group_type,
2268
- items : this.items.slice(0)
2269
- };
2270
- });
2271
-
2272
- /**
2273
- * Return the elements to checksum in place of this object
2274
- *
2275
- * @author Jelle De Loecker <jelle@develry.be>
2276
- * @since 1.1.0
2277
- * @version 1.1.0
2278
- */
2279
- Group.setMethod(Blast.checksumSymbol, function toChecksum() {
2280
- return [this.group_type, this.items];
2281
- });
2282
-
2283
- // PROTOBLAST START CUT
2284
- /**
2285
- * Make the Criteria class a global
2286
- *
2287
- * @author Jelle De Loecker <jelle@develry.be>
2288
- * @since 1.1.0
2289
- * @version 1.1.0
2290
- *
2291
- * @type {Function}
2292
- */
2293
- global.Criteria = Criteria;
2294
- // PROTOBLAST END CUT