@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,333 @@
1
+ import { Collection } from 'mongodb'
2
+ import { MongoDB } from '../index.js'
3
+ import { handle_or_id, isUndef, sanitize_array,
4
+ sanitize_one, to_objid } from './utils.funcs.js'
5
+ import { query_to_mongo } from './utils.query.js'
6
+ import { report_document_media } from './con.images.js'
7
+ import { add_search_terms_relation_on } from './utils.relations.js'
8
+
9
+
10
+ /**
11
+ * @template {import('@storecraft/core/v-api').BaseType} T
12
+ * @template {import('@storecraft/core/v-api').BaseType} G
13
+ *
14
+ *
15
+ * @param {MongoDB} driver
16
+ * @param {Collection<G>} col
17
+ *
18
+ *
19
+ * @returns {import('@storecraft/core/v-database').db_crud<T, G>["upsert"]}
20
+ *
21
+ */
22
+ export const upsert_regular = (driver, col) => {
23
+ return async (data, search_terms=[]) => {
24
+
25
+ data = {...data};
26
+
27
+ const session = driver.mongo_client.startSession();
28
+
29
+ try {
30
+ await session.withTransaction(
31
+ async () => {
32
+ // SEARCH
33
+ add_search_terms_relation_on(
34
+ data,
35
+ [
36
+ ...search_terms
37
+ ]
38
+ );
39
+
40
+ const res = await col.replaceOne(
41
+ // @ts-ignore
42
+ {
43
+ _id: to_objid(data.id)
44
+ },
45
+ data,
46
+ {
47
+ session, upsert: true
48
+ }
49
+ );
50
+
51
+ ////
52
+ // REPORT IMAGES USAGE
53
+ ////
54
+ await report_document_media(driver)(data, session);
55
+ }
56
+ );
57
+ } catch(e) {
58
+ console.log(e);
59
+
60
+ return false;
61
+ } finally {
62
+ await session.endSession();
63
+ }
64
+
65
+ return true;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Extract relations names from item
71
+ *
72
+ *
73
+ * @template {import('./utils.relations.js').WithRelations<{}>} T
74
+ *
75
+ *
76
+ * @param {T} item
77
+ */
78
+ export const get_relations_names = item => {
79
+ return Object.keys(item?._relations ?? {});
80
+ }
81
+
82
+ /**
83
+ * Expand relations in-place
84
+ *
85
+ *
86
+ * @template {any} T
87
+ *
88
+ *
89
+ * @param {import('./utils.relations.js').WithRelations<T>[]} items
90
+ * @param {import('@storecraft/core/v-api').ExpandQuery} [expand_query]
91
+ *
92
+ */
93
+ export const expand = (items, expand_query=undefined) => {
94
+
95
+ if(isUndef(expand_query) || !Array.isArray(items))
96
+ return items;
97
+
98
+ items = items.filter(Boolean)
99
+
100
+ const all = expand_query.includes('*');
101
+
102
+ for(const item of items) {
103
+ expand_query = all ? get_relations_names(item) : expand_query;
104
+
105
+ for(const e of (expand_query ?? [])) {
106
+ // try to find embedded documents relations
107
+ const rel = item?._relations?.[e];
108
+ if(rel===undefined || rel===null)
109
+ continue;
110
+
111
+ item[e] = [];
112
+
113
+ if(Array.isArray(rel)) {
114
+ item[e] = rel;
115
+ } else if(rel?.entries) {
116
+ // recurse
117
+ item[e] = sanitize_array(
118
+ expand(
119
+ Object.values(rel.entries),
120
+ ['*']
121
+ )
122
+ );
123
+ }
124
+ }
125
+ }
126
+
127
+ return items;
128
+ }
129
+
130
+
131
+ export const zeroed_relations = {
132
+ '_relations.discounts': 0,
133
+ '_relations.collections': 0,
134
+ '_relations.variants': 0,
135
+ '_relations.related_products': 0,
136
+ '_relations.search': 0,
137
+ '_relations.posts': 0,
138
+ '_relations.products': 0,
139
+ '_relations.shipping_methods': 0,
140
+ }
141
+
142
+
143
+ /**
144
+ *
145
+ * @param {import('@storecraft/core/v-database').RegularGetOptions["expand"]} expand
146
+ */
147
+ export const expand_to_mongo_projection = (expand) => {
148
+ let projection = {}
149
+
150
+ if(!expand?.includes('*')) {
151
+ projection = zeroed_relations;
152
+
153
+ expand?.forEach(
154
+ it => {
155
+ delete projection[`_relations.${it}`];
156
+ }
157
+ )
158
+ }
159
+
160
+ return projection;
161
+ }
162
+
163
+ /**
164
+ * @template T, G
165
+ *
166
+ *
167
+ * @param {MongoDB} driver
168
+ * @param {Collection<G>} col
169
+ *
170
+ *
171
+ * @returns {import('@storecraft/core/v-database').db_crud<T, G>["get"]}
172
+ */
173
+ export const get_regular = (driver, col) => {
174
+ return async (id_or_handle, options) => {
175
+ const filter = handle_or_id(id_or_handle);
176
+
177
+ /** @type {import('./utils.relations.js').WithRelations<import('mongodb').WithId<G>>} */
178
+ const res = await col.findOne(
179
+ filter,
180
+ {
181
+ projection: expand_to_mongo_projection(options?.expand)
182
+ }
183
+ );
184
+
185
+ // try to expand relations
186
+ expand([res], options?.expand);
187
+
188
+ return sanitize_one(res);
189
+ }
190
+ }
191
+
192
+ /**
193
+ * get bulk of items, ordered, if something is missing, `undefined`
194
+ * should be instead
195
+ *
196
+ *
197
+ * @template {import('@storecraft/core/v-api').idable} T
198
+ * @template {import('@storecraft/core/v-api').idable} G
199
+ *
200
+ *
201
+ * @param {MongoDB} driver
202
+ * @param {Collection<G>} col
203
+ *
204
+ *
205
+ * @returns {import('@storecraft/core/v-database').db_crud<T, G>["getBulk"]}
206
+ */
207
+ export const get_bulk = (driver, col) => {
208
+ return async (ids, options) => {
209
+ const objids = ids.map(handle_or_id)
210
+ .map(v => ('_id' in v) ? v._id : undefined)
211
+ .filter(Boolean);
212
+
213
+
214
+
215
+ const res = await col.find(
216
+ // @ts-ignore
217
+ {
218
+ $or: [
219
+ {
220
+ _id: { $in: objids }
221
+ },
222
+ {
223
+ handle: { $in: ids }
224
+ }
225
+ ]
226
+ }
227
+ ).toArray();
228
+
229
+ // try to expand relations
230
+ expand(res, options?.expand);
231
+ const sanitized = sanitize_array(res);
232
+ // console.log('res', sanitized)
233
+ // now let's order them
234
+ return ids.map(
235
+ id => sanitized.find(s => s.id===id || s?.handle===id)
236
+ );
237
+
238
+ }
239
+ }
240
+
241
+
242
+ /**
243
+ * @template T, G
244
+ *
245
+ *
246
+ * @param {MongoDB} driver
247
+ * @param {Collection<G>} col
248
+ *
249
+ *
250
+ * @returns {import('@storecraft/core/v-database').db_crud<T, G>["remove"]}
251
+ */
252
+ export const remove_regular = (driver, col) => {
253
+ return async (id_or_handle) => {
254
+
255
+ const res = await col.deleteOne(
256
+ handle_or_id(id_or_handle)
257
+ );
258
+
259
+ return res.acknowledged && res.deletedCount>0;
260
+ }
261
+ }
262
+
263
+ /**
264
+ * @template {any} T
265
+ * @template {any} G
266
+ *
267
+ *
268
+ * @param {MongoDB} driver
269
+ * @param {Collection<G>} col
270
+ *
271
+ *
272
+ * @returns {import('@storecraft/core/v-database').db_crud<T, G>["list"]}
273
+ */
274
+ export const list_regular = (driver, col) => {
275
+ return async (query) => {
276
+
277
+ const { filter, sort, reverse_sign } = query_to_mongo(query);
278
+
279
+ // console.log('reverse_sign', reverse_sign)
280
+ // console.log('query', query)
281
+ // console.log('filter', JSON.stringify(filter, null, 2))
282
+ // console.log('sort', sort)
283
+ // console.log('expand', query?.expand)
284
+
285
+ /** @type {import('mongodb').WithId<G>[]} */
286
+ const items = await col.find(
287
+ filter, {
288
+ sort,
289
+ limit: reverse_sign==-1 ? query.limitToLast : query.limit,
290
+ projection: expand_to_mongo_projection(query?.expand)
291
+ }
292
+ ).toArray();
293
+
294
+ if(reverse_sign==-1) items.reverse();
295
+
296
+ // try expand relations, that were asked
297
+ const items_expended = expand(items, query?.expand);
298
+
299
+ const sanitized = sanitize_array(items_expended);
300
+
301
+ // console.log('sanitized', sanitized)
302
+
303
+ return sanitized;
304
+ }
305
+ }
306
+
307
+ /**
308
+ * @template {any} T
309
+ * @template {any} G
310
+ *
311
+ *
312
+ * @param {MongoDB} driver
313
+ * @param {Collection<G>} col
314
+ *
315
+ *
316
+ * @returns {import('@storecraft/core/v-database').db_crud<T, G>["count"]}
317
+ */
318
+ export const count_regular = (driver, col) => {
319
+ return async (query) => {
320
+
321
+ const { filter } = query_to_mongo(query);
322
+
323
+ // console.log('query', query);
324
+ // console.log('filter', JSON.stringify(filter, null, 2));
325
+
326
+ const count = await col.countDocuments(
327
+ filter
328
+ );
329
+
330
+ return count;
331
+ }
332
+ }
333
+
@@ -0,0 +1,153 @@
1
+ import { Collection } from 'mongodb'
2
+ import { MongoDB } from '../index.js'
3
+ import { count_regular, get_regular, list_regular } from './con.shared.js'
4
+ import { handle_or_id, to_objid } from './utils.funcs.js';
5
+ import { report_document_media } from './con.images.js';
6
+ import {
7
+ add_search_terms_relation_on, delete_me,
8
+ remove_entry_from_all_connection_of_relation, save_me,
9
+ update_entry_on_all_connection_of_relation
10
+ } from './utils.relations.js';
11
+
12
+ /**
13
+ * @typedef {import('@storecraft/core/v-database').db_shipping} db_col
14
+ */
15
+
16
+ /**
17
+ * @param {MongoDB} d
18
+ *
19
+ *
20
+ * @returns {Collection<import('./utils.relations.js').WithRelations<db_col["$type_get"]>>}
21
+ */
22
+ const col = (d) => d.collection('shipping_methods');
23
+
24
+ /**
25
+ * @param {MongoDB} driver
26
+ *
27
+ *
28
+ * @returns {db_col["upsert"]}
29
+ */
30
+ const upsert = (driver) => {
31
+ return async (data, search_terms=[]) => {
32
+
33
+ data = {...data};
34
+
35
+ const objid = to_objid(data.id);
36
+ const session = driver.mongo_client.startSession();
37
+
38
+ try {
39
+ await session.withTransaction(
40
+ async () => {
41
+
42
+ // SEARCH
43
+ add_search_terms_relation_on(data, search_terms);
44
+
45
+ ////
46
+ // STOREFRONTS --> SHIPPING RELATION
47
+ ////
48
+ await update_entry_on_all_connection_of_relation(
49
+ driver, 'storefronts', 'shipping_methods', objid, data, session
50
+ );
51
+
52
+ ////
53
+ // REPORT IMAGES USAGE
54
+ ////
55
+ await report_document_media(driver)(data, session);
56
+
57
+ // SAVE ME
58
+ await save_me(
59
+ driver, 'shipping_methods', objid, data, session
60
+ );
61
+
62
+ }
63
+ );
64
+ } catch(e) {
65
+ console.log(e);
66
+ return false;
67
+ } finally {
68
+ await session.endSession();
69
+ }
70
+
71
+ return true;
72
+ }
73
+
74
+ }
75
+
76
+ /**
77
+ * @param {MongoDB} driver
78
+ */
79
+ const get = (driver) => get_regular(driver, col(driver));
80
+
81
+ /**
82
+ * @param {MongoDB} driver
83
+ *
84
+ *
85
+ * @returns {db_col["remove"]}
86
+ */
87
+ const remove = (driver) => {
88
+ return async (id_or_handle) => {
89
+ const item = await col(driver).findOne(handle_or_id(id_or_handle));
90
+
91
+ if(!item) return;
92
+
93
+ const objid = to_objid(item.id);
94
+ const session = driver.mongo_client.startSession();
95
+
96
+ try {
97
+ await session.withTransaction(
98
+ async () => {
99
+
100
+ ////
101
+ // STOREFRONTS --> SHIPPING RELATION
102
+ ////
103
+ await remove_entry_from_all_connection_of_relation(
104
+ driver, 'storefronts', 'shipping_methods', objid, session
105
+ );
106
+
107
+ // DELETE ME
108
+ await delete_me(
109
+ driver, 'shipping_methods', objid, session
110
+ );
111
+
112
+ }
113
+ );
114
+ } catch(e) {
115
+ console.log(e);
116
+
117
+ return false;
118
+ } finally {
119
+ await session.endSession();
120
+ }
121
+
122
+ return true;
123
+ }
124
+
125
+ }
126
+
127
+
128
+ /**
129
+ * @param {MongoDB} driver
130
+ */
131
+ const list = (driver) => list_regular(driver, col(driver));
132
+
133
+ /**
134
+ * @param {MongoDB} driver
135
+ */
136
+ const count = (driver) => count_regular(driver, col(driver));
137
+
138
+ /**
139
+ * @param {MongoDB} driver
140
+ *
141
+ *
142
+ * @return {db_col & { _col: ReturnType<col>}}
143
+ */
144
+ export const impl = (driver) => {
145
+ return {
146
+ _col: col(driver),
147
+ get: get(driver),
148
+ upsert: upsert(driver),
149
+ remove: remove(driver),
150
+ list: list(driver),
151
+ count: count(driver),
152
+ }
153
+ }
@@ -0,0 +1,223 @@
1
+ import { Collection } from 'mongodb'
2
+ import { MongoDB } from '../index.js'
3
+ import { count_regular, get_regular, list_regular,
4
+ remove_regular } from './con.shared.js'
5
+ import { sanitize_array, to_objid } from './utils.funcs.js'
6
+ import {
7
+ add_search_terms_relation_on, create_explicit_relation, save_me
8
+ } from './utils.relations.js';
9
+ import { report_document_media } from './con.images.js';
10
+
11
+ /**
12
+ * @typedef {import('@storecraft/core/v-database').db_storefronts} db_col
13
+ */
14
+
15
+ /**
16
+ * @param {MongoDB} d
17
+ *
18
+ *
19
+ * @returns {Collection<
20
+ * import('./utils.relations.js').WithRelations<db_col["$type_get"]>>
21
+ * }
22
+ *
23
+ */
24
+ const col = (d) => d.collection('storefronts');
25
+
26
+ /**
27
+ * @param {MongoDB} driver
28
+ *
29
+ *
30
+ * @return {db_col["upsert"]}
31
+ */
32
+ const upsert = (driver) => {
33
+ return async (data, search_terms=[]) => {
34
+ data = {...data};
35
+
36
+ const session = driver.mongo_client.startSession();
37
+
38
+ try {
39
+ await session.withTransaction(
40
+ async () => {
41
+ ////
42
+ // PRODUCTS/COLLECTIONS/DISCOUNTS/SHIPPING/POSTS RELATIONS (explicit)
43
+ ////
44
+ let replacement = await create_explicit_relation(
45
+ driver, data, 'products', 'products', true
46
+ );
47
+ replacement = await create_explicit_relation(
48
+ driver, replacement, 'collections', 'collections', true
49
+ );
50
+ replacement = await create_explicit_relation(
51
+ driver, replacement, 'discounts', 'discounts', true
52
+ );
53
+ replacement = await create_explicit_relation(
54
+ driver, replacement, 'shipping_methods', 'shipping_methods', true
55
+ );
56
+ replacement = await create_explicit_relation(
57
+ driver, replacement, 'posts', 'posts', true
58
+ );
59
+
60
+ // SEARCH
61
+ add_search_terms_relation_on(replacement, search_terms);
62
+
63
+ ////
64
+ // REPORT IMAGES USAGE
65
+ ////
66
+ await report_document_media(driver)(replacement, session);
67
+
68
+ // SAVE ME
69
+ await save_me(
70
+ driver, 'storefronts', to_objid(data.id), replacement, session
71
+ );
72
+
73
+ }
74
+ );
75
+ } catch (e) {
76
+ console.log(e);
77
+
78
+ return false;
79
+ } finally {
80
+ await session.endSession();
81
+ }
82
+
83
+ return true;
84
+ }
85
+
86
+ }
87
+
88
+ /**
89
+ * @param {MongoDB} driver
90
+ */
91
+ const get = (driver) => get_regular(driver, col(driver));
92
+
93
+ /**
94
+ * @param {MongoDB} driver
95
+ */
96
+ const remove = (driver) => remove_regular(driver, col(driver));
97
+
98
+ /**
99
+ * @param {MongoDB} driver
100
+ */
101
+ const list = (driver) => list_regular(driver, col(driver));
102
+
103
+ /**
104
+ * @param {MongoDB} driver
105
+ */
106
+ const count = (driver) => count_regular(driver, col(driver));
107
+
108
+ /**
109
+ * @param {MongoDB} driver
110
+ *
111
+ *
112
+ * @returns {db_col["list_storefront_products"]}
113
+ */
114
+ const list_storefront_products = (driver) => {
115
+ return async (product) => {
116
+ /** @type {import('@storecraft/core/v-database').RegularGetOptions} */
117
+ const options = {
118
+ expand: ['products']
119
+ };
120
+
121
+ const item = await get_regular(driver, col(driver))(product, options);
122
+
123
+ return sanitize_array(item?.products ?? []);
124
+ }
125
+ }
126
+
127
+ /**
128
+ * @param {MongoDB} driver
129
+ *
130
+ *
131
+ * @returns {db_col["list_storefront_collections"]}
132
+ */
133
+ const list_storefront_collections = (driver) => {
134
+ return async (product) => {
135
+ /** @type {import('@storecraft/core/v-database').RegularGetOptions} */
136
+ const options = {
137
+ expand: ['collections']
138
+ };
139
+
140
+ const item = await get_regular(driver, col(driver))(product, options);
141
+
142
+ return sanitize_array(item?.collections ?? []);
143
+ }
144
+ }
145
+
146
+ /**
147
+ * @param {MongoDB} driver
148
+ *
149
+ *
150
+ * @returns {db_col["list_storefront_discounts"]}
151
+ */
152
+ const list_storefront_discounts = (driver) => {
153
+ return async (product) => {
154
+ /** @type {import('@storecraft/core/v-database').RegularGetOptions} */
155
+ const options = {
156
+ expand: ['discounts']
157
+ };
158
+
159
+ const item = await get_regular(driver, col(driver))(product, options);
160
+
161
+ return sanitize_array(item?.discounts ?? []);
162
+ }
163
+ }
164
+
165
+ /**
166
+ * @param {MongoDB} driver
167
+ *
168
+ * @returns {db_col["list_storefront_shipping_methods"]}
169
+ */
170
+ const list_storefront_shipping_methods = (driver) => {
171
+ return async (product) => {
172
+ /** @type {import('@storecraft/core/v-database').RegularGetOptions} */
173
+ const options = {
174
+ expand: ['shipping_methods']
175
+ };
176
+
177
+ const item = await get_regular(driver, col(driver))(product, options);
178
+
179
+ return sanitize_array(item?.shipping_methods ?? []);
180
+ }
181
+ }
182
+
183
+ /**
184
+ * @param {MongoDB} driver
185
+ *
186
+ *
187
+ * @returns {db_col["list_storefront_posts"]}
188
+ */
189
+ const list_storefront_posts = (driver) => {
190
+ return async (product) => {
191
+ /** @type {import('@storecraft/core/v-database').RegularGetOptions} */
192
+ const options = {
193
+ expand: ['posts']
194
+ };
195
+
196
+ const item = await get_regular(driver, col(driver))(product, options);
197
+
198
+ return sanitize_array(item?.posts ?? []);
199
+ }
200
+ }
201
+
202
+ /**
203
+ * @param {MongoDB} driver
204
+ *
205
+ *
206
+ * @return {db_col & { _col: ReturnType<col>}}
207
+ * */
208
+ export const impl = (driver) => {
209
+
210
+ return {
211
+ _col: col(driver),
212
+ get: get(driver),
213
+ upsert: upsert(driver),
214
+ remove: remove(driver),
215
+ list: list(driver),
216
+ count: count(driver),
217
+ list_storefront_products: list_storefront_products(driver),
218
+ list_storefront_collections: list_storefront_collections(driver),
219
+ list_storefront_discounts: list_storefront_discounts(driver),
220
+ list_storefront_shipping_methods: list_storefront_shipping_methods(driver),
221
+ list_storefront_posts: list_storefront_posts(driver),
222
+ }
223
+ }