@storecraft/database-mongodb 1.0.20 → 1.2.5

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.
@@ -11,7 +11,6 @@ export const isDef = v => v!==undefined && v!==null;
11
11
  export const isUndef = v => !isDef(v);
12
12
 
13
13
  /**
14
- *
15
14
  * @param {...any} keys
16
15
  */
17
16
  export const delete_keys = (...keys) => {
@@ -29,7 +28,7 @@ export const delete_keys = (...keys) => {
29
28
  }
30
29
 
31
30
  /**
32
- * Sanitize hidden properties in-place
31
+ * @description Sanitize hidden properties in-place
33
32
  * @template {object} T
34
33
  * @param {T} o
35
34
  * @return {Omit<T, '_id' | '_relations'>}
@@ -46,7 +45,7 @@ export const sanitize_hidden = o => {
46
45
  }
47
46
 
48
47
  /**
49
- * Sanitize hidden properties in-place recursively
48
+ * @description Sanitize hidden properties in-place recursively
50
49
  * @template {object} T
51
50
  * @param {T} o
52
51
  * @return {Omit<T, '_id' | '_relations'>}
@@ -63,7 +62,7 @@ export const sanitize_recursively = o => {
63
62
  }
64
63
 
65
64
  /**
66
- * Sanitize the mongo document before sending to client
65
+ * @description Sanitize the mongo document before sending to client
67
66
  * @template T
68
67
  * @param {WithRelations<T>} o
69
68
  */
@@ -72,7 +71,7 @@ export const sanitize_one = o => {
72
71
  }
73
72
 
74
73
  /**
75
- * Sanitize the mongo document before sending to client
74
+ * @description Sanitize the mongo document before sending to client
76
75
  * @template T
77
76
  * @param {WithRelations<T>[]} o
78
77
  */
@@ -109,7 +108,7 @@ export const to_objid_safe = id => {
109
108
  }
110
109
 
111
110
  /**
112
- * Create a `filter` for `object-id` or `handle`
111
+ * @description Create a `filter` for `object-id` or `handle`
113
112
  * @template {{handle?: string}} G
114
113
  * @param {string} handle_or_id
115
114
  * @returns {Filter<G>}
@@ -1,127 +1,152 @@
1
1
  /**
2
- * @import { ApiQuery, Cursor, Tuple } from '@storecraft/core/api'
3
- * @import { VQL } from '@storecraft/core/vql'
2
+ * @import { ApiQuery, Tuple } from '@storecraft/core/api'
3
+ * @import { VQL, VQL_OPS, legal_value_types } from '@storecraft/core/vql'
4
+ * @import { Filter, FilterOperators, FindOptions } from 'mongodb'
4
5
  */
5
-
6
+ import {
7
+ legacy_query_with_cursors_to_vql_string
8
+ } from "@storecraft/core/api/query.legacy.js";
6
9
  import { to_objid } from "./utils.funcs.js";
7
- import { parse } from "@storecraft/core/vql";
8
-
9
- let a = {
10
- $or: [
11
- { updated_at: { $gt: '2024-01-24T20:28:24.126Z'} },
12
- { $and : [ { updated_at: '2024-01-24T20:28:24.126Z' }, { id: { $gte : 'tag_65b172ebc4c9552fd46c1027'}}]}
13
- ],
14
- }
10
+ import { parse, utils } from "@storecraft/core/vql";
15
11
 
16
12
  /**
17
- * Convert an API Query cursor into mongo dialect, also sanitize.
18
- *
19
- * 1. (a1, a2) > (b1, b2) ==> (a1 > b1) || (a1=b1 & a2>b2)
20
- * 2. (a1, a2) >= (b1, b2) ==> (a1 > b1) || (a1=b1 & a2>=b2)
21
- * 3. (a1, a2, a3) > (b1, b2, b3) ==> (a1 > b1) || (a1=b1 & a2>b2) || (a1=b1 & a2=b2 & a3>b3)
22
- * 4. (a1, a2, a3) >= (b1, b2, b3) ==> (a1 > b1) || (a1=b1 & a2>b2) || (a1=b1 & a2=b2 & a3>=b3)
23
- *
24
- *
25
- * @param {Cursor} c
26
- * @param {'>' | '>=' | '<' | '<='} relation
27
- * @param {(x: [k: string, v: any]) => [k: string, v: any]} transformer
28
- * Your chance to change key and value
29
- *
13
+ * @description Convert a **VQL** to a mongo filter
14
+ * @param {VQL} vql
30
15
  */
31
- export const query_cursor_to_mongo = (c, relation, transformer=(x)=>x) => {
16
+ export const query_vql_to_mongo_filter = (vql) => {
32
17
 
33
- let rel_key_1; // relation in last conjunction term in [0, n-1] disjunctions
34
- let rel_key_2; // relation in last conjunction term in last disjunction
18
+ if(!vql)
19
+ return undefined;
35
20
 
36
- if (relation==='>' || relation==='>=') {
37
- rel_key_1 = rel_key_2 = '$gt';
38
- if(relation==='>=')
39
- rel_key_2='$gte';
21
+ /** @template T @param {T} fn @returns {T} */
22
+ const identity = (fn) => {
23
+ return fn;
40
24
  }
41
- else if (relation==='<' || relation==='<=') {
42
- rel_key_1 = rel_key_2 = '$lt';
43
- if(relation==='<=')
44
- rel_key_2='$lte';
45
- } else return undefined;
46
-
47
- const disjunctions = [];
48
- // each disjunction clause
49
- for (let ix = 0; ix < c.length; ix++) {
50
- const is_last_disjunction = ix==c.length-1;
51
- const conjunctions = [];
52
- // each conjunction clause up until the last term (not inclusive)
53
- for (let jx = 0; jx < ix; jx++) {
54
- // the a_n=b_n
55
- const r = transformer(c[jx]);
56
- conjunctions.push({ [r[0]] : r[1] });
57
- }
58
25
 
59
- // Last conjunction term
60
- const relation_key = is_last_disjunction ? rel_key_2 : rel_key_1;
61
- const r = transformer(c[ix]);
62
- conjunctions.push({ [r[0]] : { [relation_key]: r[1] } });
63
- // Add to disjunctions list
64
- disjunctions.push({ $and: conjunctions });
26
+ /**
27
+ * @param {VQL_OPS<legal_value_types>} ops
28
+ * `ops` object about this property
29
+ * @param {string} name
30
+ * property name in the table
31
+ */
32
+ const leaf_ops = (ops, name) => {
33
+
34
+ const ops_keys = /** @type {(keyof VQL_OPS)[]} */(
35
+ Object.keys(ops)
36
+ );
37
+
38
+ const values = ops_keys.map(
39
+ (k) => {
40
+ /** `arg` is basically the value of `op.$eq` `op.$gte` etc.. */
41
+ const arg = (ops[k]);
42
+
43
+ switch (k) {
44
+ case '$eq':
45
+ return /** @type {FilterOperators<legal_value_types>} */({
46
+ $eq: arg
47
+ });
48
+ case '$ne':
49
+ return /** @type {FilterOperators<legal_value_types>} */({
50
+ $ne: arg
51
+ });
52
+ case '$gt':
53
+ return /** @type {FilterOperators<legal_value_types>} */({
54
+ $gt: arg
55
+ });
56
+ case '$gte':
57
+ return /** @type {FilterOperators<legal_value_types>} */({
58
+ $gte: arg
59
+ });
60
+ case '$lt':
61
+ return /** @type {FilterOperators<legal_value_types>} */({
62
+ $lt: arg
63
+ });
64
+ case '$lte':
65
+ return /** @type {FilterOperators<legal_value_types>} */({
66
+ $lte: arg
67
+ });
68
+ case '$like':
69
+ return /** @type {FilterOperators<legal_value_types>} */({
70
+ $regex: String(arg)
71
+ });
72
+ case '$in': {
73
+ return /** @type {FilterOperators<legal_value_types>} */({
74
+ $in: arg
75
+ });
76
+ }
77
+ case '$nin': {
78
+ return /** @type {FilterOperators<legal_value_types>} */({
79
+ $nin: arg
80
+ });
81
+ }
82
+ default:
83
+ throw new Error(
84
+ `VQL-ops-failed: Unrecognized operator ${k}`
85
+ );
86
+ }
87
+ }
88
+ );
89
+
90
+ if(values.length===0)
91
+ return undefined;
92
+
93
+ return /** @type {Filter<any>} */ ({
94
+ [name]: values.reduce(
95
+ (p, c) => {
96
+ return { ...p, ...c }
97
+ },
98
+ {}
99
+ )
100
+ })
65
101
  }
66
102
 
67
- if(disjunctions.length==0)
68
- return undefined;
69
-
70
- const result = {
71
- $or: disjunctions
72
- };
73
-
74
- return result;
75
- }
103
+ const reduced = utils.reduce_vql(
104
+ {
105
+ vql,
106
+
107
+ map_leaf: (node) => {
108
+ return leaf_ops(
109
+ node.op,
110
+ node.name
111
+ );
112
+ },
113
+
114
+ reduce_AND: identity(
115
+ (nodes) => {
116
+ return /** @type {Filter<any>} */({
117
+ $and: nodes
118
+ });
119
+ }
120
+ ),
121
+
122
+ reduce_OR: (nodes) => {
123
+ return /** @type {Filter<any>} */({
124
+ $or: nodes
125
+ });
126
+ },
127
+
128
+ reduce_NOT: (node) => {
129
+ return /** @type {Filter<any>} */({
130
+ $nor: [node]
131
+ });
132
+ },
133
+
134
+ reduce_SEARCH: (value) => {
135
+ return /** @type {Filter<{_relations : { search: string[]}}>} */({
136
+ '_relations.search': { $regex: value }
137
+ });
138
+ },
76
139
 
77
- /**
78
- * @param {VQL.Node} node
79
- */
80
- export const query_vql_node_to_mongo = node => {
81
- if(node.op==='LEAF') {
82
- return {
83
- '_relations.search': { $regex: `${node.value}` }
84
140
  }
85
- }
86
-
87
- let conjunctions = [];
88
- for(let arg of node?.args) {
89
- conjunctions.push(query_vql_node_to_mongo(arg));
90
- }
91
141
 
92
- switch (node.op) {
93
- case '&':
94
- return {
95
- $and: conjunctions
96
- }
97
- case '|':
98
- return {
99
- $or: conjunctions
100
- }
101
- case '!':
102
- return {
103
- $nor: [ conjunctions[0] ]
104
- }
105
-
106
- default:
107
- throw new Error('VQL-to-mongo-failed')
108
- }
142
+ );
109
143
 
144
+ return reduced;
110
145
  }
111
146
 
112
147
  /**
113
- *
114
- * @param {VQL.Node} root
115
- */
116
- export const query_vql_to_mongo = root => {
117
- return root ? query_vql_node_to_mongo(root) : undefined;
118
- }
119
-
120
- /**
121
- * Let's transform ids into mongo ids
122
- *
148
+ * @description Let's transform ids into mongo ids
123
149
  * @param {Tuple} c a cursor record
124
- *
125
150
  * @returns {[k: string, v: any]}
126
151
  */
127
152
  const transform = c => {
@@ -131,55 +156,55 @@ const transform = c => {
131
156
  }
132
157
 
133
158
  /**
134
- * Convert an API Query into mongo dialect, also sanitize.
135
- *
136
- *
159
+ * @description Convert an API Query into mongo dialect,
160
+ * also sanitize.
137
161
  * @param {ApiQuery<any>} q
138
162
  */
139
163
  export const query_to_mongo = (q) => {
140
- try {
141
- if(q.vql && !q.vqlParsed) {
142
- q.vqlParsed = parse(q.vql)
164
+
165
+ try { // compute VQL clauses
166
+ const parts = [
167
+ parse(q.vql),
168
+ // supports legacy queries with cursors, will be deprecated
169
+ // in future versions.
170
+ parse(
171
+ legacy_query_with_cursors_to_vql_string(q)
172
+ )
173
+ ].filter(Boolean);
174
+
175
+ if(parts.length>0) {
176
+ q.vql = /** @type {VQL} */({
177
+ $and: parts
178
+ });
143
179
  }
144
- } catch(e) {}
180
+ } catch(e) {
181
+ console.error('VQL parse error:\n', e, '\nfor query:\n', q);
182
+ }
183
+
184
+ /** @type {Filter<any>} */
185
+ const filter = query_vql_to_mongo_filter(
186
+ /** @type {VQL} */(q.vql)
187
+ );
145
188
 
146
- const filter = {};
147
- const clauses = [];
148
189
  // `reverse_sign=-1` means we need to reverse because of `limitToLast`
149
- const reverse_sign = (q.limitToLast && !q.limit) ? -1 : 1;
190
+ const reverse_sign = q.limitToLast ? -1 : 1;
150
191
  const asc = q.order === 'asc';
151
192
  const sort_sign = (asc ? 1 : -1) * reverse_sign;
152
193
 
153
- // const sort_sign = (q.order === 'asc' ? 1 : -1) * reverse_sign;
154
- // const asc = (sort_sign * reverse_sign)==1;
155
-
156
- // compute index clauses
157
- if(q.startAt) {
158
- clauses.push(query_cursor_to_mongo(q.startAt, asc ? '>=' : '<=', transform));
159
- } else if(q.startAfter) {
160
- clauses.push(query_cursor_to_mongo(q.startAfter, asc ? '>' : '<', transform));
161
- }
162
-
163
- if(q.endAt) {
164
- clauses.push(query_cursor_to_mongo(q.endAt, asc ? '<=' : '>=', transform));
165
- } else if(q.endBefore) {
166
- clauses.push(query_cursor_to_mongo(q.endBefore, asc ? '<' : '>', transform));
167
- }
168
-
169
- // compute VQL clauses
170
- const vql_clause = query_vql_to_mongo(q.vqlParsed)
171
- vql_clause && clauses.push(vql_clause);
172
-
173
194
  // compute sort fields and order
174
- const sort = (q.sortBy?.length ? q.sortBy : ['updated_at', 'id']).reduce(
175
- (p, c) => (p[c==='id' ? '_id' : c]=sort_sign) && p,
195
+ /** @type {FindOptions["sort"]} */
196
+ const sort = (
197
+ q.sortBy?.length ?
198
+ q.sortBy :
199
+ ['updated_at', 'id']
200
+ )
201
+ .reduce(
202
+ (p, c) => (
203
+ p[c]=sort_sign
204
+ ) && p,
176
205
  {}
177
206
  );
178
207
 
179
- if(clauses?.length) {
180
- filter['$and'] = clauses;
181
- }
182
-
183
208
  return {
184
209
  filter,
185
210
  sort,
@@ -0,0 +1,189 @@
1
+ // /**
2
+ // * @import { ApiQuery, Cursor, Tuple } from '@storecraft/core/api'
3
+ // * @import { BOOLQL } from '@storecraft/core/vql'
4
+ // */
5
+
6
+ // import { to_objid } from "./utils.funcs.js";
7
+ // import { parse } from "@storecraft/core/vql";
8
+
9
+ // let a = {
10
+ // $or: [
11
+ // { updated_at: { $gt: '2024-01-24T20:28:24.126Z'} },
12
+ // { $and : [ { updated_at: '2024-01-24T20:28:24.126Z' }, { id: { $gte : 'tag_65b172ebc4c9552fd46c1027'}}]}
13
+ // ],
14
+ // }
15
+
16
+ // /**
17
+ // * Convert an API Query cursor into mongo dialect, also sanitize.
18
+ // *
19
+ // * 1. (a1, a2) > (b1, b2) ==> (a1 > b1) || (a1=b1 & a2>b2)
20
+ // * 2. (a1, a2) >= (b1, b2) ==> (a1 > b1) || (a1=b1 & a2>=b2)
21
+ // * 3. (a1, a2, a3) > (b1, b2, b3) ==> (a1 > b1) || (a1=b1 & a2>b2) || (a1=b1 & a2=b2 & a3>b3)
22
+ // * 4. (a1, a2, a3) >= (b1, b2, b3) ==> (a1 > b1) || (a1=b1 & a2>b2) || (a1=b1 & a2=b2 & a3>=b3)
23
+ // *
24
+ // *
25
+ // * @param {Cursor} c
26
+ // * @param {'>' | '>=' | '<' | '<='} relation
27
+ // * @param {(x: [k: string, v: any]) => [k: string, v: any]} transformer
28
+ // * Your chance to change key and value
29
+ // *
30
+ // */
31
+ // export const query_cursor_to_mongo = (c, relation, transformer=(x)=>x) => {
32
+
33
+ // let rel_key_1; // relation in last conjunction term in [0, n-1] disjunctions
34
+ // let rel_key_2; // relation in last conjunction term in last disjunction
35
+
36
+ // if (relation==='>' || relation==='>=') {
37
+ // rel_key_1 = rel_key_2 = '$gt';
38
+ // if(relation==='>=')
39
+ // rel_key_2='$gte';
40
+ // }
41
+ // else if (relation==='<' || relation==='<=') {
42
+ // rel_key_1 = rel_key_2 = '$lt';
43
+ // if(relation==='<=')
44
+ // rel_key_2='$lte';
45
+ // } else return undefined;
46
+
47
+ // const disjunctions = [];
48
+ // // each disjunction clause
49
+ // for (let ix = 0; ix < c.length; ix++) {
50
+ // const is_last_disjunction = ix==c.length-1;
51
+ // const conjunctions = [];
52
+ // // each conjunction clause up until the last term (not inclusive)
53
+ // for (let jx = 0; jx < ix; jx++) {
54
+ // // the a_n=b_n
55
+ // const r = transformer(c[jx]);
56
+ // conjunctions.push({ [r[0]] : r[1] });
57
+ // }
58
+
59
+ // // Last conjunction term
60
+ // const relation_key = is_last_disjunction ? rel_key_2 : rel_key_1;
61
+ // const r = transformer(c[ix]);
62
+ // conjunctions.push({ [r[0]] : { [relation_key]: r[1] } });
63
+ // // Add to disjunctions list
64
+ // disjunctions.push({ $and: conjunctions });
65
+ // }
66
+
67
+ // if(disjunctions.length==0)
68
+ // return undefined;
69
+
70
+ // const result = {
71
+ // $or: disjunctions
72
+ // };
73
+
74
+ // return result;
75
+ // }
76
+
77
+ // /**
78
+ // * @param {VQL.Node} node
79
+ // */
80
+ // export const query_vql_node_to_mongo = node => {
81
+ // if(node.op==='LEAF') {
82
+ // return {
83
+ // '_relations.search': { $regex: `${node.value}` }
84
+ // }
85
+ // }
86
+
87
+ // let conjunctions = [];
88
+ // for(let arg of node?.args) {
89
+ // conjunctions.push(query_vql_node_to_mongo(arg));
90
+ // }
91
+
92
+ // switch (node.op) {
93
+ // case '&':
94
+ // return {
95
+ // $and: conjunctions
96
+ // }
97
+ // case '|':
98
+ // return {
99
+ // $or: conjunctions
100
+ // }
101
+ // case '!':
102
+ // return {
103
+ // $nor: [ conjunctions[0] ]
104
+ // }
105
+
106
+ // default:
107
+ // throw new Error('VQL-to-mongo-failed')
108
+ // }
109
+
110
+ // }
111
+
112
+ // /**
113
+ // *
114
+ // * @param {VQL.Node} root
115
+ // */
116
+ // export const query_vql_to_mongo = root => {
117
+ // return root ? query_vql_node_to_mongo(root) : undefined;
118
+ // }
119
+
120
+ // /**
121
+ // * Let's transform ids into mongo ids
122
+ // *
123
+ // * @param {Tuple} c a cursor record
124
+ // *
125
+ // * @returns {[k: string, v: any]}
126
+ // */
127
+ // const transform = c => {
128
+ // if(c[0]!=='id')
129
+ // return c;
130
+ // return [ '_id', to_objid(String(c[1])) ];
131
+ // }
132
+
133
+ // /**
134
+ // * Convert an API Query into mongo dialect, also sanitize.
135
+ // *
136
+ // *
137
+ // * @param {ApiQuery<any>} q
138
+ // */
139
+ // export const query_to_mongo = (q) => {
140
+ // try {
141
+ // if(q.vql && !q.vqlParsed) {
142
+ // q.vqlParsed = parse(q.vql)
143
+ // }
144
+ // } catch(e) {}
145
+
146
+ // const filter = {};
147
+ // const clauses = [];
148
+ // // `reverse_sign=-1` means we need to reverse because of `limitToLast`
149
+ // const reverse_sign = (q.limitToLast && !q.limit) ? -1 : 1;
150
+ // const asc = q.order === 'asc';
151
+ // const sort_sign = (asc ? 1 : -1) * reverse_sign;
152
+
153
+ // // const sort_sign = (q.order === 'asc' ? 1 : -1) * reverse_sign;
154
+ // // const asc = (sort_sign * reverse_sign)==1;
155
+
156
+ // // compute index clauses
157
+ // if(q.startAt) {
158
+ // clauses.push(query_cursor_to_mongo(q.startAt, asc ? '>=' : '<=', transform));
159
+ // } else if(q.startAfter) {
160
+ // clauses.push(query_cursor_to_mongo(q.startAfter, asc ? '>' : '<', transform));
161
+ // }
162
+
163
+ // if(q.endAt) {
164
+ // clauses.push(query_cursor_to_mongo(q.endAt, asc ? '<=' : '>=', transform));
165
+ // } else if(q.endBefore) {
166
+ // clauses.push(query_cursor_to_mongo(q.endBefore, asc ? '<' : '>', transform));
167
+ // }
168
+
169
+ // // compute VQL clauses
170
+ // const vql_clause = query_vql_to_mongo(q.vqlParsed)
171
+ // vql_clause && clauses.push(vql_clause);
172
+
173
+ // // compute sort fields and order
174
+ // const sort = (q.sortBy?.length ? q.sortBy : ['updated_at', 'id']).reduce(
175
+ // (p, c) => (p[c==='id' ? '_id' : c]=sort_sign) && p,
176
+ // {}
177
+ // );
178
+
179
+ // if(clauses?.length) {
180
+ // filter['$and'] = clauses;
181
+ // }
182
+
183
+ // return {
184
+ // filter,
185
+ // sort,
186
+ // reverse_sign
187
+ // }
188
+
189
+ // }