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,18 +1,16 @@
1
1
  /**
2
2
  * NoSQL Datasource
3
3
  *
4
- * @author Jelle De Loecker <jelle@develry.be>
4
+ * @author Jelle De Loecker <jelle@elevenways.be>
5
5
  * @since 1.1.0
6
6
  * @version 1.1.0
7
7
  */
8
- var NoSQL = Function.inherits('Alchemy.Datasource', function Nosql(name, options) {
9
- Nosql.super.call(this, name, options);
10
- });
8
+ var NoSQL = Function.inherits('Alchemy.Datasource', 'Nosql');
11
9
 
12
10
  /**
13
11
  * All comparison functions
14
12
  *
15
- * @author Jelle De Loecker <jelle@develry.be>
13
+ * @author Jelle De Loecker <jelle@elevenways.be>
16
14
  * @since 1.0.0
17
15
  * @version 1.0.0
18
16
  */
@@ -21,7 +19,7 @@ NoSQL.setStatic('comparisons', {});
21
19
  /**
22
20
  * All logical operator functions
23
21
  *
24
- * @author Jelle De Loecker <jelle@develry.be>
22
+ * @author Jelle De Loecker <jelle@elevenways.be>
25
23
  * @since 1.0.0
26
24
  * @version 1.0.0
27
25
  */
@@ -30,7 +28,7 @@ NoSQL.setStatic('logical_operators', {});
30
28
  /**
31
29
  * Add comparison function
32
30
  *
33
- * @author Jelle De Loecker <jelle@develry.be>
31
+ * @author Jelle De Loecker <jelle@elevenways.be>
34
32
  * @since 1.0.0
35
33
  * @version 1.0.0
36
34
  */
