@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
@@ -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();
|