@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,410 @@
1
+ import { ClientSession, ObjectId } from 'mongodb';
2
+ import { isDef, isUndef, to_objid } from './utils.funcs.js';
3
+ import { MongoDB } from '../index.js';
4
+ import { zeroed_relations } from './con.shared.js';
5
+
6
+ /**
7
+ * @template {any} T
8
+ *
9
+ *
10
+ * @typedef {Object} Relation
11
+ * @property {ObjectId[]} [ids]
12
+ * @property {Record<
13
+ * import('@storecraft/core/v-database').ID, T>
14
+ * } [entries]
15
+ */
16
+
17
+ /**
18
+ * @template {any} T
19
+ *
20
+ * @typedef {T & { _relations? : Record<string, Relation<any>> }} WithRelations
21
+ */
22
+
23
+ /**
24
+ *
25
+ * On upsert Create a relation on a given field that represents a relation.
26
+ * for example, each product specifies collections it belongs to.
27
+ * Basically creates an ids array and embedded documents for fast retrival.
28
+ *
29
+ *
30
+ * @template {import('@storecraft/core/v-api').BaseType} T
31
+ *
32
+ *
33
+ * @param {MongoDB} driver our driver
34
+ * @param {T} data data to create the connection from
35
+ * @param {string} fieldName the field name, that represents
36
+ * a relation, a field with { id } property
37
+ * @param {string} belongsToCollection which collection
38
+ * does the field relate to
39
+ * @param {boolean} [reload=false] re-retrive documents ?
40
+ *
41
+ *
42
+ * @returns {Promise<WithRelations<T>>}
43
+ */
44
+ export const create_explicit_relation = async (
45
+ driver, data, fieldName, belongsToCollection, reload=false
46
+ ) => {
47
+
48
+ const value = data?.[fieldName];
49
+
50
+ if(isUndef(value))
51
+ return data;
52
+
53
+ /** @type {import('@storecraft/core/v-api').BaseType[]} */
54
+ const items = Array.isArray(value) ? value : [value];
55
+
56
+ /** @type {WithRelations<any>} */
57
+ let data_with_rel = { ...data }
58
+ data_with_rel._relations = data_with_rel._relations ?? {};
59
+
60
+ /** @type {Relation<any>} */
61
+ const relation = data_with_rel._relations[fieldName] = {};
62
+
63
+ relation.ids = items.filter(i => isDef(i?.id)).map(c => to_objid(c.id));
64
+ relation.entries = {};
65
+
66
+ if(reload) {
67
+ const entries = await driver.collection(belongsToCollection).find(
68
+ {
69
+ _id: {
70
+ $in : relation.ids
71
+ }
72
+ },
73
+ {
74
+ projection: zeroed_relations
75
+ }
76
+ ).toArray();
77
+
78
+ // console.log('belongsToCollection', belongsToCollection)
79
+ // console.log('entries', entries)
80
+
81
+ entries.forEach(
82
+ e => {
83
+ relation.entries[e._id.toString()] = e;
84
+ }
85
+ );
86
+
87
+ } else {
88
+ relation.entries = Object.fromEntries(
89
+ items.map(it => [it.id.split('_').at(-1), it])
90
+ );
91
+ }
92
+
93
+ // delete fieldname
94
+ delete data_with_rel[fieldName];
95
+
96
+ return data_with_rel;
97
+ }
98
+
99
+ /**
100
+ * Create a `search` relation on the document (embed it)
101
+ *
102
+ * @template {Object.<string, any>} T
103
+ *
104
+ * @param {WithRelations<T>} data
105
+ * @param {string[]} terms
106
+ *
107
+ */
108
+ export const add_search_terms_relation_on = (data, terms=[]) => {
109
+ if(!data)
110
+ return;
111
+
112
+ if(!Array.isArray(terms))
113
+ throw new Error('terms is not an array !');
114
+
115
+ data._relations = data._relations ?? {};
116
+ data._relations.search = terms;
117
+
118
+ delete data['search'];
119
+
120
+ return data;
121
+ }
122
+
123
+
124
+ /**
125
+ *
126
+ * Update an `entry` on all of it's connections in the `relation`.
127
+ * Suppose, we have a many-to-x relation, then we update `x` on
128
+ * all of these many connections at once.
129
+ *
130
+ *
131
+ * @param {MongoDB} driver mongodb driver instance
132
+ * @param {string} collection the collection from which the `relation` is from
133
+ * @param {string} relation_name the `relation` name
134
+ * @param {ObjectId} entry_objid the proper `ObjectId` of the entry
135
+ * @param {object} entry the entry data
136
+ * @param {ClientSession} [session] client `session` for atomicity purposes
137
+ * @param {string[]} [search_terms_to_add=[]] Extra `search` terms to add
138
+ * to all the affected connections
139
+ *
140
+ */
141
+ export const update_entry_on_all_connection_of_relation = (
142
+ driver, collection, relation_name, entry_objid, entry, session,
143
+ search_terms_to_add=[]
144
+ ) => {
145
+
146
+ return driver.collection(collection).updateMany(
147
+ {
148
+ [`_relations.${relation_name}.ids`] : entry_objid
149
+ },
150
+ {
151
+ $set: {
152
+ [`_relations.${relation_name}.entries.${entry_objid.toString()}`]: entry
153
+ },
154
+ $addToSet: {
155
+ '_relations.search': { $each : search_terms_to_add}
156
+ },
157
+ },
158
+ {
159
+ session,
160
+ upsert: false
161
+ }
162
+ );
163
+
164
+ }
165
+
166
+
167
+ /**
168
+ *
169
+ * Update / Create an `entry` on a specific connection, that is
170
+ * found by `mongodb` filter in the `relation`.
171
+ * Suppose, we have a many-to-x relation, then we create a new
172
+ * connection a-to-x
173
+ *
174
+ *
175
+ * @param {MongoDB} driver mongodb driver instance
176
+ * @param {string} collection the collection from which the `relation` is from
177
+ * @param {string} relation_name the `relation` name
178
+ * @param {import('mongodb').Filter<any>} from_object_filter the proper `ObjectId` of the from connection
179
+ * @param {ObjectId} entry_objid the proper `ObjectId` of the entry
180
+ * @param {object} entry the entry data
181
+ * @param {ClientSession} [session] client `session` for atomicity purposes
182
+ * @param {string[]} [search_terms_to_add=[]] Extra `search` terms to add
183
+ * to the affected connection
184
+ *
185
+ */
186
+ export const update_specific_connection_of_relation_with_filter = (
187
+ driver, collection, relation_name, from_object_filter,
188
+ entry_objid, entry, session, search_terms_to_add=[]
189
+ ) => {
190
+
191
+ return driver.collection(collection).updateOne(
192
+ from_object_filter,
193
+ {
194
+ $set: {
195
+ [`_relations.${relation_name}.entries.${entry_objid.toString()}`]: entry
196
+ },
197
+ $addToSet: {
198
+ [`_relations.${relation_name}.ids`]: entry_objid,
199
+ '_relations.search': { $each : search_terms_to_add}
200
+ },
201
+ },
202
+ {
203
+ session,
204
+ upsert: false
205
+ }
206
+ );
207
+
208
+ }
209
+
210
+ /**
211
+ *
212
+ * Update / Create an `entry` on a specific connection in the `relation`.
213
+ * Suppose, we have a many-to-x relation, then we create a new
214
+ * connection a-to-x
215
+ *
216
+ *
217
+ * @param {MongoDB} driver mongodb driver instance
218
+ * @param {string} collection the collection from which the `relation` is from
219
+ * @param {string} relation_name the `relation` name
220
+ * @param {ObjectId} from_objid the proper `ObjectId` of the from connection
221
+ * @param {ObjectId} entry_objid the proper `ObjectId` of the entry
222
+ * @param {object} entry the entry data
223
+ * @param {ClientSession} [session] client `session` for atomicity purposes
224
+ * @param {string[]} [search_terms_to_add=[]] Extra `search` terms to add
225
+ * to the affected connection
226
+ *
227
+ */
228
+ export const update_specific_connection_of_relation = (
229
+ driver, collection, relation_name, from_objid, entry_objid,
230
+ entry, session, search_terms_to_add=[]
231
+ ) => {
232
+
233
+ return update_specific_connection_of_relation_with_filter(
234
+ driver, collection, relation_name,
235
+ {
236
+ _id: from_objid
237
+ },
238
+ entry_objid, entry, session,
239
+ search_terms_to_add
240
+ );
241
+
242
+ }
243
+
244
+
245
+ /**
246
+ *
247
+ * Remove an `entry` from all of it's connections in the `relation`.
248
+ * Suppose, we have a many-to-x relation, then we remove `x` from
249
+ * all these many connections at once.
250
+ *
251
+ *
252
+ * @param {MongoDB} driver mongodb driver instance
253
+ * @param {string} collection the collection from which the `relation` is from
254
+ * @param {string} relation_name the `relation` name
255
+ * @param {ObjectId} entry_objid the proper `ObjectId` of the entry
256
+ * @param {ClientSession} [session] client `session` for atomicity purposes
257
+ * @param {string[]} [search_terms_to_remove=[]] Extra `search` terms to remove
258
+ * from all the connections
259
+ *
260
+ */
261
+ export const remove_entry_from_all_connection_of_relation = (
262
+ driver, collection, relation_name, entry_objid, session,
263
+ search_terms_to_remove=[]
264
+ ) => {
265
+ return driver.collection(collection).updateMany(
266
+ {
267
+ [`_relations.${relation_name}.ids`] : entry_objid
268
+ },
269
+ {
270
+ $pull: {
271
+ [`_relations.${relation_name}.ids`] : entry_objid,
272
+ '_relations.search': { $in : search_terms_to_remove }
273
+ },
274
+ $unset: {
275
+ [`_relations.${relation_name}.entries.${entry_objid.toString()}`]: ''
276
+ },
277
+ },
278
+ {
279
+ session,
280
+ upsert: false
281
+ }
282
+ );
283
+ }
284
+
285
+
286
+ /**
287
+ *
288
+ * Remove an `entry` from a specific connection in the `relation`.
289
+ * Suppose, we have a a-to-x relation, then we remove `x` from
290
+ * `a` connection.
291
+ *
292
+ *
293
+ * @param {MongoDB} driver mongodb driver instance
294
+ * @param {string} collection the collection from which the `relation` is from
295
+ * @param {string} relation_name the `relation` name
296
+ * @param {ObjectId} from_objid the proper `ObjectId` of the from connection
297
+ * @param {ObjectId} entry_objid the proper `ObjectId` of the entry
298
+ * @param {ClientSession} [session] client `session` for atomicity purposes
299
+ * @param {string[]} [search_terms_to_remove=[]] Extra `search` terms to remove
300
+ * from the affected connection
301
+ *
302
+ */
303
+ export const remove_specific_connection_of_relation = (
304
+ driver, collection, relation_name, from_objid, entry_objid, session,
305
+ search_terms_to_remove=[]
306
+ ) => {
307
+
308
+ return remove_specific_connection_of_relation_with_filter(
309
+ driver, collection, relation_name,
310
+ {
311
+ _id: from_objid
312
+ },
313
+ entry_objid, session,
314
+ search_terms_to_remove
315
+ );
316
+ }
317
+
318
+
319
+ /**
320
+ *
321
+ * Remove an `entry` from a specific connection in the `relation`.
322
+ * Suppose, we have a a-to-x relation, then we remove `x` from
323
+ * `a` connection.
324
+ *
325
+ * We locate `a` by a `mongodb` filter
326
+ *
327
+ *
328
+ * @param {MongoDB} driver mongodb driver instance
329
+ * @param {string} collection the collection from which the `relation` is from
330
+ * @param {string} relation_name the `relation` name
331
+ * @param {import('mongodb').Filter<any>} from_object_filter
332
+ * `mongodb` Filter to locate the first document, the from part of the connection
333
+ * @param {ObjectId} entry_objid the proper `ObjectId` of the entry
334
+ * @param {ClientSession} [session] client `session` for atomicity purposes
335
+ * @param {string[]} [search_terms_to_remove=[]] Extra `search` terms to remove
336
+ * from the affected connection
337
+ *
338
+ */
339
+ export const remove_specific_connection_of_relation_with_filter = (
340
+ driver, collection, relation_name, from_object_filter, entry_objid, session,
341
+ search_terms_to_remove
342
+ ) => {
343
+
344
+ return driver.collection(collection).updateOne(
345
+ from_object_filter,
346
+ {
347
+ $pull: {
348
+ [`_relations.${relation_name}.ids`] : entry_objid,
349
+ '_relations.search': { $in : search_terms_to_remove }
350
+ },
351
+ $unset: {
352
+ [`_relations.${relation_name}.entries.${entry_objid.toString()}`]: ''
353
+ },
354
+ },
355
+ {
356
+ session, upsert: false
357
+ }
358
+ );
359
+
360
+ }
361
+
362
+
363
+ /**
364
+ *
365
+ * A simple `save` (using **Mongo** `replaceOne`)
366
+ *
367
+ *
368
+ * @param {MongoDB} driver `mongodb` driver
369
+ * @param {string} collection the `collection` to save into
370
+ * @param {ObjectId} object_id the `object id` of the item
371
+ * @param {object} document the document data
372
+ * @param {ClientSession} [session] client `session` for atomicity purposes
373
+ *
374
+ */
375
+ export const save_me = (driver, collection, object_id, document, session) => {
376
+ return driver.collection(collection).replaceOne(
377
+ {
378
+ _id: object_id
379
+ },
380
+ document,
381
+ {
382
+ session,
383
+ upsert: true
384
+ }
385
+ );
386
+
387
+ }
388
+
389
+
390
+ /**
391
+ *
392
+ * A simple `delete` (using **Mongo** `deleteOne`) with `session` transaction
393
+ *
394
+ *
395
+ * @param {MongoDB} driver `mongodb` driver
396
+ * @param {string} collection the `collection` to save into
397
+ * @param {ObjectId} object_id the `object id` of the item
398
+ * @param {ClientSession} [session] client `session` for atomicity purposes
399
+ *
400
+ */
401
+ export const delete_me = (driver, collection, object_id, session) => {
402
+ return driver.collection(collection).deleteOne(
403
+ {
404
+ _id: object_id
405
+ },
406
+ {
407
+ session,
408
+ }
409
+ );
410
+ }
@@ -0,0 +1,34 @@
1
+ import 'dotenv/config'
2
+ import { MongoClient, ServerApiVersion } from 'mongodb';
3
+
4
+ import { test } from 'uvu';
5
+ import * as assert from 'uvu/assert';
6
+
7
+ const uri = process.env.MONGODB_URL;
8
+
9
+ console.log('uri ', uri);
10
+
11
+ const client = new MongoClient(
12
+ uri, {
13
+ serverApi: {
14
+ version: ServerApiVersion.v1,
15
+ strict: true,
16
+ deprecationErrors: true,
17
+ }
18
+ }
19
+ );
20
+
21
+ test('basic connect', async () => {
22
+ // Connect the client to the server (optional starting in v4.7)
23
+ await client.connect();
24
+ // Send a ping to confirm a successful connection
25
+ await client.db("test").command({ ping: 1 });
26
+ console.log("Pinged your deployment. You successfully connected to MongoDB!");
27
+ // Ensures that the client will close when you finish/error
28
+ await client.close();
29
+
30
+ // assert.is(res2.verified, false);
31
+ });
32
+
33
+
34
+ test.run();