@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,537 @@ | |
| 1 | 
            +
            import { Collection } from 'mongodb'
         | 
| 2 | 
            +
            import { MongoDB } from '../index.js'
         | 
| 3 | 
            +
            import { 
         | 
| 4 | 
            +
              count_regular, expand, get_bulk, get_regular, list_regular, 
         | 
| 5 | 
            +
              zeroed_relations
         | 
| 6 | 
            +
            } from './con.shared.js'
         | 
| 7 | 
            +
            import { 
         | 
| 8 | 
            +
              delete_keys, handle_or_id, sanitize_array, sanitize_one, to_objid 
         | 
| 9 | 
            +
            } from './utils.funcs.js'
         | 
| 10 | 
            +
            import { 
         | 
| 11 | 
            +
              add_search_terms_relation_on, create_explicit_relation, 
         | 
| 12 | 
            +
              delete_me, 
         | 
| 13 | 
            +
              remove_entry_from_all_connection_of_relation, 
         | 
| 14 | 
            +
              remove_specific_connection_of_relation, 
         | 
| 15 | 
            +
              remove_specific_connection_of_relation_with_filter, 
         | 
| 16 | 
            +
              save_me, 
         | 
| 17 | 
            +
              update_entry_on_all_connection_of_relation,
         | 
| 18 | 
            +
              update_specific_connection_of_relation,
         | 
| 19 | 
            +
              update_specific_connection_of_relation_with_filter
         | 
| 20 | 
            +
            } from './utils.relations.js'
         | 
| 21 | 
            +
            import { enums } from '@storecraft/core/v-api'
         | 
| 22 | 
            +
            import { report_document_media } from './con.images.js'
         | 
| 23 | 
            +
            import { union } from '@storecraft/core/v-api/utils.func.js'
         | 
| 24 | 
            +
            import { 
         | 
| 25 | 
            +
              test_product_filters_against_product 
         | 
| 26 | 
            +
            } from '@storecraft/core/v-api/con.pricing.logic.js'
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            /**
         | 
| 29 | 
            +
             * @typedef {import('@storecraft/core/v-database').db_products} db_col
         | 
| 30 | 
            +
             */
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            /**
         | 
| 33 | 
            +
             * @param {MongoDB} d 
         | 
| 34 | 
            +
             * 
         | 
| 35 | 
            +
             * 
         | 
| 36 | 
            +
             * @returns {Collection<
         | 
| 37 | 
            +
             *  import('./utils.relations.js').WithRelations<db_col["$type_get"]>
         | 
| 38 | 
            +
             * >}
         | 
| 39 | 
            +
             * 
         | 
| 40 | 
            +
             */
         | 
| 41 | 
            +
            const col = (d) => d.collection('products');
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            /**
         | 
| 44 | 
            +
             * @param {MongoDB} driver 
         | 
| 45 | 
            +
             * 
         | 
| 46 | 
            +
             * 
         | 
| 47 | 
            +
             * @returns {db_col["upsert"]}
         | 
| 48 | 
            +
             */
         | 
