@storecraft/database-mongodb 1.0.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.
- package/README.md +50 -0
- package/db-strategy.md +284 -0
- package/index.js +193 -0
- package/jsconfig.json +14 -0
- package/migrate.js +39 -0
- package/migrations/00000_init_tables.js +60 -0
- package/migrations/00001_seed_email_templates.js +271 -0
- package/package.json +38 -0
- package/src/con.auth_users.js +147 -0
- package/src/con.collections.js +232 -0
- package/src/con.customers.js +172 -0
- package/src/con.discounts.js +261 -0
- package/src/con.discounts.utils.js +137 -0
- package/src/con.images.js +173 -0
- package/src/con.notifications.js +101 -0
- package/src/con.orders.js +61 -0
- package/src/con.posts.js +149 -0
- package/src/con.products.js +537 -0
- package/src/con.search.js +162 -0
- package/src/con.shared.js +333 -0
- package/src/con.shipping.js +153 -0
- package/src/con.storefronts.js +223 -0
- package/src/con.tags.js +62 -0
- package/src/con.templates.js +62 -0
- package/src/utils.funcs.js +152 -0
- package/src/utils.query.js +186 -0
- package/src/utils.relations.js +410 -0
- package/tests/mongo-ping.test.js +34 -0
- package/tests/query.cursor.test.js +389 -0
- package/tests/query.vql.test.js +71 -0
- package/tests/runner.test.js +35 -0
- package/tests/sandbox.test.js +56 -0
- package/types.public.d.ts +22 -0
package/src/con.tags.js
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
import { Collection } from 'mongodb'
|
2
|
+
import { MongoDB } from '../index.js'
|
3
|
+
import { count_regular, get_regular, list_regular,
|
4
|
+
remove_regular, upsert_regular } from './con.shared.js'
|
5
|
+
|
6
|
+
/**
|
7
|
+
* @typedef {import('@storecraft/core/v-database').db_tags} db_col
|
8
|
+
*/
|
9
|
+
|
10
|
+
|
11
|
+
/**
|
12
|
+
* @param {MongoDB} d
|
13
|
+
*
|
14
|
+
*
|
15
|
+
* @returns {Collection<db_col["$type_get"]>}
|
16
|
+
*/
|
17
|
+
const col = (d) => d.collection('tags');
|
18
|
+
|
19
|
+
/**
|
20
|
+
* @param {MongoDB} driver
|
21
|
+
*
|
22
|
+
* @returns {db_col["upsert"]}
|
23
|
+
*/
|
24
|
+
const upsert = (driver) => upsert_regular(driver, col(driver));
|
25
|
+
|
26
|
+
/**
|
27
|
+
* @param {MongoDB} driver
|
28
|
+
*/
|
29
|
+
const get = (driver) => get_regular(driver, col(driver));
|
30
|
+
|
31
|
+
/**
|
32
|
+
* @param {MongoDB} driver
|
33
|
+
*/
|
34
|
+
const remove = (driver) => remove_regular(driver, col(driver));
|
35
|
+
|
36
|
+
/**
|
37
|
+
* @param {MongoDB} driver
|
38
|
+
*/
|
39
|
+
const list = (driver) => list_regular(driver, col(driver));
|
40
|
+
|
41
|
+
/**
|
42
|
+
* @param {MongoDB} driver
|
43
|
+
*/
|
44
|
+
const count = (driver) => count_regular(driver, col(driver));
|
45
|
+
|
46
|
+
/**
|
47
|
+
* @param {MongoDB} driver
|
48
|
+
*
|
49
|
+
*
|
50
|
+
* @return {db_col & { _col: ReturnType<col>}}
|
51
|
+
* */
|
52
|
+
export const impl = (driver) => {
|
53
|
+
|
54
|
+
return {
|
55
|
+
_col: col(driver),
|
56
|
+
get: get(driver),
|
57
|
+
upsert: upsert(driver),
|
58
|
+
remove: remove(driver),
|
59
|
+
list: list(driver),
|
60
|
+
count: count(driver),
|
61
|
+
}
|
62
|
+
}
|
@@ -0,0 +1,62 @@
|
|
1
|
+
import { Collection } from 'mongodb'
|
2
|
+
import { MongoDB } from '../index.js'
|
3
|
+
import { count_regular, get_regular, list_regular,
|
4
|
+
remove_regular, upsert_regular } from './con.shared.js'
|
5
|
+
|
6
|
+
/**
|
7
|
+
* @typedef {import('@storecraft/core/v-database').db_templates} db_col
|
8
|
+
*/
|
9
|
+
|
10
|
+
|
11
|
+
/**
|
12
|
+
* @param {MongoDB} d
|
13
|
+
*
|
14
|
+
*
|
15
|
+
* @returns {Collection<db_col["$type_get"]>}
|
16
|
+
*/
|
17
|
+
const col = (d) => d.collection('templates');
|
18
|
+
|
19
|
+
/**
|
20
|
+
* @param {MongoDB} driver
|
21
|
+
*
|
22
|
+
* @returns {db_col["upsert"]}
|
23
|
+
*/
|
24
|
+
const upsert = (driver) => upsert_regular(driver, col(driver));
|
25
|
+
|
26
|
+
/**
|
27
|
+
* @param {MongoDB} driver
|
28
|
+
*/
|
29
|
+
const get = (driver) => get_regular(driver, col(driver));
|
30
|
+
|
31
|
+
/**
|
32
|
+
* @param {MongoDB} driver
|
33
|
+
*/
|
34
|
+
const remove = (driver) => remove_regular(driver, col(driver));
|
35
|
+
|
36
|
+
/**
|
37
|
+
* @param {MongoDB} driver
|
38
|
+
*/
|
39
|
+
const list = (driver) => list_regular(driver, col(driver));
|
40
|
+
|
41
|
+
/**
|
42
|
+
* @param {MongoDB} driver
|
43
|
+
*/
|
44
|
+
const count = (driver) => count_regular(driver, col(driver));
|
45
|
+
|
46
|
+
/**
|
47
|
+
* @param {MongoDB} driver
|
48
|
+
*
|
49
|
+
*
|
50
|
+
* @return {db_col & { _col: ReturnType<col>}}
|
51
|
+
* */
|
52
|
+
export const impl = (driver) => {
|
53
|
+
|
54
|
+
return {
|
55
|
+
_col: col(driver),
|
56
|
+
get: get(driver),
|
57
|
+
upsert: upsert(driver),
|
58
|
+
remove: remove(driver),
|
59
|
+
list: list(driver),
|
60
|
+
count: count(driver),
|
61
|
+
}
|
62
|
+
}
|
@@ -0,0 +1,152 @@
|
|
1
|
+
import { ObjectId } from 'mongodb';
|
2
|
+
|
3
|
+
/** @param {any} v */
|
4
|
+
export const isDef = v => v!==undefined && v!==null;
|
5
|
+
|
6
|
+
/** @param {any} v */
|
7
|
+
export const isUndef = v => !isDef(v);
|
8
|
+
|
9
|
+
/**
|
10
|
+
*
|
11
|
+
* @param {...any} keys
|
12
|
+
*/
|
13
|
+
export const delete_keys = (...keys) => {
|
14
|
+
|
15
|
+
/**
|
16
|
+
* @template T
|
17
|
+
*
|
18
|
+
*
|
19
|
+
* @param {T} o
|
20
|
+
*
|
21
|
+
*
|
22
|
+
* @returns {T}
|
23
|
+
*/
|
24
|
+
return (o) => {
|
25
|
+
keys.forEach(k => {o?.[k] && delete o[k]});
|
26
|
+
|
27
|
+
return o
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
/**
|
32
|
+
* Sanitize hidden properties in-place
|
33
|
+
*
|
34
|
+
*
|
35
|
+
* @template {object} T
|
36
|
+
*
|
37
|
+
*
|
38
|
+
* @param {T} o
|
39
|
+
*
|
40
|
+
*
|
41
|
+
* @return {Omit<T, '_id' | '_relations'>}
|
42
|
+
*
|
43
|
+
*/
|
44
|
+
export const sanitize_hidden = o => {
|
45
|
+
if(!isDef(o))
|
46
|
+
return o;
|
47
|
+
|
48
|
+
for (const k of Object.keys(o)) {
|
49
|
+
if(k.startsWith('_'))
|
50
|
+
delete o[k];
|
51
|
+
}
|
52
|
+
return o;
|
53
|
+
}
|
54
|
+
|
55
|
+
/**
|
56
|
+
*
|
57
|
+
* @template T
|
58
|
+
*
|
59
|
+
*
|
60
|
+
* @param {T} o
|
61
|
+
*
|
62
|
+
*
|
63
|
+
* @returns {T}
|
64
|
+
*/
|
65
|
+
export const delete_id = o => {
|
66
|
+
return delete_keys('_id')(o)
|
67
|
+
}
|
68
|
+
|
69
|
+
/**
|
70
|
+
*
|
71
|
+
* Sanitize the mongo document before sending to client
|
72
|
+
*
|
73
|
+
* @template T
|
74
|
+
*
|
75
|
+
*
|
76
|
+
* @param {T} o
|
77
|
+
*/
|
78
|
+
export const sanitize_one = o => {
|
79
|
+
return sanitize_hidden(o)
|
80
|
+
}
|
81
|
+
|
82
|
+
/**
|
83
|
+
* Sanitize the mongo document before sending to client
|
84
|
+
*
|
85
|
+
* @template T
|
86
|
+
*
|
87
|
+
*
|
88
|
+
* @param {T[]} o
|
89
|
+
*
|
90
|
+
*/
|
91
|
+
export const sanitize_array = o => {
|
92
|
+
return o?.map(sanitize_hidden);
|
93
|
+
}
|
94
|
+
|
95
|
+
/**
|
96
|
+
*
|
97
|
+
* @param {string} id
|
98
|
+
*
|
99
|
+
*/
|
100
|
+
export const to_objid = id => {
|
101
|
+
return new ObjectId(id.split('_').at(-1))
|
102
|
+
}
|
103
|
+
|
104
|
+
/**
|
105
|
+
*
|
106
|
+
* @param {string} id
|
107
|
+
*
|
108
|
+
*/
|
109
|
+
export const to_objid_safe = id => {
|
110
|
+
try {
|
111
|
+
return new ObjectId(id.split('_').at(-1))
|
112
|
+
} catch(e) {
|
113
|
+
}
|
114
|
+
|
115
|
+
return undefined;
|
116
|
+
}
|
117
|
+
|
118
|
+
/**
|
119
|
+
* @template {{handle?: string}} G
|
120
|
+
*
|
121
|
+
*
|
122
|
+
* @param {string} handle_or_id
|
123
|
+
*
|
124
|
+
*
|
125
|
+
* @returns {import('mongodb').Filter<G>}
|
126
|
+
*/
|
127
|
+
export const handle_or_id = (handle_or_id) => {
|
128
|
+
return objid_or_else_filter(handle_or_id);
|
129
|
+
}
|
130
|
+
|
131
|
+
|
132
|
+
/**
|
133
|
+
* @template {{handle?: string}} G
|
134
|
+
*
|
135
|
+
*
|
136
|
+
* @param {string} id_or_else
|
137
|
+
*
|
138
|
+
*
|
139
|
+
* @returns {import('mongodb').Filter<G>}
|
140
|
+
*/
|
141
|
+
export const objid_or_else_filter = (id_or_else, else_key='handle') => {
|
142
|
+
try {
|
143
|
+
return {
|
144
|
+
_id: to_objid(id_or_else)
|
145
|
+
}
|
146
|
+
} catch (e) {
|
147
|
+
return {
|
148
|
+
[else_key]: id_or_else
|
149
|
+
}
|
150
|
+
}
|
151
|
+
}
|
152
|
+
|
@@ -0,0 +1,186 @@
|
|
1
|
+
import { to_objid } from "./utils.funcs.js";
|
2
|
+
import { parse } from "@storecraft/core/v-ql";
|
3
|
+
|
4
|
+
let a = {
|
5
|
+
$or: [
|
6
|
+
{ updated_at: { $gt: '2024-01-24T20:28:24.126Z'} },
|
7
|
+
{ $and : [ { updated_at: '2024-01-24T20:28:24.126Z' }, { id: { $gte : 'tag_65b172ebc4c9552fd46c1027'}}]}
|
8
|
+
],
|
9
|
+
}
|
10
|
+
|
11
|
+
/**
|
12
|
+
* Convert an API Query cursor into mongo dialect, also sanitize.
|
13
|
+
*
|
14
|
+
* 1. (a1, a2) > (b1, b2) ==> (a1 > b1) || (a1=b1 & a2>b2)
|
15
|
+
* 2. (a1, a2) >= (b1, b2) ==> (a1 > b1) || (a1=b1 & a2>=b2)
|
16
|
+
* 3. (a1, a2, a3) > (b1, b2, b3) ==> (a1 > b1) || (a1=b1 & a2>b2) || (a1=b1 & a2=b2 & a3>b3)
|
17
|
+
* 4. (a1, a2, a3) >= (b1, b2, b3) ==> (a1 > b1) || (a1=b1 & a2>b2) || (a1=b1 & a2=b2 & a3>=b3)
|
18
|
+
*
|
19
|
+
*
|
20
|
+
* @param {import("@storecraft/core/v-api").Cursor} c
|
21
|
+
* @param {'>' | '>=' | '<' | '<='} relation
|
22
|
+
* @param {(x: [k: string, v: any]) => [k: string, v: any]} transformer
|
23
|
+
* Your chance to change key and value
|
24
|
+
*
|
25
|
+
*/
|
26
|
+
export const query_cursor_to_mongo = (c, relation, transformer=(x)=>x) => {
|
27
|
+
|
28
|
+
let rel_key_1; // relation in last conjunction term in [0, n-1] disjunctions
|
29
|
+
let rel_key_2; // relation in last conjunction term in last disjunction
|
30
|
+
|
31
|
+
if (relation==='>' || relation==='>=') {
|
32
|
+
rel_key_1 = rel_key_2 = '$gt';
|
33
|
+
if(relation==='>=')
|
34
|
+
rel_key_2='$gte';
|
35
|
+
}
|
36
|
+
else if (relation==='<' || relation==='<=') {
|
37
|
+
rel_key_1 = rel_key_2 = '$lt';
|
38
|
+
if(relation==='<=')
|
39
|
+
rel_key_2='$lte';
|
40
|
+
} else return undefined;
|
41
|
+
|
42
|
+
const disjunctions = [];
|
43
|
+
// each disjunction clause
|
44
|
+
for (let ix = 0; ix < c.length; ix++) {
|
45
|
+
const is_last_disjunction = ix==c.length-1;
|
46
|
+
const conjunctions = [];
|
47
|
+
// each conjunction clause up until the last term (not inclusive)
|
48
|
+
for (let jx = 0; jx < ix; jx++) {
|
49
|
+
// the a_n=b_n
|
50
|
+
const r = transformer(c[jx]);
|
51
|
+
conjunctions.push({ [r[0]] : r[1] });
|
52
|
+
}
|
53
|
+
|
54
|
+
// Last conjunction term
|
55
|
+
const relation_key = is_last_disjunction ? rel_key_2 : rel_key_1;
|
56
|
+
const r = transformer(c[ix]);
|
57
|
+
conjunctions.push({ [r[0]] : { [relation_key]: r[1] } });
|
58
|
+
// Add to disjunctions list
|
59
|
+
disjunctions.push({ $and: conjunctions });
|
60
|
+
}
|
61
|
+
|
62
|
+
if(disjunctions.length==0)
|
63
|
+
return undefined;
|
64
|
+
|
65
|
+
const result = {
|
66
|
+
$or: disjunctions
|
67
|
+
};
|
68
|
+
|
69
|
+
return result;
|
70
|
+
}
|
71
|
+
|
72
|
+
/**
|
73
|
+
* @param {import("@storecraft/core/v-ql").VQL.Node} node
|
74
|
+
*/
|
75
|
+
export const query_vql_node_to_mongo = node => {
|
76
|
+
if(node.op==='LEAF') {
|
77
|
+
return {
|
78
|
+
'_relations.search': { $regex: `${node.value}` }
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
let conjunctions = [];
|
83
|
+
for(let arg of node?.args) {
|
84
|
+
conjunctions.push(query_vql_node_to_mongo(arg));
|
85
|
+
}
|
86
|
+
|
87
|
+
switch (node.op) {
|
88
|
+
case '&':
|
89
|
+
return {
|
90
|
+
$and: conjunctions
|
91
|
+
}
|
92
|
+
case '|':
|
93
|
+
return {
|
94
|
+
$or: conjunctions
|
95
|
+
}
|
96
|
+
case '!':
|
97
|
+
return {
|
98
|
+
$nor: [ conjunctions[0] ]
|
99
|
+
}
|
100
|
+
|
101
|
+
default:
|
102
|
+
throw new Error('VQL-to-mongo-failed')
|
103
|
+
}
|
104
|
+
|
105
|
+
}
|
106
|
+
|
107
|
+
/**
|
108
|
+
*
|
109
|
+
* @param {import("@storecraft/core/v-ql").VQL.Node} root
|
110
|
+
*/
|
111
|
+
export const query_vql_to_mongo = root => {
|
112
|
+
return root ? query_vql_node_to_mongo(root) : undefined;
|
113
|
+
}
|
114
|
+
|
115
|
+
/**
|
116
|
+
* Let's transform ids into mongo ids
|
117
|
+
*
|
118
|
+
*
|
119
|
+
* @param {import("@storecraft/core/v-api").Tuple<string>} c a cursor record
|
120
|
+
*
|
121
|
+
*
|
122
|
+
* @returns {[k: string, v: any]}
|
123
|
+
*/
|
124
|
+
const transform = c => {
|
125
|
+
if(c[0]!=='id')
|
126
|
+
return c;
|
127
|
+
return [ '_id', to_objid(c[1]) ];
|
128
|
+
}
|
129
|
+
|
130
|
+
/**
|
131
|
+
* Convert an API Query into mongo dialect, also sanitize.
|
132
|
+
*
|
133
|
+
*
|
134
|
+
* @param {import("@storecraft/core/v-api").ApiQuery} q
|
135
|
+
*/
|
136
|
+
export const query_to_mongo = (q) => {
|
137
|
+
try {
|
138
|
+
if(q.vql && !q.vqlParsed) {
|
139
|
+
q.vqlParsed = parse(q.vql)
|
140
|
+
}
|
141
|
+
} catch(e) {}
|
142
|
+
|
143
|
+
const filter = {};
|
144
|
+
const clauses = [];
|
145
|
+
// `reverse_sign=-1` means we need to reverse because of `limitToLast`
|
146
|
+
const reverse_sign = (q.limitToLast && !q.limit) ? -1 : 1;
|
147
|
+
const asc = q.order === 'asc';
|
148
|
+
const sort_sign = (asc ? 1 : -1) * reverse_sign;
|
149
|
+
|
150
|
+
// const sort_sign = (q.order === 'asc' ? 1 : -1) * reverse_sign;
|
151
|
+
// const asc = (sort_sign * reverse_sign)==1;
|
152
|
+
|
153
|
+
// compute index clauses
|
154
|
+
if(q.startAt) {
|
155
|
+
clauses.push(query_cursor_to_mongo(q.startAt, asc ? '>=' : '<=', transform));
|
156
|
+
} else if(q.startAfter) {
|
157
|
+
clauses.push(query_cursor_to_mongo(q.startAfter, asc ? '>' : '<', transform));
|
158
|
+
}
|
159
|
+
|
160
|
+
if(q.endAt) {
|
161
|
+
clauses.push(query_cursor_to_mongo(q.endAt, asc ? '<=' : '>=', transform));
|
162
|
+
} else if(q.endBefore) {
|
163
|
+
clauses.push(query_cursor_to_mongo(q.endBefore, asc ? '<' : '>', transform));
|
164
|
+
}
|
165
|
+
|
166
|
+
// compute VQL clauses
|
167
|
+
const vql_clause = query_vql_to_mongo(q.vqlParsed)
|
168
|
+
vql_clause && clauses.push(vql_clause);
|
169
|
+
|
170
|
+
// compute sort fields and order
|
171
|
+
const sort = (q.sortBy?.length ? q.sortBy : ['updated_at', 'id']).reduce(
|
172
|
+
(p, c) => (p[c==='id' ? '_id' : c]=sort_sign) && p,
|
173
|
+
{}
|
174
|
+
);
|
175
|
+
|
176
|
+
if(clauses?.length) {
|
177
|
+
filter['$and'] = clauses;
|
178
|
+
}
|
179
|
+
|
180
|
+
return {
|
181
|
+
filter,
|
182
|
+
sort,
|
183
|
+
reverse_sign
|
184
|
+
}
|
185
|
+
|
186
|
+
}
|