@storecraft/database-mongodb 1.0.14 → 1.0.16

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 CHANGED
@@ -8,7 +8,7 @@
8
8
  [![MongoDB](https://github.com/store-craft/storecraft/actions/workflows/test.database-mongodb.yml/badge.svg)](https://github.com/store-craft/storecraft/actions/workflows/test.database-mongodb.yml)
9
9
 
10
10
  Official `mongodb` driver for `StoreCraft` on **Node.js** / **Deno** / **Bun** platforms.
11
- Also, an example of [Semantic Vector Search](https://www.mongodb.com/developer/products/atlas/semantic-search-mongodb-atlas-vector-search/) extension at `@storecraft/database-mongodb/vector-search-extension`
11
+ Also, official support for mongo-db as vector store.
12
12
 
13
13
  ```bash
14
14
  npm i @storecraft/database-mongodb
@@ -24,23 +24,19 @@ import { NodePlatform } from '@storecraft/core/platform/node';
24
24
  import { MongoDB } from '@storecraft/database-mongodb'
25
25
  import { migrateToLatest } from '@storecraft/database-mongodb/migrate.js'
26
26
  import { NodeLocalStorage } from '@storecraft/core/storage/node'
27
- import { MongoVectorSearch } from '@storecraft/database-mongodb/vector-search-extension'
27
+ import { MongoVectorStore } from '@storecraft/database-mongodb/vector-store'
28
28
 
29
- const app = new App(
30
- {
31
- auth_admins_emails: ['admin@sc.com'],
32
- auth_secret_access_token: 'auth_secret_access_token',
33
- auth_secret_refresh_token: 'auth_secret_refresh_token'
34
- }
35
- )
29
+ const app = new App()
36
30
  .withPlatform(new NodePlatform())
37
- .withDatabase(new MongoDB({ db_name: 'test', url: '...', options: {}}))
38
- .withExtensions(
39
- 'mongo-vector-search': new MongoVectorSearch({ openai_key: process.env.OPENAI })
31
+ .withDatabase(new MongoDB({}))
32
+ .withVectorStore(
33
+ new MongoVectorStore({ embedder: new OpenAIEmbedder() })
40
34
  )
41
35
 
42
36
  await app.init();
43
37
  await migrateToLatest(app.db, false);
38
+ // cerate if not exists
39
+ await app.vectorstore.createVectorIndex(false, false);
44
40
 
45
41
  const server = http.createServer(app.handler).listen(
46
42
  8000,
@@ -51,60 +47,29 @@ const server = http.createServer(app.handler).listen(
51
47
 
52
48
  ```
53
49
 
54
- ## (Recommended) setup semantic/ai vector search extension for products
55
-
56
- 1. in [Atlas](https://cloud.mongodb.com/) dashboard, create a vector index (call it `vector_index`) for `products` collection:
57
-
58
- ```json
59
- {
60
- "fields": [
61
- {
62
- "numDimensions": 1536,
63
- "path": "embedding",
64
- "similarity": "cosine",
65
- "type": "vector"
66
- }
67
- ]
68
- }
50
+ ## Testing Locally (I recommend to use `Atlas`)
51
+
52
+ 1. First start a `mongo-db` server
53
+ First, make sure you have `docker` installed,
54
+ Then, run
55
+
56
+ ```bash
57
+ npm run database-mongodb:docker-compose-up
69
58
  ```
70
59
 
71
- 2. Now, every upserted product will be eligible for semantic search by it's title + description.
72
- 3. The extension is publicly available via HTTP (`POST` request)
73
- ```js
74
- await fetch(
75
- 'http://localhost:8000/api/extensions/mongo-vector-search/search',
76
- {
77
- method: 'POST',
78
- body: JSON.stringify(
79
- {
80
- query: 'I am interested in Nintendo related clothing, such as shirts',
81
- limit: 1
82
- }
83
- )
84
- }
85
- )
60
+ 2. create Environment
61
+
62
+ create `.env` file with
63
+
64
+ ```bash
65
+ MONGODB_URL="mongodb://127.0.0.1:27017/?replicaSet=rs0"
66
+ MONGODB_NAME="main"
86
67
  ```
87
68
 
88
- returns `ProductType[]` array
89
-
90
- ```json
91
- [
92
- {
93
- "title": "Super Mario T Shirt",
94
- "handle": "super-mario-t-shirt",
95
- "description": "This Super mario shirt is XL size and
96
- features a colorful print of Lugi and Mario.",
97
- "media": [
98
- "storage://images/super-mario-shirt_1738686680944_w_819_h_460.jpeg"
99
- ],
100
- "price": 100,
101
- "qty": 1,
102
- "active": true,
103
- "id": "pr_67a240e4000000d34bcf0743",
104
- "created_at": "2025-02-04T16:31:32.909Z",
105
- "updated_at": "2025-02-04T16:58:25.286Z"
106
- }
107
- ]
69
+ 3. Run `tests/runner.test.js`
70
+
71
+ ```bash
72
+ npm run database-mongodb:test
108
73
  ```
109
74
 
110
75
 
@@ -1,7 +1,10 @@
1
1
  /**
2
+ * an older demo for extending storecraft with vector search.
3
+ * DO NOT USE this. Use the our official `MongoVectorSearch` insteas
4
+ *
2
5
  * @import {extension} from '@storecraft/core/extensions'
3
6
  */
4
- import { MongoDB } from './index.js';
7
+ import { MongoDB } from '../index.js';
5
8
  /**
6
9
  *
7
10
  * @typedef {object} Config
package/index.js CHANGED
@@ -68,6 +68,7 @@ export class MongoDB {
68
68
  constructor(config) {
69
69
  this.#is_ready = false;
70
70
  this.#config = {
71
+ db_name: 'main',
71
72
  options: {
72
73
  ignoreUndefined: true,
73
74
  serverApi: {
@@ -161,6 +162,10 @@ export class MongoDB {
161
162
  return this.#mongo_client;
162
163
  }
163
164
 
165
+ get db() {
166
+ return this.mongo_client.db(this.name);
167
+ }
168
+
164
169
  /**
165
170
  *
166
171
  * @description Get the config object
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storecraft/database-mongodb",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "Storecraft database driver for mongodb on node / bun / deno platform",
5
5
  "license": "MIT",
6
6
  "author": "Tomer Shalev (https://github.com/store-craft)",
@@ -33,6 +33,7 @@
33
33
  }
34
34
  },
35
35
  "scripts": {
36
+ "database-mongodb:docker-compose-up": "docker compose -f ./tests/docker-compose.yml up -d ",
36
37
  "database-mongodb:test": "node ./tests/runner.test.js",
37
38
  "test": "npm run database-mongodb:test",
38
39
  "prepublishOnly": "npm version patch --force",
@@ -6,6 +6,7 @@
6
6
  import { MongoDB } from '../index.js'
7
7
  import { Collection } from 'mongodb'
8
8
  import {
9
+ handle_or_id,
9
10
  objid_or_else_filter, sanitize_one, to_objid_safe
10
11
  } from './utils.funcs.js'
11
12
  import {
@@ -62,32 +63,37 @@ const getByEmail = (driver) => {
62
63
 
63
64
  /**
64
65
  * @param {MongoDB} driver
65
- *
66
- *
67
66
  * @returns {db_col["remove"]}
68
67
  */
69
68
  const remove = (driver) => {
70
- return async (id) => {
69
+ return async (id_or_handle) => {
70
+
71
+ const session = driver.mongo_client.startSession();
72
+ try {
73
+ await session.withTransaction(
74
+ async () => {
75
+ const filter = handle_or_id(id_or_handle);
76
+ await col(driver).deleteOne(
77
+ filter,
78
+ { session }
79
+ );
80
+ // customer and auth_user have the same object-id and handle
81
+ // so we can use the same filter
82
+ await driver.resources.customers._col.deleteOne(
83
+ filter,
84
+ { session }
85
+ );
71
86
 
72
- let filter;
73
-
74
- {
75
- if(to_objid_safe(id)) {
76
- filter = {
77
- _id: to_objid_safe(id)
78
- }
79
- } else {
80
- filter = {
81
- email: id
82
87
  }
83
- }
88
+ );
89
+ } catch(e) {
90
+ console.log(e);
91
+ return false;
92
+ } finally {
93
+ await session.endSession();
84
94
  }
85
95
 
86
- const res = await col(driver).deleteOne(
87
- filter
88
- );
89
-
90
- return Boolean(res.deletedCount)
96
+ return true;
91
97
  }
92
98
  }
93
99
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @import { db_collections as db_col } from '@storecraft/core/database'
3
3
  * @import { ProductType, VariantType } from '@storecraft/core/api'
4
- * @import { WithRelations } from './utils.relations.js'
4
+ * @import { WithRelations } from './utils.types.js'
5
5
  * @import { Filter } from 'mongodb'
6
6
  */
7
7
 
@@ -27,8 +27,6 @@ const transactionOptions = {
27
27
 
28
28
  /**
29
29
  * @param {MongoDB} d
30
- *
31
- *
32
30
  * @returns {Collection<WithRelations<db_col["$type_get"]>>}
33
31
  */
34
32
  const col = (d) => d.collection('collections');
@@ -169,8 +167,6 @@ const count = (driver) => count_regular(driver, col(driver));
169
167
 
170
168
  /**
171
169
  * @param {MongoDB} driver
172
- *
173
- *
174
170
  * @returns {db_col["list_collection_products"]}
175
171
  */
176
172
  const list_collection_products = (driver) => {
@@ -213,6 +209,72 @@ const list_collection_products = (driver) => {
213
209
  }
214
210
  }
215
211
 
212
+
213
+ /**
214
+ * @param {MongoDB} driver
215
+ * @returns {db_col["count_collection_products"]}
216
+ */
217
+ const count_collection_products = (driver) => {
218
+ return async (handle_or_id, query) => {
219
+
220
+ const {
221
+ filter: filter_query, sort, reverse_sign
222
+ } = query_to_mongo(query);
223
+
224
+ /**
225
+ * @type {Filter<WithRelations<ProductType | VariantType>>
226
+ * }
227
+ */
228
+ const filter = {
229
+ $and: [
230
+ { '_relations.search': `col:${handle_or_id}` },
231
+ ]
232
+ };
233
+
234
+ // add the query filter
235
+ isDef(filter_query) && filter.$and.push(filter_query);
236
+
237
+ const count = await driver.resources.products._col.countDocuments(
238
+ filter
239
+ );
240
+
241
+ return count;
242
+ }
243
+ }
244
+
245
+
246
+
247
+ /**
248
+ * @param {MongoDB} driver
249
+ * @returns {db_col["list_used_products_tags"]}
250
+ */
251
+ const list_used_products_tags = (driver) => {
252
+ return async (handle_or_id) => {
253
+ const items = await driver.resources.products._col.find(
254
+ {
255
+ '_relations.search': `col:${handle_or_id}`
256
+ },
257
+ {
258
+ projection: {
259
+ tags: 1
260
+ }
261
+ }
262
+ ).toArray();
263
+
264
+ const set = (items ?? []).reduce(
265
+ (p, c) => {
266
+ c.tags.forEach(
267
+ (tag) => p.add(tag)
268
+ );
269
+ return p;
270
+ }, new Set()
271
+ )
272
+ // return array from set
273
+ return Array.from(set);
274
+ }
275
+ }
276
+
277
+
216
278
  /**
217
279
  * @param {MongoDB} driver
218
280
  *
@@ -228,6 +290,8 @@ export const impl = (driver) => {
228
290
  remove: remove(driver),
229
291
  list: list(driver),
230
292
  count: count(driver),
231
- list_collection_products: list_collection_products(driver)
293
+ list_collection_products: list_collection_products(driver),
294
+ count_collection_products: count_collection_products(driver),
295
+ list_used_products_tags: list_used_products_tags(driver),
232
296
  }
233
297
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @import { db_customers as db_col } from '@storecraft/core/database'
3
3
  * @import { OrderData } from '@storecraft/core/api'
4
- * @import { WithRelations } from './utils.relations.js'
4
+ * @import { WithRelations } from './utils.types.js'
5
5
  * @import { Filter } from 'mongodb'
6
6
  */
7
7
 
@@ -9,7 +9,7 @@ import { Collection, ObjectId } from 'mongodb'
9
9
  import { MongoDB } from '../index.js'
10
10
  import { count_regular, get_regular, list_regular,
11
11
  upsert_regular } from './con.shared.js'
12
- import { isDef, sanitize_array, to_objid } from './utils.funcs.js'
12
+ import { handle_or_id, isDef, sanitize_array, to_objid } from './utils.funcs.js'
13
13
  import { query_to_mongo } from './utils.query.js';
14
14
 
15
15
  /**
@@ -62,8 +62,6 @@ export const email_or_id = (email_or_id) => {
62
62
 
63
63
  /**
64
64
  * @param {MongoDB} driver
65
- *
66
- *
67
65
  * @returns {db_col["remove"]}
68
66
  */
69
67
  const remove = (driver) => {
@@ -74,7 +72,7 @@ const remove = (driver) => {
74
72
  await session.withTransaction(
75
73
  async () => {
76
74
  const res = await col(driver).findOneAndDelete(
77
- email_or_id(id),
75
+ handle_or_id(id),
78
76
  { session }
79
77
  );
80
78
 
@@ -120,10 +118,10 @@ const list_customer_orders = (driver) => {
120
118
 
121
119
  const { filter: filter_query, sort, reverse_sign } = query_to_mongo(query);
122
120
 
123
- console.log('query', query)
124
- console.log('filter', JSON.stringify(filter_query, null, 2))
125
- console.log('sort', sort)
126
- console.log('expand', query?.expand)
121
+ // console.log('query', query)
122
+ // console.log('filter', JSON.stringify(filter_query, null, 2))
123
+ // console.log('sort', sort)
124
+ // console.log('expand', query?.expand)
127
125
 
128
126
  /** @type {Filter<WithRelations<OrderData>>} */
129
127
  const filter = {
@@ -147,6 +145,34 @@ const list_customer_orders = (driver) => {
147
145
  }
148
146
  }
149
147
 
148
+ /**
149
+ * @param {MongoDB} driver
150
+ * @returns {db_col["count_customer_orders"]}
151
+ */
152
+ const count_customer_orders = (driver) => {
153
+ return async (customer_id, query) => {
154
+
155
+ const { filter: filter_query } = query_to_mongo(query);
156
+
157
+ /** @type {Filter<WithRelations<OrderData>>} */
158
+ const filter = {
159
+ $and: [
160
+ {'_relations.search': `customer:${customer_id}` },
161
+ ]
162
+ };
163
+
164
+ // add the query filter
165
+ isDef(filter_query) && filter.$and.push(filter_query);
166
+
167
+ const count = await driver.resources.orders._col.countDocuments(
168
+ filter
169
+ );
170
+
171
+ return count;
172
+ }
173
+ }
174
+
175
+
150
176
  /**
151
177
  * @param {MongoDB} driver
152
178
  *
@@ -163,6 +189,7 @@ export const impl = (driver) => {
163
189
  remove: remove(driver),
164
190
  list: list(driver),
165
191
  count: count(driver),
166
- list_customer_orders: list_customer_orders(driver)
192
+ list_customer_orders: list_customer_orders(driver),
193
+ count_customer_orders: count_customer_orders(driver),
167
194
  }
168
195
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @import { db_discounts as db_col } from '@storecraft/core/database'
3
3
  * @import { ProductType, VariantType } from '@storecraft/core/api'
4
- * @import { WithRelations } from './utils.relations.js'
4
+ * @import { WithRelations } from './utils.types.js'
5
5
  * @import { Filter } from 'mongodb'
6
6
  */
7
7
 
@@ -23,7 +23,10 @@ import {
23
23
  save_me,
24
24
  update_entry_on_all_connection_of_relation
25
25
  } from './utils.relations.js'
26
-
26
+ import {
27
+ helper_compute_product_extra_search_keywords_because_of_discount_side_effect_for_db,
28
+ helper_compute_product_extra_tags_because_of_discount_side_effect_for_db
29
+ } from '@storecraft/core/database'
27
30
 
28
31
  /**
29
32
  * @param {MongoDB} d
@@ -54,16 +57,27 @@ const upsert = (driver) => {
54
57
  // SEARCH
55
58
  add_search_terms_relation_on(data, search_terms);
56
59
 
57
-
58
60
  ////
59
61
  // PRODUCT --> DISCOUNTS RELATION
60
62
  ////
63
+ const extra_search_for_products =
64
+ helper_compute_product_extra_search_keywords_because_of_discount_side_effect_for_db(
65
+ data
66
+ );
67
+
68
+ const extra_tags_for_products =
69
+ helper_compute_product_extra_tags_because_of_discount_side_effect_for_db(
70
+ data
71
+ );
61
72
 
62
73
  // first remove discount from anywhere
63
74
  await remove_entry_from_all_connection_of_relation(
64
75
  driver, 'products', 'discounts', objid, session,
65
- [
66
- `discount:${data.handle}`, `discount:${data.id}`
76
+ [ // remove search terms
77
+ ...extra_search_for_products
78
+ ],
79
+ [ // remove tags
80
+ ...extra_tags_for_products
67
81
  ]
68
82
  );
69
83
 
@@ -77,8 +91,11 @@ const upsert = (driver) => {
77
91
  $addToSet: {
78
92
  '_relations.discounts.ids': objid,
79
93
  '_relations.search': {
80
- $each : [ `discount:${data.handle}`, `discount:${data.id}` ]
81
- }
94
+ $each : extra_search_for_products
95
+ },
96
+ 'tags': {
97
+ $each : extra_tags_for_products
98
+ }
82
99
  },
83
100
 
84
101
  },
@@ -145,13 +162,27 @@ const remove = (driver) => {
145
162
  try {
146
163
  await session.withTransaction(
147
164
  async () => {
165
+
166
+ const extra_search_for_products =
167
+ helper_compute_product_extra_search_keywords_because_of_discount_side_effect_for_db(
168
+ item
169
+ );
170
+
171
+ const extra_tags_for_products =
172
+ helper_compute_product_extra_tags_because_of_discount_side_effect_for_db(
173
+ item
174
+ );
175
+
148
176
  ////
149
177
  // PRODUCT -> DISCOUNTS RELATION
150
178
  ////
151
179
  await remove_entry_from_all_connection_of_relation(
152
180
  driver, 'products', 'discounts', objid, session,
153
181
  [
154
- `discount:${item.handle}`, `discount:${item.id}`
182
+ ...extra_search_for_products
183
+ ],
184
+ [
185
+ ...extra_tags_for_products
155
186
  ]
156
187
  );
157
188
 
@@ -196,8 +227,6 @@ const count = (driver) => count_regular(driver, col(driver));
196
227
 
197
228
  /**
198
229
  * @param {MongoDB} driver
199
- *
200
- *
201
230
  * @returns {db_col["list_discount_products"]}
202
231
  */
203
232
  const list_discount_products = (driver) => {
@@ -235,6 +264,67 @@ const list_discount_products = (driver) => {
235
264
  }
236
265
  }
237
266
 
267
+
268
+ /**
269
+ * @param {MongoDB} driver
270
+ * @returns {db_col["list_all_discount_products_tags"]}
271
+ */
272
+ const list_all_discount_products_tags = (driver) => {
273
+ return async (handle_or_id) => {
274
+ const items = await driver.resources.products._col.find(
275
+ {
276
+ '_relations.search': `discount:${handle_or_id}`
277
+ },
278
+ {
279
+ projection: {
280
+ tags: 1
281
+ }
282
+ }
283
+ ).toArray();
284
+
285
+ const set = (items ?? []).reduce(
286
+ (p, c) => {
287
+ c.tags.forEach(
288
+ (tag) => p.add(tag)
289
+ );
290
+ return p;
291
+ }, new Set()
292
+ );
293
+
294
+ // return array from set
295
+ return Array.from(set);
296
+ }
297
+ }
298
+
299
+
300
+ /**
301
+ * @param {MongoDB} driver
302
+ * @returns {db_col["count_discount_products"]}
303
+ */
304
+ const count_discount_products = (driver) => {
305
+ return async (handle_or_id, query) => {
306
+
307
+ const { filter: filter_query, sort, reverse_sign } = query_to_mongo(query);
308
+
309
+ /** @type {Filter<WithRelations<ProductType | VariantType>>} */
310
+ const filter = {
311
+ $and: [
312
+ { '_relations.search': `discount:${handle_or_id}` },
313
+ ]
314
+ };
315
+
316
+ // add the query filter
317
+ isDef(filter_query) && filter.$and.push(filter_query);
318
+
319
+ const count = await driver.resources.products._col.countDocuments(
320
+ filter
321
+ );
322
+
323
+ return count;
324
+ }
325
+ }
326
+
327
+
238
328
  /**
239
329
  * @param {MongoDB} driver
240
330
  *
@@ -251,6 +341,8 @@ export const impl = (driver) => {
251
341
  remove: remove(driver),
252
342
  list: list(driver),
253
343
  count: count(driver),
254
- list_discount_products: list_discount_products(driver)
344
+ list_discount_products: list_discount_products(driver),
345
+ list_all_discount_products_tags: list_all_discount_products_tags(driver),
346
+ count_discount_products: count_discount_products(driver)
255
347
  }
256
348
  }
package/src/con.posts.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @import { db_posts as db_col } from '@storecraft/core/database'
3
- * @import { WithRelations } from './utils.relations.js'
3
+ * @import { WithRelations } from './utils.types.js'
4
4
  */
5
5
 
6
6
  import { Collection } from 'mongodb'