| 49 | 
            +
            const upsert = (driver) => {
         | 
| 50 | 
            +
              return async (data, search_terms=[]) => {
         | 
| 51 | 
            +
                // console.log('search_terms', search_terms)
         | 
| 52 | 
            +
                data = {...data};
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                const objid = to_objid(data.id);
         | 
| 55 | 
            +
                const session = driver.mongo_client.startSession();
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                try {
         | 
| 58 | 
            +
                  await session.withTransaction(
         | 
| 59 | 
            +
                    async () => {
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                      ////
         | 
| 62 | 
            +
                      // COLLECTIONS RELATION (explicit)
         | 
| 63 | 
            +
                      ////
         | 
| 64 | 
            +
                      let replacement = await create_explicit_relation(
         | 
| 65 | 
            +
                        driver, data, 'collections', 'collections', true
         | 
| 66 | 
            +
                      );
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                      //// 
         | 
| 69 | 
            +
                      // PRODUCTS -> Related Products RELATION (explicit)
         | 
| 70 | 
            +
                      ////
         | 
| 71 | 
            +
                      replacement = await create_explicit_relation(
         | 
| 72 | 
            +
                        driver, replacement, 'related_products', 'products', true
         | 
| 73 | 
            +
                      );
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                      ////
         | 
| 76 | 
            +
                      // DISCOUNTS RELATION
         | 
| 77 | 
            +
                      ////
         | 
| 78 | 
            +
                      // get all automatic + active discounts
         | 
| 79 | 
            +
                      const discounts = await driver.resources.discounts._col.find(
         | 
| 80 | 
            +
                        { 
         | 
| 81 | 
            +
                          'application.id': enums.DiscountApplicationEnum.Auto.id,
         | 
| 82 | 
            +
                          active: true
         | 
| 83 | 
            +
                        }, {
         | 
| 84 | 
            +
                          limit: 1000,
         | 
| 85 | 
            +
                          projection: zeroed_relations
         | 
| 86 | 
            +
                        }
         | 
| 87 | 
            +
                      ).toArray();
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                      // now test locally
         | 
| 90 | 
            +
                      const eligible_discounts = discounts.filter(
         | 
| 91 | 
            +
                        d => test_product_filters_against_product(
         | 
| 92 | 
            +
                          d.info.filters, data
         | 
| 93 | 
            +
                        )
         | 
| 94 | 
            +
                      );
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                      // console.log('eligible_discounts', eligible_discounts)
         | 
| 97 | 
            +
                      
         | 
| 98 | 
            +
                      // now replace discounts relation
         | 
| 99 | 
            +
                      replacement._relations = replacement._relations ?? {};
         | 
| 100 | 
            +
                      replacement._relations.discounts = {
         | 
| 101 | 
            +
                        ids: eligible_discounts.map(d => d._id),
         | 
| 102 | 
            +
                        entries: Object.fromEntries(
         | 
| 103 | 
            +
                          eligible_discounts.map(d => [d._id.toString(), d])
         | 
| 104 | 
            +
                        )
         | 
| 105 | 
            +
                      }
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                      // SEARCH
         | 
| 108 | 
            +
                      add_search_terms_relation_on(
         | 
| 109 | 
            +
                        replacement, union(
         | 
| 110 | 
            +
                          [
         | 
| 111 | 
            +
                            search_terms, 
         | 
| 112 | 
            +
                            eligible_discounts.map(d => `discount:${d.handle}`),
         | 
| 113 | 
            +
                            eligible_discounts.map(d => `discount:${d.id}`),
         | 
| 114 | 
            +
                          ]
         | 
| 115 | 
            +
                        )
         | 
| 116 | 
            +
                      );
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                      delete_keys(
         | 
| 119 | 
            +
                        'collections', 'variants', 'discounts', 'related_products', 'search'
         | 
| 120 | 
            +
                      )(replacement);
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                      // Now update other relations, that point to me
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                      //// 
         | 
| 125 | 
            +
                      // Related Products -> PRODUCTS RELATION (explicit)
         | 
| 126 | 
            +
                      ////
         | 
| 127 | 
            +
                      await update_entry_on_all_connection_of_relation(
         | 
| 128 | 
            +
                        driver, 'products', 'related_products', objid, replacement, session
         | 
| 129 | 
            +
                      );
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                      ////
         | 
| 132 | 
            +
                      // VARIANTS RELATION
         | 
| 133 | 
            +
                      ////
         | 
| 134 | 
            +
                      
         | 
| 135 | 
            +
                      if(`parent_handle` in data) { // is variant ?
         | 
| 136 | 
            +
                        // TODO: stronger validation of variant identification
         | 
| 137 | 
            +
                        
         | 
| 138 | 
            +
                        const isValid = (
         | 
| 139 | 
            +
                          data.parent_handle && data.parent_id && data.variant_hint
         | 
| 140 | 
            +
                        );
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                        if(isValid) {
         | 
| 143 | 
            +
                          // update parent product
         | 
| 144 | 
            +
                          await update_specific_connection_of_relation(
         | 
| 145 | 
            +
                            driver, 'products', 'variants', to_objid(data.parent_id),
         | 
| 146 | 
            +
                            objid, replacement, session
         | 
| 147 | 
            +
                          );
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                        }
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                      } else { // i am a parent
         | 
| 152 | 
            +
                        // let's fetch existing relations if any
         | 
| 153 | 
            +
                        const existing =  await await col(driver).findOne(
         | 
| 154 | 
            +
                          { _id: objid }
         | 
| 155 | 
            +
                        );
         | 
| 156 | 
            +
                        if(existing && existing?._relations?.variants) {
         | 
| 157 | 
            +
                          replacement._relations = replacement._relations ?? {};
         | 
| 158 | 
            +
                          replacement._relations.variants = existing._relations.variants; 
         | 
| 159 | 
            +
                        }
         | 
| 160 | 
            +
                      }
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                      ////
         | 
| 163 | 
            +
                      // STOREFRONTS -> PRODUCTS RELATION
         | 
| 164 | 
            +
                      ////
         | 
| 165 | 
            +
                      await update_entry_on_all_connection_of_relation(
         | 
| 166 | 
            +
                        driver, 'storefronts', 'products', objid, replacement, session
         | 
| 167 | 
            +
                      );
         | 
| 168 | 
            +
                      
         | 
| 169 | 
            +
                      ////
         | 
| 170 | 
            +
                      // REPORT IMAGES USAGE
         | 
| 171 | 
            +
                      ////
         | 
| 172 | 
            +
                      await report_document_media(driver)(replacement, session);
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                      // SAVE ME
         | 
| 175 | 
            +
                      await save_me(driver, 'products', objid, replacement, session);
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                    }
         | 
| 178 | 
            +
                  );
         | 
| 179 | 
            +
                } catch (e) {
         | 
| 180 | 
            +
                  console.log(e);
         | 
| 181 | 
            +
                  
         | 
| 182 | 
            +
                  return false;
         | 
| 183 | 
            +
                } finally {
         | 
| 184 | 
            +
                  await session.endSession();
         | 
| 185 | 
            +
                }
         | 
| 186 | 
            +
             | 
| 187 | 
            +
                return true;
         | 
| 188 | 
            +
              }
         | 
| 189 | 
            +
            }
         | 
