@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.
@@ -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 { query_to_eb, query_to_sort } from './utils.query.js'
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 driver.client
114
+ const items = await withQuery(
115
+ driver.client
115
116
  .selectFrom(table_name)
116
117
  .selectAll()
117
- .select(eb => [
118
- with_media(eb, eb.ref('shipping_methods.id'), driver.dialectType),
119
- with_tags(eb, eb.ref('shipping_methods.id'), driver.dialectType),
120
- with_search(eb, eb.ref('shipping_methods.id'), driver.dialectType),
121
- ].filter(Boolean))
122
- .where(
123
- (eb) => {
124
- return query_to_eb(eb, query, table_name);
125
- }
126
- )
127
- .orderBy(query_to_sort(query, 'shipping_methods'))
128
- .limit(query.limitToLast ?? query.limit ?? 10)
129
- .execute();
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) items.reverse();
137
+ if(query.limitToLast)
138
+ items.reverse();
132
139
 
133
140
  return sanitize_array(items);
134
141
  }
@@ -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 { query_to_eb, query_to_sort } from './utils.query.js'
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 driver.client
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
- .where(
221
- (eb) => {
222
- return query_to_eb(eb, query, table_name);
223
- }
224
- )
225
- .orderBy(query_to_sort(query, table_name))
226
- .limit(query.limitToLast ?? query.limit ?? 10)
227
- .execute();
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) items.reverse();
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(['updated_at desc']),
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(['updated_at desc'])
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(['updated_at desc']),
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(['updated_at desc']),
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(['updated_at desc'])
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 { query_to_eb, query_to_sort } from './utils.query.js'
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 driver.client
100
+ const items = await withQuery(
101
+ driver.client
101
102
  .selectFrom(table_name)
102
103
  .selectAll()
103
- .select(eb => [
104
- with_search(eb, eb.ref('tags.id'), driver.dialectType),
105
- ].filter(Boolean))
106
- .where(
107
- (eb) => {
108
- return query_to_eb(eb, query, table_name);
109
- }
110
- )
111
- .orderBy(query_to_sort(query, table_name))
112
- .limit(query.limitToLast ?? query.limit ?? 10)
113
- .execute();
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
  }
@@ -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 { query_to_eb, query_to_sort } from './utils.query.js'
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 driver.client
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
- .where(
149
- (eb) => {
150
- return query_to_eb(eb, query, table_name);
151
- }
152
- )
153
- .orderBy(query_to_sort(query, table_name))
154
- .limit(query.limitToLast ?? query.limit ?? 10)
155
- .execute()
156
- .then(
157
- (items) => {
158
- return items.map(
159
- (item) => {
160
- item.template_html = decode_base64_if_needed(item.template_html);
161
- item.template_text = decode_base64_if_needed(item.template_text);
162
- item.template_subject = decode_base64_if_needed(item.template_subject);
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) items.reverse();
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
+ }