@@ -41,7 +39,7 @@ NoSQL.setStatic(function addComparison(fnc) {
41
39
  /**
42
40
  * Add logical operator function
43
41
  *
44
- * @author Jelle De Loecker <jelle@develry.be>
42
+ * @author Jelle De Loecker <jelle@elevenways.be>
45
43
  * @since 1.0.0
46
44
  * @version 1.0.0
47
45
  */
@@ -52,7 +50,7 @@ NoSQL.setStatic(function addLogicalOperator(fnc) {
52
50
  /**
53
51
  * Lower than comparison
54
52
  *
55
- * @author Jelle De Loecker <jelle@develry.be>
53
+ * @author Jelle De Loecker <jelle@elevenways.be>
56
54
  * @since 1.0.0
57
55
  * @version 1.0.0
58
56
  */
@@ -63,7 +61,7 @@ NoSQL.addComparison(function $lt(a, b) {
63
61
  /**
64
62
  * Lower than or equal comparison
65
63
  *
66
- * @author Jelle De Loecker <jelle@develry.be>
64
+ * @author Jelle De Loecker <jelle@elevenways.be>
67
65
  * @since 1.0.0
68
66
  * @version 1.0.0
69
67
  */
@@ -74,7 +72,7 @@ NoSQL.addComparison(function $lte(a, b) {
74
72
  /**
75
73
  * Greater than comparison
76
74
  *
77
- * @author Jelle De Loecker <jelle@develry.be>
75
+ * @author Jelle De Loecker <jelle@elevenways.be>
78
76
  * @since 1.0.0
79
77
  * @version 1.0.0
80
78
  */
@@ -85,7 +83,7 @@ NoSQL.addComparison(function $gt(a, b) {
85
83
  /**
86
84
  * Greater than or equal comparison
87
85
  *
88
- * @author Jelle De Loecker <jelle@develry.be>
86
+ * @author Jelle De Loecker <jelle@elevenways.be>
89
87
  * @since 1.0.0
90
88
  * @version 1.0.0
91
89
  */
@@ -96,7 +94,7 @@ NoSQL.addComparison(function $gte(a, b) {
96
94
  /**
97
95
  * Not equal comparison
98
96
  *
99
- * @author Jelle De Loecker <jelle@develry.be>
97
+ * @author Jelle De Loecker <jelle@elevenways.be>
100
98
  * @since 1.0.0
101
99
  * @version 1.0.0
102
100
  */
@@ -112,7 +110,7 @@ NoSQL.addComparison(function $ne(a, b) {
112
110
  /**
113
111
  * In comparison
114
112
  *
115
- * @author Jelle De Loecker <jelle@develry.be>
113
+ * @author Jelle De Loecker <jelle@elevenways.be>
116
114
  * @since 1.0.0
117
115
  * @version 1.0.0
118
116
  */
@@ -136,7 +134,7 @@ NoSQL.addComparison(function $in(a, b) {
136
134
  /**
137
135
  * Not in comparison
138
136
  *
139
- * @author Jelle De Loecker <jelle@develry.be>
137
+ * @author Jelle De Loecker <jelle@elevenways.be>
140
138
  * @since 1.0.0
141
139
  * @version 1.0.0
142
140
  */
@@ -147,7 +145,7 @@ NoSQL.addComparison(function $nin(a, b) {
147
145
  /**
148
146
  * Regex comparison
149
147
  *
150
- * @author Jelle De Loecker <jelle@develry.be>
148
+ * @author Jelle De Loecker <jelle@elevenways.be>
151
149
  * @since 1.0.0
152
150
  * @version 1.0.0
153
151
  */
@@ -167,7 +165,7 @@ NoSQL.addComparison(function $regex(a, b) {
167
165
  /**
168
166
  * Exists comparison
169
167
  *
170
- * @author Jelle De Loecker <jelle@develry.be>
168
+ * @author Jelle De Loecker <jelle@elevenways.be>
171
169
  * @since 1.0.0
172
170
  * @version 1.0.0
173
171
  */
@@ -187,7 +185,7 @@ NoSQL.addComparison(function $exists(value, exists) {
187
185
  /**
188
186
  * Array size comparison
189
187
  *
190
- * @author Jelle De Loecker <jelle@develry.be>
188
+ * @author Jelle De Loecker <jelle@elevenways.be>
191
189
  * @since 1.0.0
192
190
  * @version 1.0.0
193
191
  */
@@ -207,7 +205,7 @@ NoSQL.addComparison(function $size(obj, value) {
207
205
  /**
208
206
  * Array elemMatch comparison
209
207
  *
210
- * @author Jelle De Loecker <jelle@develry.be>
208
+ * @author Jelle De Loecker <jelle@elevenways.be>
211
209
  * @since 1.0.0
212
210
  * @version 1.0.0
213
211
  */
@@ -231,7 +229,7 @@ NoSQL.addComparison(function $elemMatch(obj, value) {
231
229
  /**
232
230
  * Match any of the subqueries
233
231
  *
234
- * @author Jelle De Loecker <jelle@develry.be>
232
+ * @author Jelle De Loecker <jelle@elevenways.be>
235
233
  * @since 1.0.0
236
234
  * @version 1.0.0
237
235
  */
@@ -255,7 +253,7 @@ NoSQL.addLogicalOperator(function $or(obj, query) {
255
253
  /**
256
254
  * Match all of the subqueries
257
255
  *
258
- * @author Jelle De Loecker <jelle@develry.be>
256
+ * @author Jelle De Loecker <jelle@elevenways.be>
259
257
  * @since 1.0.0
260
258
  * @version 1.0.0
261
259
  */
@@ -279,7 +277,7 @@ NoSQL.addLogicalOperator(function $and(obj, query) {
279
277
  /**
280
278
  * Inverted match of the query
281
279
  *
282
- * @author Jelle De Loecker <jelle@develry.be>
280
+ * @author Jelle De Loecker <jelle@elevenways.be>
283
281
  * @since 1.0.0
284
282
  * @version 1.0.0
285
283
  */
@@ -290,7 +288,7 @@ NoSQL.addLogicalOperator(function $not(obj, query) {
290
288
  /**
291
289
  * Use a function to match
292
290
  *
293
- * @author Jelle De Loecker <jelle@develry.be>
291
+ * @author Jelle De Loecker <jelle@elevenways.be>
294
292
  * @since 1.0.0
295
293
  * @version 1.0.0
296
294
  */
@@ -316,14 +314,14 @@ NoSQL.addLogicalOperator(function $where(obj, fnc) {
316
314
  * If the treatObjAsValue flag is set,
317
315
  * don't try to match every part separately, but the array as a whole.
318
316
  *
319
- * @author Jelle De Loecker <jelle@develry.be>
317
+ * @author Jelle De Loecker <jelle@elevenways.be>
320
318
  * @since 1.0.0
321
319
  * @version 1.0.6
322
320
  *
323
321
  * @param {Object} obj
324
322
  * @param {Object} query
325
323
  *
326
- * @return {Boolean}
324
+ * @return {boolean}
327
325
  */
328
326
  NoSQL.setStatic(function matchQueryPart(obj, query_key, query_value, treat_obj_as_value) {
329
327
 
@@ -422,14 +420,14 @@ NoSQL.setStatic(function matchQueryPart(obj, query_key, query_value, treat_obj_a
422
420
  /**
423
421
  * Tell if a given document matches a query
424
422
  *
425
- * @author Jelle De Loecker <jelle@develry.be>
423
+ * @author Jelle De Loecker <jelle@elevenways.be>
426
424
  * @since 1.0.0
427
425
  * @version 1.0.0
428
426
  *
429
427
  * @param {Object} obj
430
428
  * @param {Object} query
431
429
  *
432
- * @return {Boolean}
430
+ * @return {boolean}
433
431
  */
434
432
  NoSQL.setStatic(function match(obj, query) {
435
433
 
@@ -471,11 +469,11 @@ NoSQL.setStatic(function match(obj, query) {
471
469
  /**
472
470
  * Parse a record path
473
471
  *
474
- * @author Jelle De Loecker <jelle@develry.be>
472
+ * @author Jelle De Loecker <jelle@elevenways.be>
475
473
  * @since 1.0.0
476
474
  * @version 1.0.0
477
475
  *
478
- * @param {String} path
476
+ * @param {string} path
479
477
  *
480
478
  * @return {Object}
481
479
  */
@@ -506,7 +504,7 @@ NoSQL.setStatic(function parseRecordPath(path) {
506
504
  /**
507
505
  * Get a value from object with dot notation
508
506
  *
509
- * @author Jelle De Loecker <jelle@develry.be>
507
+ * @author Jelle De Loecker <jelle@elevenways.be>
510
508
  * @since 1.0.0
511
509
  * @version 1.0.0
512
510
  *
@@ -562,11 +560,11 @@ NoSQL.setStatic(function getDotValue(obj, path) {
562
560
  /**
563
561
  * Are 2 values comparable
564
562
  *
565
- * @author Jelle De Loecker <jelle@develry.be>
563
+ * @author Jelle De Loecker <jelle@elevenways.be>
566
564
  * @since 1.0.0
567
565
  * @version 1.0.0
568
566
  *
569
- * @return {Boolean}
567
+ * @return {boolean}
570
568
  */
571
569
  NoSQL.setStatic(function areComparable(a, b) {
572
570
 
@@ -588,18 +586,72 @@ NoSQL.setStatic(function areComparable(a, b) {
588
586
  /**
589
587
  * Compile criteria into a MongoDB query object
590
588
  *
591
- * @author Jelle De Loecker <jelle@develry.be>
589
+ * @author Jelle De Loecker <jelle@elevenways.be>
592
590
  * @since 1.1.0
593
- * @version 1.3.16
591
+ * @version 1.4.0
594
592
  *
595
- * @param {Criteria} criteria
596
- * @param {Group} group
593
+ * @param {Criteria} criteria The criteria to convert
594
+ * @param {Object} context The optional context (for Trail retrieval)
597
595
  *
598
596
  * @return {Object}
599
597
  */
600
- NoSQL.setMethod(function compileCriteria(criteria, group) {
598
+ NoSQL.setStatic(function convertCriteriaToConditions(criteria, context) {
599
+
600
+ let config = {
601
+ for_database: false,
602
+ };
603
+
604
+ return convertCriteriaToConditionsWithConfig(criteria, config, context);
605
+ });
606
+
607
+ /**
608
+ * Compile criteria into a MongoDB query object with the given configuration
609
+ *
610
+ * @author Jelle De Loecker <jelle@elevenways.be>
611
+ * @since 1.1.0
612
+ * @version 1.4.0
613
+ *
614
+ * @param {Criteria} criteria The criteria to convert
615
+ * @param {Object} config Configuration flags
616
+ * @param {Object} context The optional context
617
+ *
618
+ * @return {Object}
619
+ */
620
+ function convertCriteriaToConditionsWithConfig(criteria, config, context) {
621
+
622
+ if (context) {
623
+ if (!Object.isPlainObject(context) || !context.$0) {
624
+ context = {$0: context};
625
+ }
626
+ }
627
+
628
+ if (!config) {
629
+ config = {
630
+ for_database : false,
631
+ };
632
+ }
633
+
634
+ return convertCriteriaGroupToConditions(criteria, criteria.group, config, context);
635
+ }
636
+
637
+ /**
638
+ * Compile criteria into a MongoDB query object
639
+ *
640
+ * @author Jelle De Loecker <jelle@elevenways.be>
641
+ * @since 1.1.0
642
+ * @version 1.4.0
643
+ *
644
+ * @param {Criteria} criteria The criteria to convert
645
+ * @param {Group} group The current group
646
+ * @param {Object} config Configuration flags
647
+ * @param {Object} context The optional context
648
+ *
649
+ * @return {Object}
650
+ */
651
+ function convertCriteriaGroupToConditions(criteria, group, config, context) {
601
652
 
602
- var assoc_model,
653
+ let is_for_database = config?.for_database,
654
+ assoc_model,
603
655
  aggregate,
604
656
  result = [],
605
657
  entry,
@@ -607,10 +659,6 @@ NoSQL.setMethod(function compileCriteria(criteria, group) {
607
659
  temp,
608
660
  i;
609
661
 
610
- if (!group) {
611
- group = criteria.group;
612
- }
613
-
614
662
  let getAggregate = () => {
615
663
  if (!aggregate) {
616
664
  aggregate = {
@@ -688,7 +736,7 @@ NoSQL.setMethod(function compileCriteria(criteria, group) {
688
736
  }
689
737
 
690
738
  if (entry.group_type) {
691
- let compiled_group = this.compileCriteria(entry.criteria, entry),
739
+ let compiled_group = convertCriteriaGroupToConditions(entry.criteria, entry, config, context),
692
740
  obj = {};
693
741
 
694
742
  if (compiled_group.$and) {
@@ -697,7 +745,7 @@ NoSQL.setMethod(function compileCriteria(criteria, group) {
697
745
  // @TODO: this won't work
698
746
  compiled_group = compiled_group.pipeline;
699
747
  } else {
700
- compiled_group = null;
748
+ compiled_group = Array.cast(compiled_group);
701
749
  }
702
750
 
703
751
  if (compiled_group) {
@@ -730,6 +778,13 @@ NoSQL.setMethod(function compileCriteria(criteria, group) {
730
778
  for (let i = 0; i < entry.items.length; i++) {
731
779
  item = entry.items[i];
732
780
 
781
+ if (context && item.value && typeof item.value == 'object') {
782
+ item = {
783
+ ...item,
784
+ value : Classes.Develry.Placeholder.deepResolve(item.value, context),
785
+ };
786
+ }
787
+
733
788
  // If the value is a RegExp, we might have to stringify the value
734
789
  if (shouldStringify(entry, item) && RegExp.isRegExp(item.value)) {
735
790
 
@@ -765,7 +820,19 @@ NoSQL.setMethod(function compileCriteria(criteria, group) {
765
820
  } else if (item.type == 'equals') {
766
821
  obj = item.value;
767
822
  } else if (item.type == 'contains') {
768
- obj = RegExp.interpret(item.value);
823
+
824
+ let do_regexp_search = true;
825
+
826
+ if (entry.field) {
827
+ if (entry.field.is_array || entry.field.options?.type == 'HasAndBelongsToMany') {
828
+ do_regexp_search = false;
829
+ obj = item.value;
830
+ }
831
+ }
832
+
833
+ if (do_regexp_search) {
834
+ obj = RegExp.interpret(item.value);
835
+ }
769
836
  } else if (item.type == 'in') {
770
837
 
771
838
  // @TODO: This shouldn't be needed,
@@ -791,31 +858,35 @@ NoSQL.setMethod(function compileCriteria(criteria, group) {
791
858
  }
792
859
  } else if (item.type == 'isEmpty') {
793
860
 
794
- let exists = false,
795
- comparator = '$eq';
861
+ if (is_for_database) {
862
+ let exists = false,
863
+ comparator = '$eq';
796
864
 
797
- if (item.value === false) {
798
- exists = true;
799
- comparator = '$ne';
800
- }
865
+ if (item.value === false) {
866
+ exists = true;
867
+ comparator = '$ne';
868
+ }
801
869
 
802
- let $or = [
803
- {$exists: exists}
804
- ];
870
+ let $or = [
871
+ {$exists: exists}
872
+ ];
805
873
 
806
- if (!entry.field) {
807
- throw new Error('Could not find field for path "' + entry.target_path + '" in model ' + entry.model.name);
808
- }
874
+ if (!entry.field) {
875
+ throw new Error('Could not find field for path "' + entry.target_path + '" in model ' + entry.model?.name);
876
+ }
809
877
 
810
- if (entry.field.is_array) {
811
- $or.push({[comparator]: []});
812
- } else if (entry.field instanceof Classes.Alchemy.Field.String) {
813
- $or.push({[comparator]: ''});
814
- }
878
+ if (entry.field.is_array) {
879
+ $or.push({[comparator]: []});
880
+ } else if (entry.field instanceof Classes.Alchemy.Field.String) {
881
+ $or.push({[comparator]: ''});
882
+ }
815
883
 
816
- $or.push({[comparator]: null});
884
+ $or.push({[comparator]: null});
817
885
 
818
- obj.$or = $or;
886
+ obj.$or = $or;
887
+ } else {
888
+ obj = {$isEmpty: item.value ?? true};
889
+ }
819
890
  } else {
820
891
  throw new Error('Unknown criteria expression: "' + item.type + '"');
821
892
  }
@@ -825,7 +896,7 @@ NoSQL.setMethod(function compileCriteria(criteria, group) {
825
896
 
826
897
  // Temporary fix to actually query translatable field contents
827
898
  // (entry.field can be undefined if trying to query a path)
828
- if (entry.field && entry.field.is_translatable) {
899
+ if (entry.field?.is_translatable) {
829
900
 
830
901
  let prefix = criteria.options.locale,
831
902
  specific_prefix = !!prefix;
@@ -937,16 +1008,36 @@ NoSQL.setMethod(function compileCriteria(criteria, group) {
937
1008
  }
938
1009
 
939
1010
  if (!result.length) {
940
- return {};
1011
+ result = {};
1012
+ } else if (result.length === 1) {
1013
+ result = result[0];
1014
+ } else {
1015
+ result = {$and: result};
941
1016
  }
942
1017
 
943
- return {$and: result};
1018
+ return result;
1019
+ };
1020
+
1021
+ /**
1022
+ * Compile criteria into a MongoDB-compatible query object
1023
+ *
1024
+ * @author Jelle De Loecker <jelle@elevenways.be>
1025
+ * @since 1.1.0
1026
+ * @version 1.4.0
1027
+ *
1028
+ * @param {Criteria} criteria
1029
+ * @param {Group} group
1030
+ *
1031
+ * @return {Object}
1032
+ */
1033
+ NoSQL.setMethod(function compileCriteria(criteria, group) {
1034
+ return convertCriteriaToConditionsWithConfig(criteria, {for_database: true}, group);
944
1035
  });
945
1036
 
946
1037
  /**
947
1038
  * Is the given item about a string field?
948
1039
  *
949
- * @author Jelle De Loecker <jelle@elevenways.be>
1040
+ * @author Jelle De Loecker <jelle@elevenways.be>
950
1041
  * @since 1.2.0
951
1042
  * @version 1.2.0
952
1043
  *
@@ -971,10 +1062,473 @@ function shouldStringify(entry, item) {
971
1062
  return false;
972
1063
  }
973
1064
 
1065
+ /**
1066
+ * Compile an AQL string into a MongoDB-compatible query object
1067
+ *
1068
+ * @author Jelle De Loecker <jelle@elevenways.be>
1069
+ * @since 1.4.0
1070
+ * @version 1.4.0
1071
+ *
1072
+ * @param {string} query
1073
+ *
1074
+ * @return {Object}
1075
+ */
1076
+ NoSQL.setStatic(function convertAQLToConditions(query) {
1077
+
1078
+ let tokens = tokenizeAQL(query);
1079
+
1080
+ let conditions = convertAQLTokens(tokens, (name, operator, value, not) => {
1081
+
1082
+ switch (operator) {
1083
+ case 'is':
1084
+ case 'eq':
1085
+ case '==':
1086
+ case '=':
1087
+ operator = '$eq';
1088
+ break;
1089
+
1090
+ case 'ne':
1091
+ case '!=':
1092
+ operator = '$ne';
1093
+ break;
1094
+
1095
+ case 'gt':
1096
+ case '>':
1097
+ operator = '$gt';
1098
+ break;
1099
+
1100
+ case 'gte':
1101
+ case '>=':
1102
+ operator = '$gte';
1103
+ break;
1104
+
1105
+ case 'lt':
1106
+ case '<':
1107
+ operator = '$lt';
1108
+ break;
1109
+
1110
+ case 'lte':
1111
+ case '<=':
1112
+ operator = '$lte';
1113
+ break;
1114
+
1115
+ case 'empty':
1116
+ operator = '$isEmpty';
1117
+ value = {value: !not};
1118
+ break;
1119
+ }
1120
+
1121
+ switch (value.type) {
1122
+ case 'name':
1123
+ value = Trail.fromDot(value.value);
1124
+ break;
1125
+
1126
+ case 'number':
1127
+ value = Number(value.value);
1128
+ break;
1129
+
1130
+ case 'boolean':
1131
+ value = value.value == 'true';
1132
+ break;
1133
+
1134
+ case 'string':
1135
+ value = String.parseQuoted(value.value);
1136
+ break;
1137
+
1138
+ default:
1139
+ value = value.value;
1140
+ break;
1141
+ }
1142
+
1143
+ let result = {
1144
+ [name]: {
1145
+ [operator]: value,
1146
+ }
1147
+ };
1148
+
1149
+ return result;
1150
+ });
1151
+
1152
+ return conditions;
1153
+ });
1154
+
1155
+ /**
1156
+ * Parse the AQL query into tokens
1157
+ *
1158
+ * @author Jelle De Loecker <jelle@elevenways.be>
1159
+ * @since 1.4.0
1160
+ * @version 1.4.0
1161
+ *
1162
+ * @param {string} query
1163
+ *
1164
+ * @return {Array}
1165
+ */
1166
+ function tokenizeAQL(query) {
1167
+
1168
+ const START_STATE = 1,
1169
+ NAME_STATE = 2,
1170
+ OPERATOR_STATE = 3,
1171
+ VALUE_STATE = 4;
1172
+
1173
+ let expressions = [],
1174
+ tokens = Function.tokenize(query, true),
1175
+ current_name,
1176
+ current_operator,
1177
+ current_value,
1178
+ current_not,
1179
+ lower,
1180
+ token,
1181
+ type,
1182
+ value,
1183
+ i;
1184
+
1185
+ let current_state = START_STATE;
1186
+ let states = [current_state];
1187
+
1188
+ const replaceState = state => {
1189
+ states[states.length - 1] = state;
1190
+ current_state = state;
1191
+ };
1192
+
1193
+ const pushState = state => {
1194
+ current_state = state;
1195
+ states.push(current_state);
1196
+ return current_state;
1197
+ };
1198
+
1199
+ const popState = () => {
1200
+ current_state = states.pop();
1201
+ return current_state;
1202
+ };
1203
+
1204
+ const createGroup = () => {
1205
+
1206
+ if (hasCurrent()) {
1207
+ closeValue();
1208
+ }
1209
+
1210
+ expressions.push(true);
1211
+ pushState(START_STATE);
1212
+ };
1213
+
1214
+ const closeGroup = () => {
1215
+
1216
+ if (hasCurrent()) {
1217
+ closeValue();
1218
+ }
1219
+
1220
+ popState();
1221
+ expressions.push(false);
1222
+ };
1223
+
1224
+ const setValue = (token) => {
1225
+
1226
+ if (token === true) {
1227
+ current_value = true;
1228
+ } else {
1229
+
1230
+ let new_value = {
1231
+ type : token.type,
1232
+ value: token.value,
1233
+ not : !!current_not,
1234
+ };
1235
+
1236
+ if (current_value) {
1237
+ if (current_value.type == 'name') {
1238
+ current_value.value = current_value.value + new_value.value;
1239
+ } else {
1240
+ reject('Unexpected value');
1241
+ }
1242
+ } else {
1243
+ current_value = new_value;
1244
+ }
1245
+ }
1246
+ };
1247
+
1248
+ const hasCurrent = () => !!(current_name || current_operator || current_value);
1249
+
1250
+ const closeValue = (next_logic_type) => {
1251
+
1252
+ if (!hasCurrent()) {
1253
+ reject('Incomplete statement');
1254
+ }
1255
+
1256
+ let entry = [
1257
+ current_name,
1258
+ current_operator,
1259
+ current_value,
1260
+ current_not,
1261
+ next_logic_type,
1262
+ ];
1263
+
1264
+ expressions.push(entry);
1265
+
1266
+ current_name = null;
1267
+ current_operator = null;
1268
+ current_value = null;
1269
+ current_not = null;
1270
+ replaceState(START_STATE);
1271
+ }
1272
+
1273
+ const reject = (message) => {
1274
+ throw new Error(message);
1275
+ };
1276
+
1277
+ for (i = 0; i < tokens.length; i++) {
1278
+ token = tokens[i];
1279
+ type = token.type;
1280
+
1281
+ // All whitespace can be ignored
1282
+ if (type == 'whitespace') {
1283
+ continue;
1284
+ }
1285
+
1286
+ value = token.value;
1287
+
1288
+ if (value == '(') {
1289
+ createGroup();
1290
+ continue;
1291
+ }
1292
+
1293
+ if (value == ')') {
1294
+ closeGroup();
1295
+ continue;
1296
+ }
1297
+
1298
+ lower = value.toLowerCase();
1299
+
1300
+ if (current_state == START_STATE) {
1301
+ if (type != 'name') {
1302
+ reject('Expected name');
1303
+ }
1304
+
1305
+ if (lower == 'not' || lower == 'and' || lower == 'or') {
1306
+ expressions.push(lower);
1307
+ } else {
1308
+ current_name = value;
1309
+ pushState(NAME_STATE);
1310
+ }
1311
+ } else if (current_state == NAME_STATE) {
1312
+
1313
+ if (type == 'punct') {
1314
+ if (value == '.') {
1315
+ current_name += '.';
1316
+ } else {
1317
+ current_operator = value;
1318
+ replaceState(OPERATOR_STATE);
1319
+ }
1320
+ } else if (type == 'name') {
1321
+ if (lower == 'not') {
1322
+ current_not = !current_not;
1323
+ console.log('NOT! Current operator is', current_operator);
1324
+ replaceState(OPERATOR_STATE);
1325
+ } else if (lower == 'is') {
1326
+ current_operator = '==';
1327
+ replaceState(OPERATOR_STATE);
1328
+ } else {
1329
+ current_name += value;
1330
+ }
1331
+ }
1332
+ } else if (current_state == OPERATOR_STATE) {
1333
+
1334
+ // Is there already an operator?
1335
+ if (current_operator) {
1336
+ if (lower == 'not') {
1337
+ current_not = !current_not;
1338
+ } else if (type == 'name' && lower == 'empty') {
1339
+ current_operator = 'empty';
1340
+ setValue(true);
1341
+ replaceState(VALUE_STATE);
1342
+ } else {
1343
+ setValue(token);
1344
+ replaceState(VALUE_STATE);
1345
+ }
1346
+ } else if (type == 'name') {
1347
+ current_operator = lower;
1348
+ } else {
1349
+ reject('Expected operator');
1350
+ }
1351
+ } else if (current_state == VALUE_STATE) {
1352
+
1353
+ if (type == 'name') {
1354
+ if (lower == 'not' && !current_value) {
1355
+ current_not = !current_not;
1356
+ } else if (lower == 'or' || lower == 'and') {
1357
+ if (!current_value) {
1358
+ reject('Expected a value');
1359
+ }
1360
+
1361
+ closeValue(lower);
1362
+ continue;
1363
+ }
1364
+ }
1365
+
1366
+ if (type == 'punct') {
1367
+ if (value == '.') {
1368
+ setValue(token);
1369
+ } else {
1370
+ reject('Unexpected punctuation');
1371
+ }
1372
+ } else if (type == 'name') {
1373
+ setValue(token);
1374
+ } else if (current_value) {
1375
+ reject('Unexpected string');
1376
+ } else {
1377
+ setValue(token);
1378
+ }
1379
+ }
1380
+ }
1381
+
1382
+ if (hasCurrent()) {
1383
+ closeValue();
1384
+ }
1385
+
1386
+ return expressions;
1387
+ }
1388
+
1389
+ /**
1390
+ * Convert AQL tokens into a conditions object
1391
+ *
1392
+ * @author Jelle De Loecker <jelle@elevenways.be>
1393
+ * @since 1.4.0
1394
+ * @version 1.4.0
1395
+ *
1396
+ * @param {string} query
1397
+ *
1398
+ * @return {Object}
1399
+ */
1400
+ function convertAQLTokens(expressions, expression_converter) {
1401
+
1402
+ let current_group,
1403
+ current_logic,
1404
+ next_is_not,
1405
+ expression,
1406
+ i;
1407
+
1408
+ // Make sure there currently exists a group with the given logic type
1409
+ const ensureGroup = (logic) => {
1410
+
1411
+ if (!current_group) {
1412
+ current_group = {
1413
+ logic,
1414
+ expressions: [],
1415
+ };
1416
+ } else {
1417
+
1418
+ if (!current_group.logic) {
1419
+ current_group.logic = logic;
1420
+ } else if (current_group.logic != logic) {
1421
+ pushGroup(logic);
1422
+ }
1423
+ }
1424
+
1425
+ current_logic = current_group.logic;
1426
+
1427
+ return current_group;
1428
+ };
1429
+
1430
+ // Create the root group
1431
+ let root = ensureGroup();
1432
+
1433
+ // Start a new group of the given logic type.
1434
+ const pushGroup = (logic) => {
1435
+
1436
+ let group = {
1437
+ logic,
1438
+ expressions: [],
1439
+ not: next_is_not
1440
+ };
1441
+
1442
+ if (logic == 'or') {
1443
+ group.expressions.push(simplifyGroups(current_group));
1444
+ current_group = current_group.parent;
1445
+ }
1446
+
1447
+ group.parent = current_group;
1448
+
1449
+ if (!current_group) {
1450
+ root = group;
1451
+ } else {
1452
+ current_group.child = group;
1453
+ }
1454
+
1455
+ current_group = group;
1456
+ };
1457
+
1458
+ // Close the current group (& restore the parent group)
1459
+ const closeGroup = () => {
1460
+ let group = current_group;
1461
+ current_group = group.parent;
1462
+ }
1463
+
1464
+ const simplifyGroups = (group) => {
1465
+
1466
+ let result = {};
1467
+ let logic;
1468
+
1469
+ if (group.logic == 'and' || !group.logic) {
1470
+ logic = '$and';
1471
+ } else if (group.logic == 'or') {
1472
+ logic = '$or';
1473
+ } else {
1474
+ throw new Error('Unknown logic type');
1475
+ }
1476
+
1477
+ result[logic] = group.expressions;
1478
+
1479
+ if (group.child) {
1480
+ result[logic].push(simplifyGroups(group.child));
1481
+ }
1482
+
1483
+ if (group.not) {
1484
+ result = {
1485
+ $nor: result
1486
+ };
1487
+ }
1488
+
1489
+ return result;
1490
+ };
1491
+
1492
+ for (i = 0; i < expressions.length; i++) {
1493
+ expression = expressions[i];
1494
+
1495
+ if (expression === true) {
1496
+ pushGroup();
1497
+ } else if (expression === false) {
1498
+ closeGroup();
1499
+ } else if (expression === 'and' || expression === 'or') {
1500
+ ensureGroup(expression);
1501
+ } else if (expression === 'not') {
1502
+ next_is_not = true;
1503
+ continue;
1504
+ } else {
1505
+
1506
+ current_logic = expression[4];
1507
+
1508
+ // And gets precedence
1509
+ if (current_logic == 'and') {
1510
+ ensureGroup(current_logic);
1511
+ }
1512
+
1513
+ expression = expression_converter(...expression);
1514
+
1515
+ current_group.expressions.push(expression);
1516
+
1517
+ if (current_logic == 'or') {
1518
+ ensureGroup(current_logic);
1519
+ }
1520
+ }
1521
+
1522
+ next_is_not = false;
1523
+ }
1524
+
1525
+ return simplifyGroups(root);
1526
+ }
1527
+
974
1528
  /**
975
1529
  * Get the MongoDB options from this criteria
976
1530
  *
977
- * @author Jelle De Loecker <jelle@develry.be>
1531
+ * @author Jelle De Loecker <jelle@elevenways.be>
978
1532
  * @since 1.1.0
979
1533
  * @version 1.1.0
980
1534
  *
@@ -1011,7 +1565,7 @@ NoSQL.setMethod(function compileCriteriaOptions(criteria) {
1011
1565
  /**
1012
1566
  * Handle items from the datasource
1013
1567
  *
1014
- * @author Jelle De Loecker <jelle@develry.be>
1568
+ * @author Jelle De Loecker <jelle@elevenways.be>
1015
1569
  * @since 1.1.0
1016
1570
  * @version 1.1.0
1017
1571
  *