| 190 | 
            +
             
         | 
| 191 | 
            +
             | 
| 192 | 
            +
            /**
         | 
| 193 | 
            +
             * 
         | 
| 194 | 
            +
             * @param {MongoDB} driver 
         | 
| 195 | 
            +
             */
         | 
| 196 | 
            +
            const get = (driver) => get_regular(driver, col(driver));
         | 
| 197 | 
            +
             | 
| 198 | 
            +
            /**
         | 
| 199 | 
            +
             * @param {MongoDB} driver 
         | 
| 200 | 
            +
             * 
         | 
| 201 | 
            +
             * 
         | 
| 202 | 
            +
             * @returns {db_col["remove"]}
         | 
| 203 | 
            +
             */
         | 
| 204 | 
            +
            const remove = (driver) => {
         | 
| 205 | 
            +
              return async (id) => {
         | 
| 206 | 
            +
                // todo: transaction
         | 
| 207 | 
            +
             | 
| 208 | 
            +
                const item = await col(driver).findOne(handle_or_id(id));
         | 
| 209 | 
            +
             | 
| 210 | 
            +
                if(!item) return;
         | 
| 211 | 
            +
                
         | 
| 212 | 
            +
                const objid = to_objid(item.id);
         | 
| 213 | 
            +
                const session = driver.mongo_client.startSession();
         | 
| 214 | 
            +
             | 
| 215 | 
            +
                try {
         | 
| 216 | 
            +
                  await session.withTransaction(
         | 
| 217 | 
            +
                    async () => {
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                      ////
         | 
| 220 | 
            +
                      // PRODUCTS -> VARIANTS RELATION
         | 
| 221 | 
            +
                      ////
         | 
| 222 | 
            +
                      const is_variant = `parent_handle` in item;
         | 
| 223 | 
            +
             | 
| 224 | 
            +
                      if(is_variant) {
         | 
| 225 | 
            +
                        // TODO: stronger validation
         | 
| 226 | 
            +
                        const isValid = (
         | 
| 227 | 
            +
                          item.parent_handle && item.parent_id && item.variant_hint
         | 
| 228 | 
            +
                        );
         | 
| 229 | 
            +
             | 
| 230 | 
            +
                        if(isValid) {
         | 
| 231 | 
            +
                          // remove me from parent
         | 
| 232 | 
            +
                          await remove_specific_connection_of_relation(
         | 
| 233 | 
            +
                            driver, 'products', 'variants', to_objid(item.parent_id),
         | 
| 234 | 
            +
                            objid, session
         | 
| 235 | 
            +
                          );
         | 
| 236 | 
            +
             | 
| 237 | 
            +
                        }
         | 
| 238 | 
            +
             | 
| 239 | 
            +
                      } else {
         | 
| 240 | 
            +
                        // I am a parent, let's delete all of the children variants
         | 
| 241 | 
            +
                        const ids = item?._relations?.variants?.ids;
         | 
| 242 | 
            +
                        if(ids) {
         | 
| 243 | 
            +
                          await driver.resources.products._col.deleteMany(
         | 
| 244 | 
            +
                            { _id: { $in: ids } },
         | 
| 245 | 
            +
                            { session }
         | 
| 246 | 
            +
                          );
         | 
| 247 | 
            +
                        }
         | 
| 248 | 
            +
                      }
         | 
| 249 | 
            +
             | 
| 250 | 
            +
                      ////
         | 
| 251 | 
            +
                      // STOREFRONTS --> PRODUCTS RELATION
         | 
| 252 | 
            +
                      ////
         | 
| 253 | 
            +
                      await remove_entry_from_all_connection_of_relation(
         | 
| 254 | 
            +
                        driver, 'storefronts', 'products', objid, session
         | 
| 255 | 
            +
                      );
         | 
| 256 | 
            +
             | 
| 257 | 
            +
                      ////
         | 
| 258 | 
            +
                      // PRODUCTS --> RELATED PRODUCTS RELATION
         | 
| 259 | 
            +
                      ////
         | 
| 260 | 
            +
                      await remove_entry_from_all_connection_of_relation(
         | 
| 261 | 
            +
                        driver, 'products', 'related_products', objid, session
         | 
| 262 | 
            +
                      );
         | 
| 263 | 
            +
             | 
| 264 | 
            +
                      // DELETE ME
         | 
| 265 | 
            +
                      await delete_me(
         | 
| 266 | 
            +
                        driver, 'products', objid, session
         | 
| 267 | 
            +
                      );
         | 
| 268 | 
            +
             | 
| 269 | 
            +
                    }
         | 
| 270 | 
            +
                  );
         | 
| 271 | 
            +
                } catch (e) {
         | 
| 272 | 
            +
                  console.log(e);
         | 
| 273 | 
            +
                  return false;
         | 
| 274 | 
            +
                } finally {
         | 
| 275 | 
            +
                  await session.endSession();
         | 
| 276 | 
            +
                }    
         | 
| 277 | 
            +
             | 
| 278 | 
            +
                return true;
         | 
| 279 | 
            +
              }
         | 
| 280 | 
            +
             | 
| 281 | 
            +
            }
         | 
