@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.
@@ -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
+ }