@storecraft/database-mongodb 1.0.20 → 1.2.5

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.
@@ -1,25 +1,19 @@
1
1
  /**
2
- * @import { ID } from '@storecraft/core/database'
3
2
  * @import { BaseType } from '@storecraft/core/api'
4
3
  * @import { Relation, WithRelations } from './utils.types.js'
5
- * @import { Filter } from 'mongodb'
4
+ * @import { Collection, Filter } from 'mongodb'
6
5
  */
7
-
8
6
  import { ClientSession, ObjectId } from 'mongodb';
9
7
  import { isDef, isUndef, to_objid } from './utils.funcs.js';
10
8
  import { MongoDB } from '../index.js';
11
9
  import { zeroed_relations } from './con.shared.js';
12
10
 
13
11
  /**
14
- *
15
- * On upsert Create a relation on a given field that represents a relation.
16
- * for example, each product specifies collections it belongs to.
17
- * Basically creates an ids array and embedded documents for fast retrival.
18
- *
19
- *
12
+ * @description On upsert Create a relation on a given
13
+ * field that represents a relation. for example, each
14
+ * product specifies collections it belongs to. Basically
15
+ * creates an ids array and embedded documents for fast retrival.
20
16
  * @template {BaseType} T
21
- *
22
- *
23
17
  * @param {MongoDB} driver our driver
24
18
  * @param {T} data data to create the connection from
25
19
  * @param {string} fieldName the field name, that represents
@@ -27,8 +21,6 @@ import { zeroed_relations } from './con.shared.js';
27
21
  * @param {string} belongsToCollection which collection
28
22
  * does the field relate to
29
23
  * @param {boolean} [reload=false] re-retrive documents ?
30
- *
31
- *
32
24
  * @returns {Promise<WithRelations<T>>}
33
25
  */