| 282 | 
            +
             | 
| 283 | 
            +
            /**
         | 
| 284 | 
            +
             * @param {MongoDB} driver 
         | 
| 285 | 
            +
             */
         | 
| 286 | 
            +
            const list = (driver) => list_regular(driver, col(driver));
         | 
| 287 | 
            +
             | 
| 288 | 
            +
            /**
         | 
| 289 | 
            +
             * @param {MongoDB} driver 
         | 
| 290 | 
            +
             */
         | 
| 291 | 
            +
            const count = (driver) => count_regular(driver, col(driver));
         | 
| 292 | 
            +
             | 
| 293 | 
            +
             | 
| 294 | 
            +
            /**
         | 
| 295 | 
            +
             * For now and because each product is related to very few
         | 
| 296 | 
            +
             * collections, I will not expose the query api, and use aggregate
         | 
| 297 | 
            +
             * instead.
         | 
| 298 | 
            +
             * 
         | 
| 299 | 
            +
             * 
         | 
| 300 | 
            +
             * @param {MongoDB} driver 
         | 
| 301 | 
            +
             * 
         | 
| 302 | 
            +
             * 
         | 
| 303 | 
            +
             * @returns {db_col["list_product_collections"]}
         | 
| 304 | 
            +
             */
         | 
| 305 | 
            +
            const list_product_collections = (driver) => {
         | 
| 306 | 
            +
              return async (product) => {
         | 
| 307 | 
            +
                /** @type {import('@storecraft/core/v-database').RegularGetOptions} */
         | 
| 308 | 
            +
                const options = {
         | 
| 309 | 
            +
                  expand: ['collections']
         | 
| 310 | 
            +
                };
         | 
| 311 | 
            +
             | 
| 312 | 
            +
                // We have collections embedded in products, so let's use it
         | 
| 313 | 
            +
                const item = await get_regular(driver, col(driver))(product, options);
         | 
| 314 | 
            +
             | 
| 315 | 
            +
                return sanitize_array(item?.collections ?? []);
         | 
| 316 | 
            +
              }
         | 
| 317 | 
            +
            }
         | 
| 318 | 
            +
             | 
| 319 | 
            +
            /**
         | 
| 320 | 
            +
             * For now and because each product is related to very few
         | 
| 321 | 
            +
             * collections, I will not expose the query api, and use aggregate
         | 
| 322 | 
            +
             * instead.
         | 
| 323 | 
            +
             * 
         | 
| 324 | 
            +
             * 
         | 
| 325 | 
            +
             * @param {MongoDB} driver 
         | 
| 326 | 
            +
             * 
         | 
| 327 | 
            +
             * 
         | 
| 328 | 
            +
             * @returns {db_col["list_product_variants"]}
         | 
| 329 | 
            +
             */
         | 
| 330 | 
            +
            const list_product_variants = (driver) => {
         | 
| 331 | 
            +
              return async (product) => {
         | 
| 332 | 
            +
                /** @type {import('@storecraft/core/v-database').RegularGetOptions} */
         | 
| 333 | 
            +
                const options = {
         | 
| 334 | 
            +
                  expand: ['variants']
         | 
| 335 | 
            +
                };
         | 
| 336 | 
            +
             | 
| 337 | 
            +
                // We have collections embedded in products, so let's use it
         | 
| 338 | 
            +
                const item = await get_regular(driver, col(driver))(product, options);
         | 
| 339 | 
            +
             | 
| 340 | 
            +
                if(item && ('variants' in item)) {
         | 
| 341 | 
            +
                  
         | 
| 342 | 
            +
                  return sanitize_array(item?.variants ?? []);
         | 
| 343 | 
            +
                }
         | 
| 344 | 
            +
             | 
| 345 | 
            +
                return [];
         | 
| 346 | 
            +
              }
         | 
| 347 | 
            +
            }
         | 
| 348 | 
            +
             | 
