@storecraft/database-sql-base 1.0.22 → 1.0.24
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.
- package/migrate.js +3 -0
- package/migrations.shared/00003_alter_auth_users.js +6 -0
- package/package.json +2 -2
- package/src/con.auth_users.js +17 -13
- package/src/con.collections.js +24 -16
- package/src/con.customers.js +26 -19
- package/src/con.discounts.js +31 -20
- package/src/con.discounts.utils.js +52 -41
- package/src/con.images.js +18 -13
- package/src/con.notifications.js +26 -17
- package/src/con.orders.js +25 -18
- package/src/con.posts.js +28 -28
- package/src/con.products.js +80 -128
- package/src/con.search.js +12 -12
- package/src/con.shared.js +17 -35
- package/src/con.shipping.js +23 -16
- package/src/con.storefronts.js +23 -17
- package/src/con.tags.js +22 -14
- package/src/con.templates.js +21 -24
- package/src/utils.query.OLD.js +259 -0
- package/src/utils.query.js +217 -179
- package/tests/sandbox.js +5 -6
package/src/con.shipping.js
CHANGED
@@ -9,7 +9,7 @@ import { count_regular, delete_entity_values_by_value_or_reporter_and_context,
|
|
9
9
|
insert_tags_of, regular_upsert_me, where_id_or_handle_table,
|
10
10
|
with_media, with_search, with_tags } from './con.shared.js'
|
11
11
|
import { sanitize, sanitize_array } from './utils.funcs.js'
|
12
|
-
import {
|
12
|
+
import { withQuery } from './utils.query.js'
|
13
13
|
|
14
14
|
export const table_name = 'shipping_methods'
|
15
15
|
|
@@ -111,24 +111,31 @@ const remove = (driver) => {
|
|
111
111
|
const list = (driver) => {
|
112
112
|
return async (query) => {
|
113
113
|
|
114
|
-
const items = await
|
114
|
+
const items = await withQuery(
|
115
|
+
driver.client
|
115
116
|
.selectFrom(table_name)
|
116
117
|
.selectAll()
|
117
|
-
.select(
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
.
|
128
|
-
|
129
|
-
|
118
|
+
.select(
|
119
|
+
eb => [
|
120
|
+
with_media(eb, eb.ref('shipping_methods.id'), driver.dialectType),
|
121
|
+
with_tags(eb, eb.ref('shipping_methods.id'), driver.dialectType),
|
122
|
+
with_search(eb, eb.ref('shipping_methods.id'), driver.dialectType),
|
123
|
+
].filter(Boolean)
|
124
|
+
),
|
125
|
+
query, table_name
|
126
|
+
).execute();
|
127
|
+
|
128
|
+
// .where(
|
129
|
+
// (eb) => {
|
130
|
+
// return query_to_eb(eb, query, table_name);
|
131
|
+
// }
|
132
|
+
// )
|
133
|
+
// .orderBy(query_to_sort(query, 'shipping_methods'))
|
134
|
+
// .limit(query.limitToLast ?? query.limit ?? 10)
|
135
|
+
// .execute();
|
130
136
|
|
131
|
-
if(query.limitToLast)
|
137
|
+
if(query.limitToLast)
|
138
|
+
items.reverse();
|
132
139
|
|
133
140
|
return sanitize_array(items);
|
134
141
|
}
|
package/src/con.storefronts.js
CHANGED
@@ -20,7 +20,7 @@ import { delete_entity_values_of_by_entity_id_or_handle_and_context,
|
|
20
20
|
products_with_variants,
|
21
21
|
products_with_related_products} from './con.shared.js'
|
22
22
|
import { sanitize, sanitize_array } from './utils.funcs.js'
|
23
|
-
import {
|
23
|
+
import { withQuery } from './utils.query.js'
|
24
24
|
|
25
25
|
export const table_name = 'storefronts'
|
26
26
|
|
@@ -193,7 +193,8 @@ const list = (driver) => {
|
|
193
193
|
const expand_shipping = expand_all || expand.includes('shipping_methods');
|
194
194
|
const expand_posts = expand_all || expand.includes('posts');
|
195
195
|
|
196
|
-
const items = await
|
196
|
+
const items = await withQuery(
|
197
|
+
driver.client
|
197
198
|
.selectFrom(table_name)
|
198
199
|
.selectAll()
|
199
200
|
.select(eb => [
|
@@ -216,17 +217,22 @@ const list = (driver) => {
|
|
216
217
|
eb, eb.ref('storefronts.id'), driver.dialectType
|
217
218
|
),
|
218
219
|
].filter(Boolean)
|
219
|
-
)
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
220
|
+
),
|
221
|
+
query, table_name
|
222
|
+
).execute();
|
223
|
+
|
224
|
+
|
225
|
+
// .where(
|
226
|
+
// (eb) => {
|
227
|
+
// return query_to_eb(eb, query, table_name);
|
228
|
+
// }
|
229
|
+
// )
|
230
|
+
// .orderBy(query_to_sort(query, table_name))
|
231
|
+
// .limit(query.limitToLast ?? query.limit ?? 10)
|
232
|
+
// .execute();
|
228
233
|
|
229
|
-
if(query.limitToLast)
|
234
|
+
if(query.limitToLast)
|
235
|
+
items.reverse();
|
230
236
|
|
231
237
|
return sanitize_array(items);
|
232
238
|
}
|
@@ -268,7 +274,7 @@ const get_default_auto_generated_storefront = (driver) => {
|
|
268
274
|
]
|
269
275
|
)
|
270
276
|
.where('active', '=', 1)
|
271
|
-
.orderBy(
|
277
|
+
.orderBy('updated_at', 'desc'),
|
272
278
|
driver.dialectType
|
273
279
|
).as('collections'),
|
274
280
|
|
@@ -287,7 +293,7 @@ const get_default_auto_generated_storefront = (driver) => {
|
|
287
293
|
]
|
288
294
|
)
|
289
295
|
.where('active', '=', 1)
|
290
|
-
.orderBy(
|
296
|
+
.orderBy('updated_at', 'desc')
|
291
297
|
.limit(10),
|
292
298
|
dialectType
|
293
299
|
).as('products'),
|
@@ -303,7 +309,7 @@ const get_default_auto_generated_storefront = (driver) => {
|
|
303
309
|
]
|
304
310
|
)
|
305
311
|
.where('active', '=', 1)
|
306
|
-
.orderBy(
|
312
|
+
.orderBy('updated_at', 'desc'),
|
307
313
|
dialectType
|
308
314
|
).as('discounts'),
|
309
315
|
|
@@ -318,7 +324,7 @@ const get_default_auto_generated_storefront = (driver) => {
|
|
318
324
|
]
|
319
325
|
)
|
320
326
|
.where('active', '=', 1)
|
321
|
-
.orderBy(
|
327
|
+
.orderBy('updated_at', 'desc'),
|
322
328
|
dialectType
|
323
329
|
).as('shipping_methods'),
|
324
330
|
|
@@ -333,7 +339,7 @@ const get_default_auto_generated_storefront = (driver) => {
|
|
333
339
|
]
|
334
340
|
)
|
335
341
|
.where('active', '=', 1)
|
336
|
-
.orderBy(
|
342
|
+
.orderBy('updated_at', 'desc')
|
337
343
|
.limit(3),
|
338
344
|
dialectType
|
339
345
|
).as('posts'),
|
package/src/con.tags.js
CHANGED
@@ -8,7 +8,7 @@ import {
|
|
8
8
|
with_media, with_search
|
9
9
|
} from './con.shared.js'
|
10
10
|
import { sanitize, sanitize_array } from './utils.funcs.js'
|
11
|
-
import {
|
11
|
+
import { withQuery } from './utils.query.js'
|
12
12
|
|
13
13
|
export const table_name = 'tags'
|
14
14
|
|
@@ -97,22 +97,30 @@ const remove = (driver) => {
|
|
97
97
|
const list = (driver) => {
|
98
98
|
return async (query) => {
|
99
99
|
|
100
|
-
const items = await
|
100
|
+
const items = await withQuery(
|
101
|
+
driver.client
|
101
102
|
.selectFrom(table_name)
|
102
103
|
.selectAll()
|
103
|
-
.select(
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
.
|
112
|
-
|
113
|
-
|
104
|
+
.select(
|
105
|
+
eb => [
|
106
|
+
with_search(eb, eb.ref('tags.id'), driver.dialectType),
|
107
|
+
].filter(Boolean)
|
108
|
+
),
|
109
|
+
query, table_name
|
110
|
+
).execute();
|
111
|
+
|
112
|
+
// .where(
|
113
|
+
// (eb) => {
|
114
|
+
// return query_to_eb(eb, query, table_name);
|
115
|
+
// }
|
116
|
+
// )
|
117
|
+
// .orderBy(query_to_sort(query, table_name))
|
118
|
+
// .limit(query.limitToLast ?? query.limit ?? 10)
|
119
|
+
// .execute();
|
120
|
+
|
121
|
+
if(query.limitToLast)
|
122
|
+
items.reverse();
|
114
123
|
|
115
|
-
if(query.limitToLast) items.reverse();
|
116
124
|
return sanitize_array(items);
|
117
125
|
}
|
118
126
|
}
|
package/src/con.templates.js
CHANGED
@@ -8,7 +8,7 @@ import { count_regular, delete_me, delete_search_of, insert_search_of,
|
|
8
8
|
regular_upsert_me, safe_trx, where_id_or_handle_table,
|
9
9
|
with_search} from './con.shared.js'
|
10
10
|
import { sanitize, sanitize_array } from './utils.funcs.js'
|
11
|
-
import {
|
11
|
+
import { withQuery } from './utils.query.js'
|
12
12
|
import { base64 } from '@storecraft/core/crypto';
|
13
13
|
|
14
14
|
export const table_name = 'templates'
|
@@ -137,36 +137,33 @@ const remove = (driver) => {
|
|
137
137
|
const list = (driver) => {
|
138
138
|
return async (query) => {
|
139
139
|
|
140
|
-
const items = await
|
140
|
+
const items = await withQuery(
|
141
|
+
driver.client
|
141
142
|
.selectFrom(table_name)
|
142
143
|
.selectAll()
|
143
144
|
.select(
|
144
145
|
eb => [
|
145
146
|
with_search(eb, eb.ref('templates.id'), driver.dialectType),
|
146
147
|
].filter(Boolean)
|
147
|
-
)
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
)
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
return item;
|
164
|
-
}
|
165
|
-
)
|
166
|
-
}
|
167
|
-
);
|
148
|
+
),
|
149
|
+
query, table_name
|
150
|
+
)
|
151
|
+
.execute()
|
152
|
+
.then(
|
153
|
+
(items) => {
|
154
|
+
return items.map(
|
155
|
+
(item) => {
|
156
|
+
item.template_html = decode_base64_if_needed(item.template_html);
|
157
|
+
item.template_text = decode_base64_if_needed(item.template_text);
|
158
|
+
item.template_subject = decode_base64_if_needed(item.template_subject);
|
159
|
+
return item;
|
160
|
+
}
|
161
|
+
)
|
162
|
+
}
|
163
|
+
);
|
168
164
|
|
169
|
-
if(query.limitToLast)
|
165
|
+
if(query.limitToLast)
|
166
|
+
items.reverse();
|
170
167
|
|
171
168
|
return sanitize_array(items);
|
172
169
|
}
|
@@ -0,0 +1,259 @@
|
|
1
|
+
/**
|
2
|
+
* @import { ApiQuery, Cursor } from '@storecraft/core/api'
|
3
|
+
* @import { BOOLQL } from '@storecraft/core/vql/bool-ql'
|
4
|
+
* @import { Database } from '../types.sql.tables.js'
|
5
|
+
* @import { BinaryOperator, ExpressionBuilder } from 'kysely'
|
6
|
+
* @import {
|
7
|
+
* DirectedOrderByStringReference, QueryableTables
|
8
|
+
* } from './utils.types.js'
|
9
|
+
*/
|
10
|
+
|
11
|
+
import { parse } from "@storecraft/core/vql/bool-ql";
|
12
|
+
|
13
|
+
/**
|
14
|
+
* Convert an API Query cursor into mongo dialect, also sanitize.
|
15
|
+
*
|
16
|
+
* 1. (a1, a2) > (b1, b2) ==> (a1 > b1) || (a1=b1 & a2>b2)
|
17
|
+
* 2. (a1, a2) >= (b1, b2) ==> (a1 > b1) || (a1=b1 & a2>=b2)
|
18
|
+
* 3. (a1, a2, a3) > (b1, b2, b3) ==> (a1 > b1) || (a1=b1 & a2>b2) || (a1=b1 & a2=b2 & a3>b3)
|
19
|
+
* 4. (a1, a2, a3) >= (b1, b2, b3) ==> (a1 > b1) || (a1=b1 & a2>b2) || (a1=b1 & a2=b2 & a3>=b3)
|
20
|
+
*
|
21
|
+
* @param {ExpressionBuilder<Database>} eb
|
22
|
+
* @param {Cursor} c
|
23
|
+
* @param {'>' | '>=' | '<' | '<='} relation
|
24
|
+
* @param {(x: [k: string, v: any]) => [k: string, v: any]} transformer
|
25
|
+
* Your chance to change key and value
|
26
|
+
*/
|
27
|
+
export const query_cursor_to_eb = (
|
28
|
+
eb, c, relation, transformer=(x)=>x
|
29
|
+
) => {
|
30
|
+
|
31
|
+
/** @type {BinaryOperator} */
|
32
|
+
let rel_key_1; // relation in last conjunction term in [0, n-1] disjunctions
|
33
|
+
/** @type {BinaryOperator} */
|
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 = '>';
|
38
|
+
if(relation==='>=')
|
39
|
+
rel_key_2='>=';
|
40
|
+
}
|
41
|
+
else if (relation==='<' || relation==='<=') {
|
42
|
+
rel_key_1 = rel_key_2 = '<';
|
43
|
+
if(relation==='<=')
|
44
|
+
rel_key_2='<=';
|
45
|
+
} else return undefined;
|
46
|
+
|
47
|
+
|
48
|
+
const disjunctions = [];
|
49
|
+
// each disjunction clause
|
50
|
+
for (let ix = 0; ix < c.length; ix++) {
|
51
|
+
const is_last_disjunction = ix==c.length-1;
|
52
|
+
const conjunctions = [];
|
53
|
+
// each conjunction clause up until the last term (not inclusive)
|
54
|
+
for (let jx = 0; jx < ix; jx++) {
|
55
|
+
// the a_n=b_n
|
56
|
+
const r = transformer(c[jx]);
|
57
|
+
|
58
|
+
// conjunctions.push({ [r[0]] : r[1] });
|
59
|
+
conjunctions.push(eb(r[0], '=', r[1]));
|
60
|
+
}
|
61
|
+
|
62
|
+
// Last conjunction term
|
63
|
+
const relation_key = is_last_disjunction ? rel_key_2 : rel_key_1;
|
64
|
+
const r = transformer(c[ix]);
|
65
|
+
// conjunctions.push({ [r[0]] : { [relation_key]: r[1] } });
|
66
|
+
conjunctions.push(eb(r[0], relation_key, r[1]));
|
67
|
+
// Add to disjunctions list
|
68
|
+
// disjunctions.push({ $and: conjunctions });
|
69
|
+
disjunctions.push(eb.and(conjunctions));
|
70
|
+
}
|
71
|
+
|
72
|
+
if(disjunctions.length==0)
|
73
|
+
return undefined;
|
74
|
+
|
75
|
+
// const result = {
|
76
|
+
// $or: disjunctions
|
77
|
+
// };
|
78
|
+
|
79
|
+
return eb.or(disjunctions);
|
80
|
+
|
81
|
+
// return result;
|
82
|
+
}
|
83
|
+
|
84
|
+
|
85
|
+
/**
|
86
|
+
* @template {QueryableTables} T
|
87
|
+
* @param {ExpressionBuilder<Database>} eb
|
88
|
+
* @param {BOOLQL.Node} node
|
89
|
+
* @param {T} table_name
|
90
|
+
*/
|
91
|
+
export const query_vql_node_to_eb = (eb, node, table_name) => {
|
92
|
+
if(node.op==='LEAF') {
|
93
|
+
// console.log('value', node.value)
|
94
|
+
return eb
|
95
|
+
.exists(
|
96
|
+
eb => eb
|
97
|
+
.selectFrom('entity_to_search_terms')
|
98
|
+
.select('id')
|
99
|
+
.where(
|
100
|
+
eb => eb.and(
|
101
|
+
[
|
102
|
+
eb.or(
|
103
|
+
[
|
104
|
+
eb(
|
105
|
+
'entity_to_search_terms.entity_id', '=',
|
106
|
+
eb.ref(`${table_name}.id`)
|
107
|
+
),
|
108
|
+
eb(
|
109
|
+
`entity_to_search_terms.entity_handle`, '=',
|
110
|
+
eb.ref(`${table_name}.handle`)
|
111
|
+
),
|
112
|
+
]
|
113
|
+
),
|
114
|
+
eb(
|
115
|
+
`entity_to_search_terms.value`, 'like',
|
116
|
+
node.value.toLowerCase()
|
117
|
+
)
|
118
|
+
]
|
119
|
+
)
|
120
|
+
)
|
121
|
+
)
|
122
|
+
}
|
123
|
+
|
124
|
+
let conjunctions = [];
|
125
|
+
for(let arg of node?.args) {
|
126
|
+
conjunctions.push(query_vql_node_to_eb(eb, arg, table_name));
|
127
|
+
}
|
128
|
+
|
129
|
+
switch (node.op) {
|
130
|
+
case '&':
|
131
|
+
return eb.and(conjunctions)
|
132
|
+
case '|':
|
133
|
+
return eb.or(conjunctions)
|
134
|
+
case '!':
|
135
|
+
return eb.not(conjunctions[0])
|
136
|
+
default:
|
137
|
+
throw new Error('VQL-failed')
|
138
|
+
}
|
139
|
+
|
140
|
+
}
|
141
|
+
|
142
|
+
|
143
|
+
/**
|
144
|
+
*
|
145
|
+
* @param {[k: string, v: any]} kv
|
146
|
+
* @param {keyof Database} table_name
|
147
|
+
* @returns {[k: string, v: any]}
|
148
|
+
*/
|
149
|
+
const transform_boolean_to_0_or_1 = (kv, table_name) => {
|
150
|
+
|
151
|
+
// console.log('transform_boolean_to_0_or_1', kv)
|
152
|
+
kv = [
|
153
|
+
table_name ? `${table_name}.${kv[0]}` : kv[0],
|
154
|
+
typeof kv[1] === 'boolean' ? (kv[1] ? 1 : 0) : kv[1]
|
155
|
+
];
|
156
|
+
|
157
|
+
return kv;
|
158
|
+
}
|
159
|
+
|
160
|
+
/**
|
161
|
+
* Convert an API Query into dialect, also sanitize.
|
162
|
+
*
|
163
|
+
* @template {any} [T=any]
|
164
|
+
*
|
165
|
+
* @param {ExpressionBuilder<Database>} eb
|
166
|
+
* @param {ApiQuery<T>} q
|
167
|
+
* @param {QueryableTables} table_name
|
168
|
+
*
|
169
|
+
*/
|
170
|
+
export const query_to_eb = (eb, q={}, table_name) => {
|
171
|
+
const clauses = [];
|
172
|
+
|
173
|
+
const sort_sign = q.order === 'asc' ? 1 : -1;
|
174
|
+
const asc = sort_sign==1;
|
175
|
+
const transformer = (x) => transform_boolean_to_0_or_1(x, table_name);
|
176
|
+
|
177
|
+
// compute index clauses
|
178
|
+
// if(q.startAt) {
|
179
|
+
// clauses.push(
|
180
|
+
// query_cursor_to_eb(
|
181
|
+
// eb, q.startAt, asc ? '>=' : '<=', transformer
|
182
|
+
// )
|
183
|
+
// );
|
184
|
+
// } else if(q.startAfter) {
|
185
|
+
// clauses.push(
|
186
|
+
// query_cursor_to_eb(
|
187
|
+
// eb, q.startAfter, asc ? '>' : '<', transformer
|
188
|
+
// )
|
189
|
+
// );
|
190
|
+
// }
|
191
|
+
|
192
|
+
// if(q.endAt) {
|
193
|
+
// clauses.push(
|
194
|
+
// query_cursor_to_eb(
|
195
|
+
// eb, q.endAt, asc ? '<=' : '>=', transformer
|
196
|
+
// )
|
197
|
+
// );
|
198
|
+
// } else if(q.endBefore) {
|
199
|
+
// clauses.push(
|
200
|
+
// query_cursor_to_eb(
|
201
|
+
// eb, q.endBefore, asc ? '<' : '>', transformer
|
202
|
+
// )
|
203
|
+
// );
|
204
|
+
// }
|
205
|
+
|
206
|
+
// compute VQL clauses
|
207
|
+
try {
|
208
|
+
if(q.vql && !q.vqlParsed) {
|
209
|
+
q.vqlParsed = parse(q.vql)
|
210
|
+
}
|
211
|
+
} catch(e) {
|
212
|
+
console.error('VQL parse error', e);
|
213
|
+
}
|
214
|
+
|
215
|
+
if(q.vqlParsed) {
|
216
|
+
const vql_clause = query_vql_node_to_eb(
|
217
|
+
eb, q.vqlParsed, table_name
|
218
|
+
);
|
219
|
+
vql_clause &&
|
220
|
+
clauses.push(vql_clause);
|
221
|
+
}
|
222
|
+
|
223
|
+
return eb.and(clauses);
|
224
|
+
}
|
225
|
+
|
226
|
+
const SIGN = {
|
227
|
+
'1': 'asc',
|
228
|
+
'-1': 'desc'
|
229
|
+
}
|
230
|
+
|
231
|
+
/**
|
232
|
+
* Convert an API Query into mongo dialect, also sanitize.
|
233
|
+
* @template {Record<string, any>} [Type=Record<string, any>]
|
234
|
+
* @template {keyof Database} [Table=keyof Database]
|
235
|
+
*
|
236
|
+
* @param {ApiQuery<Type>} q
|
237
|
+
* @param {Table} table
|
238
|
+
* @returns {DirectedOrderByStringReference<Database, Table, Database[Table]>[]}
|
239
|
+
*/
|
240
|
+
export const query_to_sort = (q={}, table) => {
|
241
|
+
// const sort_sign = q.order === 'asc' ? 'asc' : 'desc';
|
242
|
+
// `reverse_sign=-1` means we need to reverse because of `limitToLast`
|
243
|
+
const reverse_sign = (q.limitToLast && !q.limit) ? -1 : 1;
|
244
|
+
const asc = q.order === 'asc';
|
245
|
+
const sort_sign = (asc ? 1 : -1) * reverse_sign;
|
246
|
+
|
247
|
+
// compute sort fields and order
|
248
|
+
const keys = q.sortBy?.length ? q.sortBy : ['updated_at', 'id'];
|
249
|
+
const sort = keys.map(
|
250
|
+
s => table ? `${table}.${s} ${SIGN[sort_sign]}` : `${s} ${SIGN[sort_sign]}`
|
251
|
+
)
|
252
|
+
// it's too complicated to map each ket to table column.
|
253
|
+
// kysely was designed to do this in place
|
254
|
+
return (
|
255
|
+
/** @type {DirectedOrderByStringReference<Database, Table, Database[Table]>[]} */ (
|
256
|
+
sort
|
257
|
+
)
|
258
|
+
);
|
259
|
+
}
|