34
26
  export const create_explicit_relation = async (
@@ -50,11 +42,16 @@ export const create_explicit_relation = async (
50
42
  /** @type {Relation<any>} */
51
43
  const relation = data_with_rel._relations[fieldName] = {};
52
44
 
53
- relation.ids = items.filter(i => isDef(i?.id)).map(c => to_objid(c.id));
45
+ relation.ids = items
46
+ .filter(i => isDef(i?.id))
47
+ .map(c => to_objid(c.id));
48
+
54
49
  relation.entries = {};
55
50
 
56
51
  if(reload) {
57
- const entries = await driver.collection(belongsToCollection).find(
52
+ const entries = await driver
53
+ .collection(belongsToCollection)
54
+ .find(
58
55
  {
59
56
  _id: {
60
57
  $in : relation.ids
@@ -76,7 +73,9 @@ export const create_explicit_relation = async (
76
73
 
77
74
  } else {
78
75
  relation.entries = Object.fromEntries(
79
- items.map(it => [it.id.split('_').at(-1), it])
76
+ items.map(
77
+ it => [it.id.split('_').at(-1), it]
78
+ )
80
79
  );
81
80
  }
82
81
 
@@ -87,17 +86,14 @@ export const create_explicit_relation = async (
87
86
  }
88
87
 
89
88
  /**
90
- * Create a `search` relation on the document (embed it)
91
- *
89
+ * @description Create a `search` relation on the document (embed it)
92
90
  * @template {Object.<string, any>} T
93
- *
94
91
  * @param {WithRelations<T>} data
95
92
  * @param {string[]} terms
96
- *
97
93
  */
98
94
  export const add_search_terms_relation_on = (data, terms=[]) => {
99
95
  if(!data)
100
- return;
96
+ return undefined;
101
97
 
102
98
  if(!Array.isArray(terms))
103
99
  throw new Error('terms is not an array !');
@@ -112,12 +108,9 @@ export const add_search_terms_relation_on = (data, terms=[]) => {
112
108
 
113
109
 
114
110
  /**
115
- *
116
- * Update an `entry` on all of it's connections in the `relation`.
111
+ * @description Update an `entry` on all of it's connections in the `relation`.
117
112
  * Suppose, we have a many-to-x relation, then we update `x` on
118
113
  * all of these many connections at once.
119
- *
120
- *
121
114
  * @param {MongoDB} driver mongodb driver instance
122
115
  * @param {string} collection the collection from which the `relation` is from
123
116
  * @param {string} relation_name the `relation` name
@@ -126,7 +119,6 @@ export const add_search_terms_relation_on = (data, terms=[]) => {
126
119
  * @param {ClientSession} [session] client `session` for atomicity purposes
127
120
  * @param {string[]} [search_terms_to_add=[]] Extra `search` terms to add
128
121
  * to all the affected connections
129
- *
130
122
  */
131
123
  export const update_entry_on_all_connection_of_relation = (
132
124
  driver, collection, relation_name, entry_objid, entry, session,
@@ -155,23 +147,20 @@ export const update_entry_on_all_connection_of_relation = (
155
147
 
156
148
 
157
149
  /**
158
- *
159
- * Update / Create an `entry` on a specific connection, that is
160
- * found by `mongodb` filter in the `relation`.
150
+ * @description Update / Create an `entry` on a specific connection,
151
+ * that is found by `mongodb` filter in the `relation`.
161
152
  * Suppose, we have a many-to-x relation, then we create a new
162
153
  * connection a-to-x
163
- *
164
- *
165
154
  * @param {MongoDB} driver mongodb driver instance
166
155
  * @param {string} collection the collection from which the `relation` is from
167
156
  * @param {string} relation_name the `relation` name
168
- * @param {import('mongodb').Filter<any>} from_object_filter the proper `ObjectId` of the from connection
157
+ * @param {import('mongodb').Filter<any>} from_object_filter
158
+ * the proper `ObjectId` of the from connection
169
159
  * @param {ObjectId} entry_objid the proper `ObjectId` of the entry
170
160
  * @param {object} entry the entry data
171
161
  * @param {ClientSession} [session] client `session` for atomicity purposes
172
162
  * @param {string[]} [search_terms_to_add=[]] Extra `search` terms to add
173
163
  * to the affected connection
174
- *
175
164
  */
176
165
  export const update_specific_connection_of_relation_with_filter = (
177
166
  driver, collection, relation_name, from_object_filter,
@@ -198,12 +187,9 @@ export const update_specific_connection_of_relation_with_filter = (
198
187
  }
199
188
 
200
189
  /**
201
- *
202
- * Update / Create an `entry` on a specific connection in the `relation`.
203
- * Suppose, we have a many-to-x relation, then we create a new
204
- * connection a-to-x
205
- *
206
- *
190
+ * @description Update / Create an `entry` on a specific connection
191
+ * in the `relation`. Suppose, we have a many-to-x relation, then we
192
+ * create a new connection a-to-x
207
193
  * @param {MongoDB} driver mongodb driver instance
208
194
  * @param {string} collection the collection from which the `relation` is from
209
195
  * @param {string} relation_name the `relation` name
@@ -213,7 +199,6 @@ export const update_specific_connection_of_relation_with_filter = (
213
199
  * @param {ClientSession} [session] client `session` for atomicity purposes
214
200
  * @param {string[]} [search_terms_to_add=[]] Extra `search` terms to add
215
201
  * to the affected connection
216
- *
217
202
  */
218
203
  export const update_specific_connection_of_relation = (
219
204
  driver, collection, relation_name, from_objid, entry_objid,
@@ -233,12 +218,9 @@ export const update_specific_connection_of_relation = (
233
218
 
234
219
 
235
220
  /**
236
- *
237
- * Remove an `entry` from all of it's connections in the `relation`.
238
- * Suppose, we have a many-to-x relation, then we remove `x` from
239
- * all these many connections at once.
240
- *
241
- *
221
+ * @description Remove an `entry` from all of it's connections
222
+ * in the `relation`. Suppose, we have a many-to-x relation, then
223
+ * we remove `x` from all these many connections at once.
242
224
  * @param {MongoDB} driver mongodb driver instance
243
225
  * @param {string} collection the collection from which the `relation` is from
244
226
  * @param {string} relation_name the `relation` name
@@ -247,13 +229,14 @@ export const update_specific_connection_of_relation = (
247
229
  * @param {string[]} [search_terms_to_remove=[]] Extra `search` terms to remove
248
230
  * @param {string[]} [tags_to_remove=[]] Extra `tags` terms to remove
249
231
  * from all the connections
250
- *
251
232
  */
252
233
  export const remove_entry_from_all_connection_of_relation = (
253
234
  driver, collection, relation_name, entry_objid, session,
254
235
  search_terms_to_remove=[], tags_to_remove=[]
255
236
  ) => {
256
- return driver.collection(collection).updateMany(
237
+ return /** @type {Collection<{}>} */(driver
238
+ .collection(collection))
239
+ .updateMany(
257
240
  {
258
241
  [`_relations.${relation_name}.ids`] : entry_objid
259
242
  },
@@ -276,12 +259,9 @@ export const remove_entry_from_all_connection_of_relation = (
276
259
 
277
260
 
278
261
  /**
279
- *
280
- * Remove an `entry` from a specific connection in the `relation`.
281
- * Suppose, we have a a-to-x relation, then we remove `x` from
282
- * `a` connection.
283
- *
284
- *
262
+ * @description Remove an `entry` from a specific connection in
263
+ * the `relation`. Suppose, we have a a-to-x relation, then we remove
264
+ * `x` from `a` connection.
285
265
  * @param {MongoDB} driver mongodb driver instance
286
266
  * @param {string} collection the collection from which the `relation` is from
287
267
  * @param {string} relation_name the `relation` name
@@ -290,7 +270,6 @@ export const remove_entry_from_all_connection_of_relation = (
290
270
  * @param {ClientSession} [session] client `session` for atomicity purposes
291
271
  * @param {string[]} [search_terms_to_remove=[]] Extra `search` terms to remove
292
272
  * from the affected connection
293
- *
294
273
  */
295
274
  export const remove_specific_connection_of_relation = (
296
275
  driver, collection, relation_name, from_objid, entry_objid, session,
@@ -309,31 +288,27 @@ export const remove_specific_connection_of_relation = (
309
288
 
310
289
 
311
290
  /**
312
- *
313
- * Remove an `entry` from a specific connection in the `relation`.
291
+ * @description Remove an `entry` from a specific connection in the `relation`.
314
292
  * Suppose, we have a a-to-x relation, then we remove `x` from
315
- * `a` connection.
316
- *
317
- * We locate `a` by a `mongodb` filter
318
- *
319
- *
293
+ * `a` connection. We locate `a` by a `mongodb` filter
320
294
  * @param {MongoDB} driver mongodb driver instance
321
295
  * @param {string} collection the collection from which the `relation` is from
322
296
  * @param {string} relation_name the `relation` name
323
- * @param {Filter<any>} from_object_filter
297
+ * @param {Filter<{}>} from_object_filter
324
298
  * `mongodb` Filter to locate the first document, the from part of the connection
325
299
  * @param {ObjectId} entry_objid the proper `ObjectId` of the entry
326
300
  * @param {ClientSession} [session] client `session` for atomicity purposes
327
301
  * @param {string[]} [search_terms_to_remove=[]] Extra `search` terms to remove
328
302
  * from the affected connection
329
- *
330
303
  */
331
304
  export const remove_specific_connection_of_relation_with_filter = (
332
305
  driver, collection, relation_name, from_object_filter, entry_objid, session,
333
306
  search_terms_to_remove
334
307
  ) => {
335
308
 
336
- return driver.collection(collection).updateOne(
309
+ return /** @type {Collection<{}>} */(driver
310
+ .collection(collection))
311
+ .updateOne(
337
312
  from_object_filter,
338
313
  {
339
314
  $pull: {
@@ -353,16 +328,12 @@ export const remove_specific_connection_of_relation_with_filter = (
353
328
 
354
329
 
355
330
  /**
356
- *
357
- * A simple `save` (using **Mongo** `replaceOne`)
358
- *
359
- *
331
+ * @description A simple `save` (using **Mongo** `replaceOne`)
360
332
  * @param {MongoDB} driver `mongodb` driver
361
333
  * @param {string} collection the `collection` to save into
362
334
  * @param {ObjectId} object_id the `object id` of the item
363
335
  * @param {object} document the document data
364
336
  * @param {ClientSession} [session] client `session` for atomicity purposes
365
- *
366
337
  */
367
338
  export const save_me = (driver, collection, object_id, document, session) => {
368
339
  return driver.collection(collection).replaceOne(
@@ -375,20 +346,16 @@ export const save_me = (driver, collection, object_id, document, session) => {
375
346
  upsert: true
376
347
  }
377
348
  );
378
-
379
349
  }
380
350
 
381
351
 
382
352
  /**
383
- *
384
- * A simple `delete` (using **Mongo** `deleteOne`) with `session` transaction
385
- *
386
- *
353
+ * @description A simple `delete` (using **Mongo** `deleteOne`)
354
+ * with `session` transaction
387
355
  * @param {MongoDB} driver `mongodb` driver
388
356
  * @param {string} collection the `collection` to save into
389
357
  * @param {ObjectId} object_id the `object id` of the item
390
358
  * @param {ClientSession} [session] client `session` for atomicity purposes
391
- *
392
359
  */
393
360
  export const delete_me = (driver, collection, object_id, session) => {
394
361
  return driver.collection(collection).deleteOne(
@@ -4,7 +4,11 @@ import { MongoDB, migrateToLatest } from '@storecraft/database-mongodb';
4
4
  import { NodePlatform } from '@storecraft/core/platform/node';
5
5
  import { api } from '@storecraft/core/test-runner';
6
6
 
7
- export const create_app = async () => {
7
+ //
8
+ // Main MongoDB test suite with the core test-runner for api layer
9
+ //
10
+
11
+ export const create_app = () => {
8
12
  const app = new App(
9
13
  {
10
14
  auth_admins_emails: ['admin@sc.com'],
@@ -24,13 +28,13 @@ export const create_app = async () => {
24
28
  )
25
29
  )
26
30
 
27
- return app.init()
31
+ return app.init().__show_me_everything.app;
28
32
  }
29
33
 
30
34
  async function test() {
31
- const app = await create_app();
35
+ const app = create_app();
32
36
 
33
- await migrateToLatest(app.db, false);
37
+ await migrateToLatest(app.__show_me_everything.db, false);
34
38
 
35
39
  Object.entries(api).slice(0, -1).forEach(
36
40
  ([name, runner]) => {
@@ -39,7 +43,7 @@ async function test() {
39
43
  );
40
44
 
41
45
  const last_test = Object.values(api).at(-1).create(app);
42
- last_test.after(async ()=>{app.db.disconnect()});
46
+ last_test.after(async ()=>{app.__show_me_everything.db.disconnect()});
43
47
  last_test.run();
44
48
  }
45
49
 
package/tests/sandbox.js CHANGED
@@ -16,21 +16,21 @@ export const create_app = async () => {
16
16
  )
17
17
  .withPlatform(new NodePlatform())
18
18
  .withDatabase(new MongoDB({ db_name: 'test'}))
19
+ .init();
19
20
 
20
- await app.init();
21
- await migrateToLatest(app.db, false);
21
+ await migrateToLatest(app.__show_me_everything.db, false);
22
22
  return app;
23
23
  }
24
24
 
25
25
 
26
26
  async function test() {
27
27
  const app = await create_app();
28
-
29
- const sf = await app.db.resources.storefronts.get_default_auto_generated_storefront()
28
+ const db = app.__show_me_everything.db;
29
+ const sf = await db.resources.storefronts.get_default_auto_generated_storefront()
30
30
 
31
31
  console.log(JSON.stringify(sf, null, 2));
32
32
 
33
- await app.db.disconnect();
33
+ await db.disconnect();
34
34
  }
35
35
 
36
36
  test();
@@ -15,7 +15,7 @@ mongo_vectorSearch_pipeline,
15
15
  * } from 'mongodb'
16
16
  * @import { ENV } from '@storecraft/core';
17
17
  */
18
-
18
+ import { truncate_or_pad_vector } from '@storecraft/core/ai/models/vector-stores/index.js';
19
19
  import { Collection } from 'mongodb';
20
20
  import { MongoClient, ServerApiVersion } from 'mongodb';
21
21
 
@@ -30,13 +30,12 @@ export const DEFAULT_INDEX_NAME = 'vector_store';
30
30
  /**
31
31
  * @description MongoDB Atlas Vector Store
32
32
  * {@link https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-type/#:~:text=You%20can%20use%20the%20vectorSearch,to%20pre%2Dfilter%20your%20data.}
33
- *
34
33
  * @implements {VectorStore}
35
34
  */
36
35
  export class MongoVectorStore {
37
36
 
38
37
  /** @satisfies {ENV<Config>} */
39
- static EnvConfig = /** @type{const} */ ({
38
+ static EnvConfig = /** @type {const} */ ({
40
39
  db_name: 'MONGODB_VECTOR_STORE_DB_NAME',
41
40
  url: 'MONGODB_VECTOR_STORE_URL'
42
41
  });
@@ -95,10 +94,10 @@ export class MongoVectorStore {
95
94
 
96
95
  /** @type {VectorStore["onInit"]} */
97
96
  onInit = (app) => {
98
- this.config.url ??= app.platform.env[MongoVectorStore.EnvConfig.url]
99
- ?? app.platform.env['MONGODB_URL'];
100
- this.config.db_name ??= app.platform.env[MongoVectorStore.EnvConfig.db_name]
101
- ?? app.platform.env['MONGODB_DB_NAME'] ?? 'main';
97
+ this.config.url ??= app.env[MongoVectorStore.EnvConfig.url]
98
+ ?? app.env['MONGODB_URL'];
99
+ this.config.db_name ??= app.env[MongoVectorStore.EnvConfig.db_name]
100
+ ?? app.env['MONGODB_DB_NAME'] ?? 'main';
102
101
  }
103
102
 
104
103
  /** @type {VectorStore["embedder"]} */
@@ -118,7 +117,9 @@ export class MongoVectorStore {
118
117
  (doc, ix) => (
119
118
  {
120
119
  updated_at: new Date().toISOString(),
121
- embedding: vectors[ix],
120
+ embedding: truncate_or_pad_vector(
121
+ vectors[ix], this.config.dimensions
122
+ ),
122
123
  metadata: doc.metadata,
123
124
  pageContent: doc.pageContent,
124
125
  [NAMESPACE_KEY]: doc.namespace,
@@ -165,6 +166,13 @@ export class MongoVectorStore {
165
166
  }
166
167
  );
167
168
 
169
+ if(!result) {
170
+ console.warn(
171
+ 'MongoVectoreStore::upsertDocuments() - no result from embedder'
172
+ );
173
+ return;
174
+ }
175
+
168
176
  const vectors = result.content;
169
177
 
170
178
  return this.upsertVectors(
@@ -248,15 +256,41 @@ export class MongoVectorStore {
248
256
  * @param {boolean} [delete_index_if_exists_before=false]
249
257
  * @returns {Promise<boolean>}
250
258
  */
251
- createVectorIndex = async (disconnect_after_finish=true, delete_index_if_exists_before=false) => {
259
+ createVectorIndex = async (
260
+ disconnect_after_finish=true,
261
+ delete_index_if_exists_before=false
262
+ ) => {
252
263
  if(delete_index_if_exists_before) {
253
264
  await this.deleteVectorIndex();
254
265
  }
255
-
266
+
256
267
  const db = this.client.db(this.config.db_name);
257
268
  const collection_name = this.config.index_name;
269
+
270
+ { // skip if index already exists
271
+ const indices = await db
272
+ .collection(collection_name)
273
+ .listSearchIndexes()
274
+ .toArray();
275
+
276
+ if(indices?.length) {
277
+ const index = indices.find(
278
+ (index) => index.name === this.config.index_name
279
+ );
280
+ if(index) {
281
+ console.log('MongoVectorStore::createVectorIndex - index already exists, skipping');
282
+ if(disconnect_after_finish)
283
+ await this.client.close();
284
+
285
+ return true;
286
+ }
287
+ }
288
+ }
289
+
258
290
  // collection name will have the same name as the index
259
291
  await db.createCollection(collection_name);
292
+
293
+
260
294
  const index_result = await db.collection(collection_name).createSearchIndex(
261
295
  {
262
296
  name: this.config.index_name,