| 349 | 
            +
            /**
         | 
| 350 | 
            +
             * For now and because each product is related to very few
         | 
| 351 | 
            +
             * collections, I will not expose the query api, and use aggregate
         | 
| 352 | 
            +
             * instead.
         | 
| 353 | 
            +
             * 
         | 
| 354 | 
            +
             * 
         | 
| 355 | 
            +
             * @param {MongoDB} driver 
         | 
| 356 | 
            +
             * 
         | 
| 357 | 
            +
             * 
         | 
| 358 | 
            +
             * @returns {db_col["list_related_products"]}
         | 
| 359 | 
            +
             */
         | 
| 360 | 
            +
            const list_related_products = (driver) => {
         | 
| 361 | 
            +
              return async (product) => {
         | 
| 362 | 
            +
                /** @type {import('@storecraft/core/v-database').RegularGetOptions} */
         | 
| 363 | 
            +
                const options = {
         | 
| 364 | 
            +
                  expand: ['related_products']
         | 
| 365 | 
            +
                };
         | 
| 366 | 
            +
             | 
| 367 | 
            +
                // We have collections embedded in products, so let's use it
         | 
| 368 | 
            +
                const item = await get_regular(driver, col(driver))(product, options);
         | 
| 369 | 
            +
             | 
| 370 | 
            +
                return sanitize_array(item?.related_products ?? []);
         | 
| 371 | 
            +
              }
         | 
| 372 | 
            +
            }
         | 
| 373 | 
            +
             | 
| 374 | 
            +
             | 
| 375 | 
            +
            /**
         | 
| 376 | 
            +
             * @param {MongoDB} driver 
         | 
| 377 | 
            +
             * 
         | 
| 378 | 
            +
             * 
         | 
| 379 | 
            +
             * @returns {db_col["list_product_discounts"]}
         | 
| 380 | 
            +
             */
         | 
| 381 | 
            +
            const list_product_discounts = (driver) => {
         | 
| 382 | 
            +
              return async (product) => {
         | 
| 383 | 
            +
                /** @type {import('@storecraft/core/v-database').RegularGetOptions} */
         | 
| 384 | 
            +
                const options = {
         | 
| 385 | 
            +
                  expand: ['discounts']
         | 
| 386 | 
            +
                };
         | 
| 387 | 
            +
             | 
| 388 | 
            +
                // We have collections embedded in products, so let's use it
         | 
| 389 | 
            +
                const item = await get_regular(driver, col(driver))(product, options);
         | 
| 390 | 
            +
             | 
| 391 | 
            +
                return sanitize_array(item?.discounts ?? []);
         | 
| 392 | 
            +
              }
         | 
| 393 | 
            +
            }
         | 
| 394 | 
            +
             | 
| 395 | 
            +
            /**
         | 
| 396 | 
            +
             * @param {MongoDB} driver 
         | 
| 397 | 
            +
             * 
         | 
| 398 | 
            +
             * 
         | 
| 399 | 
            +
             * @returns {db_col["add_product_to_collection"]}
         | 
| 400 | 
            +
             */
         | 
| 401 | 
            +
            const add_product_to_collection = (driver) => {
         | 
| 402 | 
            +
              return async (product_id_or_handle, collection_handle_or_id) => {
         | 
| 403 | 
            +
             | 
| 404 | 
            +
                const coll = await driver.resources.collections._col.findOne(
         | 
| 405 | 
            +
                  handle_or_id(collection_handle_or_id)
         | 
| 406 | 
            +
                );
         | 
| 407 | 
            +
             | 
| 408 | 
            +
                if(!coll)
         | 
| 409 | 
            +
                  return;
         | 
| 410 | 
            +
             | 
| 411 | 
            +
                const objid = to_objid(coll.id);
         | 
| 412 | 
            +
             | 
| 413 | 
            +
                await update_specific_connection_of_relation_with_filter(
         | 
| 414 | 
            +
                  driver, 'products', 'collections', 
         | 
| 415 | 
            +
                  handle_or_id(product_id_or_handle),
         | 
| 416 | 
            +
                  objid, coll, undefined, 
         | 
| 417 | 
            +
                  [
         | 
| 418 | 
            +
                    `col:${coll.handle}`, `col:${coll.id}`
         | 
| 419 | 
            +
                  ]
         | 
| 420 | 
            +
                );
         | 
| 421 | 
            +
             | 
| 422 | 
            +
              }
         | 
| 423 | 
            +
            }
         | 
| 424 | 
            +
             | 
| 425 | 
            +
             | 
| 426 | 
            +
            /**
         | 
| 427 | 
            +
             * @param {MongoDB} driver 
         | 
| 428 | 
            +
             * 
         | 
| 429 | 
            +
             * 
         | 
| 430 | 
            +
             * @returns {db_col["remove_product_from_collection"]}
         | 
| 431 | 
            +
             */
         | 
| 432 | 
            +
            const remove_product_from_collection = (driver) => {
         | 
| 433 | 
            +
              return async (product_id_or_handle, collection_handle_or_id) => {
         | 
| 434 | 
            +
             | 
| 435 | 
            +
                const coll = await driver.resources.collections._col.findOne(
         | 
| 436 | 
            +
                  handle_or_id(collection_handle_or_id)
         | 
| 437 | 
            +
                );
         | 
| 438 | 
            +
             | 
| 439 | 
            +
                if(!coll)
         | 
| 440 | 
            +
                  return;
         | 
| 441 | 
            +
             | 
| 442 | 
            +
                const objid = to_objid(coll.id);
         | 
| 443 | 
            +
             | 
| 444 | 
            +
                await remove_specific_connection_of_relation_with_filter(
         | 
| 445 | 
            +
                  driver, 'products', 'collections', 
         | 
| 446 | 
            +
                  handle_or_id(product_id_or_handle), 
         | 
| 447 | 
            +
                  objid, undefined, 
         | 
| 448 | 
            +
                  [
         | 
| 449 | 
            +
                    `col:${coll.handle}`, `col:${coll.id}`
         | 
| 450 | 
            +
                  ]
         | 
| 451 | 
            +
                );
         | 
| 452 | 
            +
              }
         | 
| 453 | 
            +
            }
         | 
| 454 | 
            +
             | 
| 455 | 
            +
            /**
         | 
| 456 | 
            +
             * @param {MongoDB} driver 
         | 
| 457 | 
            +
             * 
         | 
| 458 | 
            +
             * 
         | 
| 459 | 
            +
             * @returns {db_col["changeStockOfBy"]}
         | 
| 460 | 
            +
             */
         | 
| 461 | 
            +
            const changeStockOfBy = (driver) => {
         | 
| 462 | 
            +
              return async (product_ids_or_handles, deltas) => {
         | 
| 463 | 
            +
             | 
| 464 | 
            +
                /** 
         | 
| 465 | 
            +
                 * @type {import('mongodb').AnyBulkWriteOperation<
         | 
| 466 | 
            +
                 *  import('./utils.relations.js').WithRelations<
         | 
| 467 | 
            +
                 *    import('@storecraft/core/v-api').ProductType | 
         | 
| 468 | 
            +
                 *    import('@storecraft/core/v-api').VariantType
         | 
| 469 | 
            +
                 *  >
         | 
| 470 | 
            +
                 * >[]} 
         | 
| 471 | 
            +
                 */
         | 
| 472 | 
            +
                let ops = []
         | 
| 473 | 
            +
             | 
| 474 | 
            +
                product_ids_or_handles.forEach(
         | 
| 475 | 
            +
                  (id, ix) => {
         | 
| 476 | 
            +
                    ops.push(
         | 
| 477 | 
            +
                      {
         | 
| 478 | 
            +
                        updateOne: {
         | 
| 479 | 
            +
                          filter: handle_or_id(id),
         | 
| 480 | 
            +
                          update: {
         | 
| 481 | 
            +
                            $inc: { qty: Math.round(deltas[ix]) }
         | 
| 482 | 
            +
                          }
         | 
| 483 | 
            +
                        }
         | 
| 484 | 
            +
                      }
         | 
| 485 | 
            +
                    );
         | 
| 486 | 
            +
             | 
| 487 | 
            +
                    ops.push(
         | 
| 488 | 
            +
                      {
         | 
| 489 | 
            +
                        updateOne: {
         | 
| 490 | 
            +
                          filter: handle_or_id(id),
         | 
| 491 | 
            +
                          update: {
         | 
| 492 | 
            +
                            $max: { qty: 0 }
         | 
| 493 | 
            +
                          }
         | 
| 494 | 
            +
                        }
         | 
| 495 | 
            +
                      }
         | 
| 496 | 
            +
                    );
         | 
| 497 | 
            +
             | 
| 498 | 
            +
                  }
         | 
| 499 | 
            +
                );
         | 
| 500 | 
            +
             | 
| 501 | 
            +
                if(!ops.length)
         | 
| 502 | 
            +
                  return;
         | 
| 503 | 
            +
             | 
| 504 | 
            +
                await driver.resources.products._col.bulkWrite(
         | 
| 505 | 
            +
                  ops
         | 
| 506 | 
            +
                );
         | 
| 507 | 
            +
             | 
| 508 | 
            +
              }
         | 
| 509 | 
            +
            }
         | 
| 510 | 
            +
             | 
| 511 | 
            +
             | 
| 512 | 
            +
            /** 
         | 
| 513 | 
            +
             * @param {MongoDB} driver
         | 
| 514 | 
            +
             * 
         | 
| 515 | 
            +
             * 
         | 
| 516 | 
            +
             * @return {db_col & { _col: ReturnType<col> }}
         | 
| 517 | 
            +
             */
         | 
| 518 | 
            +
            export const impl = (driver) => {
         | 
| 519 | 
            +
             | 
| 520 | 
            +
              return {
         | 
| 521 | 
            +
                _col: col(driver),
         | 
| 522 | 
            +
                changeStockOfBy: changeStockOfBy(driver), 
         | 
| 523 | 
            +
                get: get(driver),
         | 
| 524 | 
            +
                getBulk: get_bulk(driver, col(driver)),
         | 
| 525 | 
            +
                upsert: upsert(driver),
         | 
| 526 | 
            +
                remove: remove(driver),
         | 
| 527 | 
            +
                list: list(driver),
         | 
| 528 | 
            +
                add_product_to_collection: add_product_to_collection(driver),
         | 
| 529 | 
            +
                remove_product_from_collection: remove_product_from_collection(driver),
         | 
| 530 | 
            +
                list_product_collections: list_product_collections(driver),
         | 
| 531 | 
            +
                list_product_variants: list_product_variants(driver),
         | 
| 532 | 
            +
                list_related_products: list_related_products(driver),
         | 
| 533 | 
            +
                list_product_discounts: list_product_discounts(driver),
         | 
| 534 | 
            +
                count: count(driver)
         | 
| 535 | 
            +
              }
         | 
| 536 | 
            +
            }
         | 
| 537 | 
            +
             
         | 
| @@ -0,0 +1,162 @@ | |
| 1 | 
            +
            import { MongoDB } from '../index.js'
         | 
| 2 | 
            +
            import { query_to_mongo } from './utils.query.js';
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            /**
         | 
| 5 | 
            +
             * @typedef {import('@storecraft/core/v-database').search} db_col
         | 
| 6 | 
            +
             */
         | 
| 7 | 
            +
             | 
| 8 | 
            +
             | 
| 9 | 
            +
             | 
| 10 | 
            +
            /**
         | 
| 11 | 
            +
             * @type {(keyof import('@storecraft/core/v-database').db_driver["resources"])[]}
         | 
| 12 | 
            +
             */
         | 
| 13 | 
            +
            const tables = [
         | 
| 14 | 
            +
              'tags',
         | 
| 15 | 
            +
              'collections',
         | 
| 16 | 
            +
              'customers',
         | 
| 17 | 
            +
              'products',
         | 
| 18 | 
            +
              'storefronts',
         | 
| 19 | 
            +
              'images',
         | 
| 20 | 
            +
              'posts',
         | 
| 21 | 
            +
              'shipping_methods',
         | 
| 22 | 
            +
              'notifications',
         | 
| 23 | 
            +
              'discounts',
         | 
| 24 | 
            +
              'orders',
         | 
| 25 | 
            +
              'templates'
         | 
| 26 | 
            +
            ]
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            /**
         | 
| 29 | 
            +
             * @type {Record<string, keyof import('@storecraft/core/v-database').db_driver["resources"]>}
         | 
| 30 | 
            +
             */
         | 
| 31 | 
            +
            const prefix_to_resource = {
         | 
| 32 | 
            +
              'au': 'auth_users',
         | 
| 33 | 
            +
              'col': 'collections',
         | 
| 34 | 
            +
              'cus': 'customers',
         | 
| 35 | 
            +
              'dis': 'discounts',
         | 
| 36 | 
            +
              'img': 'images',
         | 
| 37 | 
            +
              'not': 'notifications',
         | 
| 38 | 
            +
              'order': 'orders',
         | 
| 39 | 
            +
              'pr': 'products',
         | 
| 40 | 
            +
              'ship': 'shipping_methods',
         | 
| 41 | 
            +
              'sf': 'storefronts',
         | 
| 42 | 
            +
              'tag': 'tags',
         | 
| 43 | 
            +
              'template': 'templates',
         | 
| 44 | 
            +
              'post': 'posts',
         | 
| 45 | 
            +
              
         | 
| 46 | 
            +
            }
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            /**
         | 
| 49 | 
            +
             * 
         | 
| 50 | 
            +
             * @param {string} id 
         | 
| 51 | 
            +
             * 
         | 
| 52 | 
            +
             * @returns {keyof import('@storecraft/core/v-database').db_driver["resources"]}
         | 
| 53 | 
            +
             */
         | 
| 54 | 
            +
            export const id_to_resource = id => {
         | 
| 55 | 
            +
              let result = undefined;
         | 
| 56 | 
            +
              try {
         | 
| 57 | 
            +
                const prefix = id.split('_').at(0);
         | 
| 58 | 
            +
                result = prefix_to_resource[prefix];
         | 
| 59 | 
            +
              } catch(e) {
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              } finally {
         | 
| 62 | 
            +
                return result;
         | 
| 63 | 
            +
              }
         | 
| 64 | 
            +
            }
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            /**
         | 
| 67 | 
            +
             * @param {MongoDB} driver 
         | 
| 68 | 
            +
             * 
         | 
| 69 | 
            +
             * 
         | 
| 70 | 
            +
             * @returns {db_col["quicksearch"]}
         | 
| 71 | 
            +
             */
         | 
| 72 | 
            +
            export const quicksearch = (driver) => {
         | 
| 73 | 
            +
              return async (query) => {
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                const { filter, sort, reverse_sign } = query_to_mongo(query);
         | 
| 76 | 
            +
                const expand = query.expand ?? ['*']; 
         | 
| 77 | 
            +
                const tables_filtered = tables.filter(t => expand.includes('*') || expand.includes(t));
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                if(tables_filtered.length==0)
         | 
| 80 | 
            +
                    return {};
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                const pipeline = [
         | 
| 83 | 
            +
                  {
         | 
| 84 | 
            +
                    "$match": filter
         | 
| 85 | 
            +
                  },
         | 
| 86 | 
            +
                  {
         | 
| 87 | 
            +
                    $sort: sort
         | 
| 88 | 
            +
                  },
         | 
| 89 | 
            +
                  {
         | 
| 90 | 
            +
                    $limit: query.limit ?? 5
         | 
| 91 | 
            +
                  },
         | 
| 92 | 
            +
                  {
         | 
| 93 | 
            +
                    $project: { 
         | 
| 94 | 
            +
                      title: 1,
         | 
| 95 | 
            +
                      handle: 1,
         | 
| 96 | 
            +
                      // '_relations.search': 1,
         | 
| 97 | 
            +
                      id: 1,
         | 
| 98 | 
            +
                      _id: 0
         | 
| 99 | 
            +
                    }
         | 
| 100 | 
            +
                  }
         | 
| 101 | 
            +
                ];
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                const db = driver.mongo_client.db();
         | 
| 104 | 
            +
                
         | 
| 105 | 
            +
                /** @type {import('@storecraft/core/v-api').QuickSearchResource[]} */ 
         | 
| 106 | 
            +
                const items = await db.collection(tables_filtered[0]).aggregate(
         | 
| 107 | 
            +
                  [
         | 
| 108 | 
            +
                    ...pipeline,
         | 
| 109 | 
            +
                    ...tables_filtered.slice(1).map(
         | 
| 110 | 
            +
                      t => (
         | 
| 111 | 
            +
                        {
         | 
| 112 | 
            +
                          $unionWith: {
         | 
| 113 | 
            +
                            coll: t,
         | 
| 114 | 
            +
                            pipeline: pipeline
         | 
| 115 | 
            +
                          }
         | 
| 116 | 
            +
                        }
         | 
| 117 | 
            +
                      )
         | 
| 118 | 
            +
                    )
         | 
| 119 | 
            +
                  ], 
         | 
| 120 | 
            +
                  {
         | 
| 121 | 
            +
                    
         | 
| 122 | 
            +
                  }
         | 
| 123 | 
            +
                ).toArray();
         | 
| 124 | 
            +
             | 
| 125 | 
            +
             | 
| 126 | 
            +
                /** @type {import('@storecraft/core/v-api').QuickSearchResult} */
         | 
| 127 | 
            +
                const result = {};
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                items.reduce(
         | 
| 130 | 
            +
                  (p, c) => {
         | 
| 131 | 
            +
                    const resource = id_to_resource(c.id);
         | 
| 132 | 
            +
                    const resource_meta = p[resource];
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                    if(resource_meta) {
         | 
| 135 | 
            +
                      resource_meta.push(c);
         | 
| 136 | 
            +
                    } else {
         | 
| 137 | 
            +
                      p[resource] = [
         | 
| 138 | 
            +
                        c
         | 
| 139 | 
            +
                      ]
         | 
| 140 | 
            +
                    }
         | 
| 141 | 
            +
                    
         | 
| 142 | 
            +
                    return p;
         | 
| 143 | 
            +
                  }, 
         | 
| 144 | 
            +
                  result
         | 
| 145 | 
            +
                );
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                return result;
         | 
| 148 | 
            +
              }
         | 
| 149 | 
            +
            }
         | 
| 150 | 
            +
             | 
| 151 | 
            +
            /** 
         | 
| 152 | 
            +
             * @param {MongoDB} driver
         | 
| 153 | 
            +
             * 
         | 
| 154 | 
            +
             * 
         | 
| 155 | 
            +
             * @return {db_col}
         | 
| 156 | 
            +
             * */
         | 
| 157 | 
            +
            export const impl = (driver) => {
         | 
| 158 | 
            +
             | 
| 159 | 
            +
              return {
         | 
| 160 | 
            +
                quicksearch: quicksearch(driver)
         | 
| 161 | 
            +
              }
         | 
| 162 | 
            +
            }
         |