├── .github ├── renovate.json └── workflows │ └── ci-modus-build.yml ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── backend ├── .gitignore ├── asconfig.json ├── assembly │ ├── crud.ts │ ├── index.ts │ ├── search.ts │ ├── tsconfig.json │ └── types.ts ├── eslint.config.js ├── extras │ ├── ecommerce_populate.py │ ├── hyper_toys.csv │ └── requirements.txt ├── modus.json ├── package-lock.json └── package.json └── frontend ├── .env.example ├── .eslintrc.json ├── .gitignore ├── README.md ├── app ├── actions.ts ├── components │ ├── advanced-search.tsx │ ├── carousel.tsx │ ├── cart │ │ ├── RecommendedProducts.tsx │ │ ├── cart-modal.tsx │ │ ├── delete-item.tsx │ │ ├── edit-quantity.tsx │ │ ├── index.tsx │ │ └── submit-button.tsx │ ├── footer.tsx │ ├── grid.tsx │ ├── header.tsx │ ├── logo.tsx │ ├── product-details.tsx │ ├── product-rating.tsx │ ├── rating-filter.tsx │ ├── search-results-grid.tsx │ ├── search.tsx │ ├── skeletons.tsx │ ├── submit-button.tsx │ └── tile.tsx ├── favicon.ico ├── layout.tsx ├── page.tsx ├── product │ └── [id] │ │ └── page.tsx └── search │ └── page.tsx ├── favicon.ico ├── globals.css ├── lib └── constants.ts ├── next.config.mjs ├── package.json ├── pnpm-lock.yaml ├── postcss.config.mjs ├── public └── logo-dark.svg ├── tailwind.config.ts └── tsconfig.json /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["github>hypermodeinc/renovate-config"] 4 | } 5 | -------------------------------------------------------------------------------- /.github/workflows/ci-modus-build.yml: -------------------------------------------------------------------------------- 1 | 2 | name: ci-modus-build 3 | 4 | on: 5 | workflow_dispatch: 6 | pull_request: 7 | push: 8 | branches: 9 | - main 10 | 11 | env: 12 | MODUS_DIR: "" 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | build: 19 | name: build 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout code 23 | uses: actions/checkout@v4 24 | 25 | - name: Locate directory with modus.json 26 | id: set-dir 27 | run: | 28 | MODUS_JSON=$(find $(pwd) -name 'modus.json' -print0 | xargs -0 -n1 echo) 29 | if [ -n "$MODUS_JSON" ]; then 30 | MODUS_DIR=$(dirname "$MODUS_JSON") 31 | echo "MODUS_DIR=$MODUS_DIR" >> $GITHUB_ENV 32 | else 33 | echo "modus.json not found" 34 | exit 1 35 | fi 36 | 37 | - name: Setup Node 38 | uses: actions/setup-node@v4 39 | with: 40 | node-version: "22.16.0" 41 | 42 | - name: Setup Go 43 | uses: actions/setup-go@v5 44 | 45 | - name: Setup TinyGo 46 | uses: acifani/setup-tinygo@v2 47 | with: 48 | tinygo-version: "0.34.0" 49 | 50 | - name: Build project 51 | run: npx -p @hypermode/modus-cli -y modus build 52 | working-directory: ${{ env.MODUS_DIR }} 53 | shell: bash 54 | 55 | - name: Publish GitHub artifact 56 | uses: actions/upload-artifact@v4 57 | with: 58 | name: build 59 | path: ${{ env.MODUS_DIR }}/build/* 60 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "esbenp.prettier-vscode", 4 | "dbaeumer.vscode-eslint", 5 | "streetsidesoftware.code-spell-checker" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "files.insertFinalNewline": true, 4 | "files.trimFinalNewlines": true, 5 | "files.associations": { 6 | "hypermode.json": "jsonc" 7 | }, 8 | "[typescript]": { 9 | "editor.defaultFormatter": "esbenp.prettier-vscode" 10 | }, 11 | "[json]": { 12 | "editor.defaultFormatter": "esbenp.prettier-vscode" 13 | }, 14 | "[jsonc]": { 15 | "editor.defaultFormatter": "esbenp.prettier-vscode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | ©️ Hypermode Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hyper-Commerce 2 | 3 | Composable-commerce storefront demo to show off the performance of Hypermode's vector search. 4 | 5 | Example site: 6 | 7 | [hypermode-commerce.com](https://hypermode-commerce.com) 8 | 9 | Explainer Video: 10 | 11 | [![Hypermode Demo](https://img.youtube.com/vi/Me4YjNzR-cg/0.jpg)](https://www.youtube.com/watch?v=Me4YjNzR-cg) 12 | 13 | When a user pauses in the search bar, Hypermode generates an embedding of the user's input and executes a vector search (HNSW or sequential) of a 10,000 item catalog in less than 200ms. 14 | 15 | ## Lightning Fast Semantic Search 16 | 17 | This template illustrates the power of Hypermode collections to create real-time production-grade semantic search. 18 | 19 | This is accomplished by hosting small embedding models, and building vector indexes in memory, allowing for low latency results. 20 | 21 | To try it out, add sample data to your collection using the `upsertProducts` api, which embeds & inserts texts into your index, and query the index using `searchProducts`. 22 | 23 | ## How to use the template 24 | 25 | - To deploy this code, click "use this template" in this repo. 26 | - Go to [hypermode.com/sign-up](https://hypermode.com/sign-up). 27 | - Create new project 28 | - Import this repo 29 | 30 | Hypermode will automatically spin up the embedding model found in `backend/hypermode.json` file as well as generate a working GraphQL API for all functions exported from your `backend/functions/index.ts` file. 31 | 32 | ### Sample data 33 | 34 | An example is provided, named `backend/extras/hyper_toys.csv`, and the corresponding python script to run it is named `backend/extras/ecommerce_populate.py`. Navigate to the subfolder to run them. 35 | To install dependencies, run `pip install -r requirements.txt` from within that directory. 36 | 37 | You'll need to edit the `ecommerce_populate.py` file with your Hypermode project URL and auth token found in your Hypermode console dashboard. 38 | 39 | 40 | [![hyper-cons](https://github.com/user-attachments/assets/18478278-93bf-479b-955c-c23c7a7cdecb)](hypermode.com) 41 | 42 | Load the data using `python3 ecommerce_populate.py`, and it will show you the batched inserts and time taken. 43 | Please note: since this is inserting 10k rows sequentially, it will take ~18 minutes to embed & insert data. If you want to just try this out with a smaller dataset, feel free to shrink the csv to whatever you need. 44 | 45 | ### Calling the APIs 46 | 47 | Open up postman and create a new GraphQL request, and add the endpoint with the authorization as a bearer token. On schema introspection you will see all the functions from the template light up as APIs. Just mess around with it and see what happens! 48 | 49 | ## References 50 | 51 | Hypermode is a framework for building AI powered API. 52 | In an Hypermode project, exported functions from `functions/assembly/index.ts` are immediately available as a scalable GraphQL API, allowing direct integration of your AI logic into your existing applications. 53 | 54 | Each function can use AI models inferences, data connections (HTTP, GraphQL, DB, ...), collections ( a flexible vector search abstraction) and custom logic. 55 | 56 | For more information on functions, models, connections, collections and project configuration, consult [our documentation](https://docs.hypermode.com). 57 | 58 | For writing functions in AssemblyScript, you can refer to the [examples](https://github.com/hypermodeinc/functions-as/tree/main/examples) in [hypermodeinc/functions-as](https://github.com/hypermodeinc/functions-as). 59 | 60 | ## Frontend Template 61 | 62 | ### Running Locally 63 | 64 | To run this project locally, start by setting up your environment variables. Copy the definitions from [`.env.example`](https://github.com/hypermodeinc/hyper-commerce/blob/main/frontend/.env.example) into a new file named `.env.local` at the root of your project, and provide the values from your Hypermode dashboard. 65 | 66 | Once your environment variables are configured, install the necessary dependencies with: 67 | 68 | ``` 69 | pnpm install 70 | ``` 71 | 72 | Then, start the development server using: 73 | 74 | ``` 75 | pnpm dev 76 | ``` 77 | 78 | Your app should be up and running at [localhost:3000](http://localhost:3000/) 79 | 80 | ### Deploy with Vercel 81 | 82 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fhypermodeinc%2Fhyper-commerce%2Ftree%2Fmain%2Ffrontend) 83 | 84 | _NOTES_: Make sure your environment variables are added to your Vercel project. 85 | 86 | - HYPERMODE_API_TOKEN 87 | - HYPERMODE_API_ENDPOINT 88 | 89 | ![hyp-com-env](https://github.com/user-attachments/assets/38264879-b462-44bf-b658-cc6f14000266) 90 | 91 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /backend/asconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@hypermode/modus-sdk-as/plugin.asconfig.json", 3 | "options": { 4 | "transform": ["@hypermode/modus-sdk-as/transform", "json-as/transform"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /backend/assembly/crud.ts: -------------------------------------------------------------------------------- 1 | import { collections } from "@hypermode/modus-sdk-as"; 2 | import { Product, Cart, CartItemObject, consts } from "./types"; 3 | 4 | export function upsertProduct( 5 | id: string, 6 | name: string, 7 | category: string, 8 | price: f64, 9 | description: string, 10 | image: string, 11 | stars: f64, 12 | isStocked: boolean, 13 | ): string { 14 | let result = collections.upsert(consts.productNameCollection, id, name); 15 | if (!result.isSuccessful) { 16 | return result.error; 17 | } 18 | result = collections.upsert(consts.productCategoryCollection, id, category); 19 | if (!result.isSuccessful) { 20 | return result.error; 21 | } 22 | result = collections.upsert( 23 | consts.productPriceCollection, 24 | id, 25 | price.toString(), 26 | ); 27 | if (!result.isSuccessful) { 28 | return result.error; 29 | } 30 | result = collections.upsert( 31 | consts.productDescriptionCollection, 32 | id, 33 | description, 34 | ); 35 | if (!result.isSuccessful) { 36 | return result.error; 37 | } 38 | result = collections.upsert(consts.productImageCollection, id, image); 39 | if (!result.isSuccessful) { 40 | return result.error; 41 | } 42 | result = collections.upsert( 43 | consts.productStarCollection, 44 | id, 45 | stars.toString(), 46 | ); 47 | if (!result.isSuccessful) { 48 | return result.error; 49 | } 50 | result = collections.upsert( 51 | consts.isProductStockedCollection, 52 | id, 53 | isStocked.toString(), 54 | ); 55 | if (!result.isSuccessful) { 56 | return result.error; 57 | } 58 | return id; 59 | } 60 | 61 | export function upsertProducts( 62 | ids: string[], 63 | names: string[], 64 | categories: string[], 65 | prices: f64[], 66 | descriptions: string[], 67 | images: string[], 68 | stars: f64[], 69 | isStockedArray: boolean[], 70 | ): string[] { 71 | const errors: string[] = []; 72 | 73 | if ( 74 | ids.length !== names.length || 75 | ids.length !== categories.length || 76 | ids.length !== prices.length || 77 | ids.length !== descriptions.length || 78 | ids.length !== images.length || 79 | ids.length !== stars.length || 80 | ids.length !== isStockedArray.length 81 | ) { 82 | errors.push("Length of all arrays must be the same"); 83 | return errors; 84 | } 85 | let result = collections.upsertBatch( 86 | consts.productNameCollection, 87 | ids, 88 | names, 89 | ); 90 | 91 | if (!result.isSuccessful) { 92 | errors.push(result.error); 93 | return errors; 94 | } 95 | 96 | result = collections.upsertBatch( 97 | consts.productCategoryCollection, 98 | ids, 99 | categories, 100 | ); 101 | if (!result.isSuccessful) { 102 | errors.push(result.error); 103 | return errors; 104 | } 105 | result = collections.upsertBatch( 106 | consts.productPriceCollection, 107 | ids, 108 | prices.map((x) => x.toString()), 109 | ); 110 | if (!result.isSuccessful) { 111 | errors.push(result.error); 112 | return errors; 113 | } 114 | 115 | result = collections.upsertBatch( 116 | consts.productDescriptionCollection, 117 | ids, 118 | descriptions, 119 | ); 120 | if (!result.isSuccessful) { 121 | errors.push(result.error); 122 | return errors; 123 | } 124 | 125 | result = collections.upsertBatch(consts.productImageCollection, ids, images); 126 | if (!result.isSuccessful) { 127 | errors.push(result.error); 128 | return errors; 129 | } 130 | 131 | result = collections.upsertBatch( 132 | consts.productStarCollection, 133 | ids, 134 | stars.map((x) => x.toString()), 135 | ); 136 | if (!result.isSuccessful) { 137 | errors.push(result.error); 138 | return errors; 139 | } 140 | 141 | result = collections.upsertBatch( 142 | consts.isProductStockedCollection, 143 | ids, 144 | isStockedArray.map((x) => x.toString()), 145 | ); 146 | if (!result.isSuccessful) { 147 | errors.push(result.error); 148 | return errors; 149 | } 150 | 151 | return ids; 152 | } 153 | 154 | export function deleteProduct(id: string): string { 155 | let result = collections.remove(consts.productNameCollection, id); 156 | if (!result.isSuccessful) { 157 | return result.error; 158 | } 159 | result = collections.remove(consts.productCategoryCollection, id); 160 | if (!result.isSuccessful) { 161 | return result.error; 162 | } 163 | result = collections.remove(consts.productPriceCollection, id); 164 | if (!result.isSuccessful) { 165 | return result.error; 166 | } 167 | result = collections.remove(consts.productDescriptionCollection, id); 168 | if (!result.isSuccessful) { 169 | return result.error; 170 | } 171 | result = collections.remove(consts.productImageCollection, id); 172 | if (!result.isSuccessful) { 173 | return result.error; 174 | } 175 | result = collections.remove(consts.productStarCollection, id); 176 | if (!result.isSuccessful) { 177 | return result.error; 178 | } 179 | result = collections.remove(consts.isProductStockedCollection, id); 180 | if (!result.isSuccessful) { 181 | return result.error; 182 | } 183 | return "success"; 184 | } 185 | 186 | export function deleteProducts(ids: string[]): string { 187 | for (let i = 0; i < ids.length; i++) { 188 | const res = deleteProduct(ids[i]); 189 | if (res !== "success") { 190 | return "Error deleting product with id: " + ids[i]; 191 | } 192 | } 193 | 194 | return "success"; 195 | } 196 | 197 | export function getProduct(id: string): Product { 198 | const name = collections.getText(consts.productNameCollection, id); 199 | const category = collections.getText(consts.productCategoryCollection, id); 200 | const priceRes = collections.getText(consts.productPriceCollection, id); 201 | let price = parseFloat(priceRes); 202 | if (isNaN(price)) { 203 | price = 0; 204 | } 205 | const description = collections.getText( 206 | consts.productDescriptionCollection, 207 | id, 208 | ); 209 | const image = collections.getText(consts.productImageCollection, id); 210 | const starRes = collections.getText(consts.productStarCollection, id); 211 | let stars = parseFloat(starRes); 212 | if (isNaN(stars)) { 213 | stars = 0; 214 | } 215 | const isStockedRes = collections.getText( 216 | consts.isProductStockedCollection, 217 | id, 218 | ); 219 | const isStocked = isStockedRes === "true"; 220 | 221 | return new Product( 222 | id, 223 | name, 224 | category, 225 | price, 226 | description, 227 | image, 228 | stars, 229 | isStocked, 230 | ); 231 | } 232 | 233 | export function getProducts(ids: string[]): Product[] { 234 | const products: Product[] = []; 235 | for (let i = 0; i < ids.length; i++) { 236 | products.push(getProduct(ids[i])); 237 | } 238 | return products; 239 | } 240 | 241 | export function upsertCart(cartId: string, cartItemIds: string): string { 242 | const result = collections.upsert( 243 | consts.cartsCollection, 244 | cartId, 245 | cartItemIds, 246 | ); 247 | if (!result.isSuccessful) { 248 | return result.error; 249 | } 250 | return cartId; 251 | } 252 | 253 | function upsertCartQuantity(cartItemId: string, quantity: f64): string { 254 | const result = collections.upsert( 255 | consts.cartItemsCollection, 256 | cartItemId, 257 | quantity.toString(), 258 | ); 259 | if (!result.isSuccessful) { 260 | return result.error; 261 | } 262 | return cartItemId; 263 | } 264 | 265 | export function addToCart(cartId: string, productId: string): string { 266 | const cart = collections.getText(consts.cartsCollection, cartId); 267 | const cartItemId = cartId + "_" + productId; 268 | 269 | if (cart === null || cart === "") { 270 | const upsertResult = upsertCart(cartId, productId); 271 | if (upsertResult !== cartId) { 272 | console.log("Failed to create new cart:"); 273 | return upsertResult; 274 | } 275 | 276 | const quantityResult = upsertCartQuantity(cartItemId, 1); 277 | if (quantityResult !== cartItemId) { 278 | console.log("Failed to set initial quantity:"); 279 | return quantityResult; 280 | } 281 | } else { 282 | const cartItems = cart.split(","); 283 | let isProductInCart = false; 284 | 285 | for (let i = 0; i < cartItems.length; i++) { 286 | if (cartItems[i] === productId) { 287 | isProductInCart = true; 288 | break; 289 | } 290 | } 291 | 292 | if (isProductInCart) { 293 | const cartItemQuantity = collections.getText( 294 | consts.cartItemsCollection, 295 | cartItemId, 296 | ); 297 | 298 | if (cartItemQuantity === null || cartItemQuantity === "") { 299 | console.log("Failed to retrieve cart item quantity for:"); 300 | return "error"; 301 | } 302 | let cartItemQuantityNumber = parseFloat(cartItemQuantity); 303 | if (isNaN(cartItemQuantityNumber)) { 304 | cartItemQuantityNumber = 0; 305 | } 306 | 307 | const newQuantity = cartItemQuantityNumber + 1; 308 | const upsertQuantityResult = collections.upsert( 309 | consts.cartItemsCollection, 310 | cartItemId, 311 | newQuantity.toString(), 312 | ); 313 | 314 | if (!upsertQuantityResult.isSuccessful) { 315 | console.log("Failed to update quantity:"); 316 | return upsertQuantityResult.error; 317 | } 318 | } else { 319 | const updatedCart = cart + "," + productId; 320 | const upsertResult = upsertCart(cartId, updatedCart); 321 | if (upsertResult !== cartId) { 322 | console.log("Failed to update cart with new product:"); 323 | return upsertResult; 324 | } 325 | 326 | const quantityResult = upsertCartQuantity(cartItemId, 1); 327 | if (quantityResult !== cartItemId) { 328 | console.log("Failed to add new product quantity:"); 329 | return quantityResult; 330 | } 331 | } 332 | } 333 | 334 | return "success"; 335 | } 336 | 337 | export function decreaseQuantity(cartId: string, productId: string): string { 338 | const cartItemId = cartId + "_" + productId; 339 | const cartItemQuantity = collections.getText( 340 | consts.cartItemsCollection, 341 | cartItemId, 342 | ); 343 | 344 | if (cartItemQuantity === null || cartItemQuantity === "") { 345 | console.log("Failed to retrieve cart item quantity for:"); 346 | return "error"; 347 | } 348 | 349 | let cartItemQuantityNumber = parseFloat(cartItemQuantity); 350 | if (isNaN(cartItemQuantityNumber)) { 351 | cartItemQuantityNumber = 0; 352 | } 353 | 354 | const newQuantity = cartItemQuantityNumber - 1; 355 | if (newQuantity === 0) { 356 | return removeFromCart(cartId, productId); 357 | } 358 | 359 | const upsertQuantityResult = collections.upsert( 360 | consts.cartItemsCollection, 361 | cartItemId, 362 | newQuantity.toString(), 363 | ); 364 | 365 | if (!upsertQuantityResult.isSuccessful) { 366 | console.log("Failed to update quantity:"); 367 | return upsertQuantityResult.error; 368 | } 369 | 370 | return "success"; 371 | } 372 | 373 | export function removeFromCart(cartId: string, productId: string): string { 374 | const cartItemId = cartId + "_" + productId; 375 | const result = collections.remove(consts.cartItemsCollection, cartItemId); 376 | if (!result.isSuccessful) { 377 | return result.error; 378 | } 379 | 380 | const cart = collections.getText(consts.cartsCollection, cartId); 381 | if (cart === null || cart === "") { 382 | return "Cart not found"; 383 | } 384 | 385 | const cartItems = cart.split(","); 386 | let updatedCart = ""; 387 | let itemFound = false; 388 | 389 | for (let i = 0; i < cartItems.length; i++) { 390 | if (cartItems[i] !== productId) { 391 | if (updatedCart.length > 0) { 392 | updatedCart += ","; 393 | } 394 | updatedCart += cartItems[i]; 395 | } else { 396 | itemFound = true; 397 | } 398 | } 399 | 400 | if (!itemFound) { 401 | return "Item not found in cart"; 402 | } 403 | 404 | if (updatedCart === "") { 405 | collections.remove(consts.cartsCollection, cartId); 406 | } else { 407 | collections.upsert(consts.cartsCollection, cartId, updatedCart); 408 | } 409 | 410 | return "success"; 411 | } 412 | 413 | export function getCart(cartId: string): Cart { 414 | const cartItemIds = collections.getText(consts.cartsCollection, cartId); 415 | if (cartItemIds === "") { 416 | return new Cart(cartId, []); 417 | } 418 | const cartItems = cartItemIds.split(","); 419 | const items = new Array(); 420 | for (let i = 0; i < cartItems.length; i++) { 421 | const cartItemID = cartItems[i]; 422 | const quantity = collections.getText( 423 | consts.cartItemsCollection, 424 | cartId + "_" + cartItemID, 425 | ); 426 | if (quantity === null || quantity === "") { 427 | console.log(`Failed to retrieve quantity for cartItemID: ${cartItemID}`); 428 | continue; 429 | } 430 | const product = getProduct(cartItemID); 431 | if (!product) { 432 | console.log(`Product not found for cartItemID: ${cartItemID}`); 433 | } 434 | const cartItemObject = new CartItemObject( 435 | cartItemID, 436 | product, 437 | parseFloat(quantity), 438 | ); 439 | items.push(cartItemObject); 440 | } 441 | const totalCartQuantity = items.reduce( 442 | (acc, item) => acc + item.quantity, 443 | 0, 444 | ); 445 | return new Cart(cartId, items, totalCartQuantity); 446 | } 447 | -------------------------------------------------------------------------------- /backend/assembly/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./crud"; 2 | export * from "./search"; 3 | export * from "./types"; 4 | -------------------------------------------------------------------------------- /backend/assembly/search.ts: -------------------------------------------------------------------------------- 1 | import { collections, models } from "@hypermode/modus-sdk-as"; 2 | import { EmbeddingsModel } from "@hypermode/modus-sdk-as/models/experimental/embeddings"; 3 | import { getProduct, getCart } from "./crud"; 4 | import { ProductSearchResult, ProductSearchObject, consts } from "./types"; 5 | 6 | export function recommendProductByCart( 7 | cartId: string, 8 | maxItems: i32, 9 | ): ProductSearchResult { 10 | const productSearchRes = new ProductSearchResult( 11 | consts.productNameCollection, 12 | consts.searchMethod, 13 | "success", 14 | "", 15 | ); 16 | 17 | const cart = getCart(cartId); 18 | if (cart === null || cart.items.length === 0) { 19 | productSearchRes.status = "error"; 20 | productSearchRes.error = "Cart not found"; 21 | return productSearchRes; 22 | } 23 | 24 | const cartVecs: f32[][] = []; 25 | 26 | for (let i = 0; i < cart.items.length; i++) { 27 | const vec = collections.getVector( 28 | consts.productNameCollection, 29 | consts.searchMethod, 30 | cart.items[i].Product.id, 31 | ); 32 | 33 | cartVecs.push(vec); 34 | } 35 | 36 | const sumVec: f32[] = []; 37 | 38 | for (let i = 0; i < cartVecs[0].length; i++) { 39 | sumVec[i] = 0; 40 | for (let j = 0; j < cartVecs.length; j++) { 41 | sumVec[i] += cartVecs[j][i]; 42 | } 43 | } 44 | 45 | const normalizedVec = normalize(sumVec); 46 | 47 | const cartProductIds = cart.items.map((item) => item.Product.id); 48 | 49 | const semanticSearchRes = collections.searchByVector( 50 | consts.productNameCollection, 51 | consts.searchMethod, 52 | normalizedVec, 53 | maxItems + cart.items.length, 54 | ); 55 | 56 | if (!semanticSearchRes.isSuccessful) { 57 | productSearchRes.status = semanticSearchRes.status; 58 | productSearchRes.error = semanticSearchRes.error; 59 | 60 | return productSearchRes; 61 | } 62 | 63 | for (let i = 0; i < semanticSearchRes.objects.length; i++) { 64 | if (cartProductIds.includes(semanticSearchRes.objects[i].key)) { 65 | continue; 66 | } 67 | const searchObj = getSearchObject( 68 | semanticSearchRes.objects[i].key, 69 | semanticSearchRes.objects[i].score, 70 | semanticSearchRes.objects[i].distance, 71 | ); 72 | productSearchRes.searchObjs.push(searchObj); 73 | } 74 | 75 | productSearchRes.searchObjs = productSearchRes.searchObjs.slice(0, maxItems); 76 | 77 | return productSearchRes; 78 | } 79 | 80 | export function searchProducts( 81 | query: string, 82 | maxItems: i32, 83 | thresholdStars: f32 = 0.0, 84 | inStockOnly: boolean = false, 85 | ): ProductSearchResult { 86 | const productSearchRes = new ProductSearchResult( 87 | consts.productNameCollection, 88 | consts.searchMethod, 89 | "success", 90 | "", 91 | ); 92 | 93 | const semanticSearchRes = collections.search( 94 | consts.productNameCollection, 95 | consts.searchMethod, 96 | query, 97 | maxItems, 98 | true, 99 | ); 100 | 101 | if (!semanticSearchRes.isSuccessful) { 102 | productSearchRes.status = semanticSearchRes.status; 103 | productSearchRes.error = semanticSearchRes.error; 104 | 105 | return productSearchRes; 106 | } 107 | 108 | if (inStockOnly) { 109 | for (let i = 0; i < semanticSearchRes.objects.length; i++) { 110 | const inStockRes = collections.getText( 111 | consts.isProductStockedCollection, 112 | semanticSearchRes.objects[i].key, 113 | ); 114 | const inStock = inStockRes === "true"; 115 | if (!inStock) { 116 | semanticSearchRes.objects.splice(i, 1); 117 | i--; 118 | } 119 | } 120 | } 121 | 122 | const rankedResults = reRankAndFilterSearchResultObjects( 123 | semanticSearchRes.objects, 124 | thresholdStars, 125 | ); 126 | 127 | for (let i = 0; i < rankedResults.length; i++) { 128 | const searchObj = getSearchObject( 129 | rankedResults[i].key, 130 | rankedResults[i].score, 131 | rankedResults[i].distance, 132 | ); 133 | productSearchRes.searchObjs.unshift(searchObj); 134 | } 135 | 136 | return productSearchRes; 137 | } 138 | 139 | function getSearchObject( 140 | key: string, 141 | score: f64, 142 | distance: f64, 143 | ): ProductSearchObject { 144 | return new ProductSearchObject(getProduct(key), score, distance); 145 | } 146 | 147 | function reRankAndFilterSearchResultObjects( 148 | objs: collections.CollectionSearchResultObject[], 149 | thresholdStars: f32, 150 | ): collections.CollectionSearchResultObject[] { 151 | for (let i = 0; i < objs.length; i++) { 152 | const starRes = collections.getText( 153 | consts.productStarCollection, 154 | objs[i].key, 155 | ); 156 | let stars = parseFloat(starRes); 157 | if (isNaN(stars)) { 158 | stars = 0; 159 | } 160 | 161 | const inStockRes = collections.getText( 162 | consts.isProductStockedCollection, 163 | objs[i].key, 164 | ); 165 | const inStock = inStockRes === "true"; 166 | if (!inStock) { 167 | objs[i].score *= 0.5; 168 | } 169 | objs[i].score *= stars * 0.1; 170 | } 171 | 172 | objs.sort((a, b) => { 173 | if (a.score < b.score) { 174 | return -1; 175 | } else if (a.score > b.score) { 176 | return 1; 177 | } else { 178 | return 0; 179 | } 180 | }); 181 | 182 | const filteredResults: collections.CollectionSearchResultObject[] = []; 183 | for (let i = 0; i < objs.length; i++) { 184 | const starRes = collections.getText( 185 | consts.productStarCollection, 186 | objs[i].key, 187 | ); 188 | let stars = parseFloat(starRes); 189 | if (isNaN(stars)) { 190 | stars = 0; 191 | } 192 | if (stars >= thresholdStars) { 193 | filteredResults.push(objs[i]); 194 | } 195 | } 196 | 197 | return filteredResults; 198 | } 199 | 200 | export function miniLMEmbed(texts: string[]): f32[][] { 201 | const model = models.getModel(consts.embeddingModel); 202 | const input = model.createInput(texts); 203 | const output = model.invoke(input); 204 | 205 | return output.predictions; 206 | } 207 | 208 | // Function to calculate the magnitude of a vector 209 | function magnitude(vec: f32[]): f32 { 210 | let sum: f32 = 0.0; 211 | for (let i = 0; i < vec.length; i++) { 212 | sum += vec[i] * vec[i]; 213 | } 214 | return f32(Math.sqrt(sum)); 215 | } 216 | 217 | // Function to normalize a vector 218 | function normalize(vec: f32[]): f32[] { 219 | const mag = magnitude(vec); 220 | if (mag == 0) { 221 | throw new Error("Cannot normalize a zero vector"); 222 | } 223 | 224 | const normalizedVec: f32[] = []; 225 | for (let i = 0; i < vec.length; i++) { 226 | normalizedVec.push(vec[i] / mag); 227 | } 228 | 229 | return normalizedVec; 230 | } 231 | -------------------------------------------------------------------------------- /backend/assembly/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "assemblyscript/std/assembly.json", 3 | "include": ["./**/*.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /backend/assembly/types.ts: -------------------------------------------------------------------------------- 1 | 2 | @json 3 | export class Product { 4 | constructor( 5 | public id: string, 6 | public name: string, 7 | public category: string, 8 | public price: f64, 9 | public description: string, 10 | public image: string, 11 | public stars: f64, 12 | public isStocked: boolean, 13 | ) {} 14 | } 15 | 16 | 17 | @json 18 | export class ProductSearchObject { 19 | constructor( 20 | public product: Product, 21 | public score: f64, 22 | public distance: f64, 23 | ) {} 24 | } 25 | 26 | 27 | @json 28 | export class ProductSearchResult { 29 | constructor( 30 | public collection: string, 31 | public searchMethod: string, 32 | public status: string, 33 | public error: string, 34 | public searchObjs: ProductSearchObject[] = [], 35 | ) {} 36 | } 37 | 38 | 39 | @json 40 | export class Cart { 41 | constructor( 42 | public cartId: string, 43 | public items: CartItemObject[] = [], 44 | public totalCartQuantity: f64 = 0, 45 | ) {} 46 | } 47 | 48 | 49 | @json 50 | export class CartItemObject { 51 | constructor( 52 | public cartItemID: string, 53 | public Product: Product, 54 | public quantity: f64, 55 | ) {} 56 | } 57 | 58 | 59 | @json 60 | export class consts { 61 | static readonly productNameCollection: string = "productNames"; 62 | static readonly productDescriptionCollection: string = "productDescriptions"; 63 | static readonly productCategoryCollection: string = "productCategories"; 64 | static readonly productPriceCollection: string = "productPrices"; 65 | static readonly productImageCollection: string = "productImages"; 66 | static readonly productStarCollection: string = "productStars"; 67 | static readonly isProductStockedCollection: string = "isProductStocked"; 68 | static readonly cartsCollection: string = "carts"; 69 | static readonly cartItemsCollection: string = "cartItems"; 70 | 71 | static readonly searchMethod: string = "searchMethod1"; 72 | static readonly embeddingModel: string = "minilm"; 73 | } 74 | -------------------------------------------------------------------------------- /backend/eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import eslint from "@eslint/js"; 4 | import tseslint from "typescript-eslint"; 5 | import aseslint from "@hypermode/modus-sdk-as/tools/assemblyscript-eslint"; 6 | 7 | export default tseslint.config( 8 | eslint.configs.recommended, 9 | ...tseslint.configs.recommended, 10 | aseslint.config, 11 | ); 12 | -------------------------------------------------------------------------------- /backend/extras/ecommerce_populate.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | import statistics 4 | import copy 5 | import pandas as pd 6 | import numpy as np 7 | import concurrent.futures 8 | import math 9 | import json 10 | from gql import gql, Client 11 | from gql.transport.requests import RequestsHTTPTransport 12 | 13 | def post_mutation_to_runtime(ids, names, categories, selling_prices, descriptions, images, stars, in_stock): 14 | url = '' 15 | 16 | headers = { 17 | 'Authorization': 'Bearer ', 18 | } 19 | 20 | # Define the transport with the URL and headers 21 | transport = RequestsHTTPTransport(url=url, headers=headers, use_json=True) 22 | 23 | # Create the client 24 | client = Client(transport=transport, fetch_schema_from_transport=True) 25 | 26 | # Convert ids and texts to JSON arrays 27 | ids_json = json.dumps(ids) 28 | names_json = json.dumps(names) 29 | categories_json = json.dumps(categories) 30 | selling_prices_json = json.dumps(selling_prices) 31 | descriptions_json = json.dumps(descriptions) 32 | images_json = json.dumps(images) 33 | stars_json = json.dumps(stars) 34 | in_stock_json = json.dumps(in_stock) 35 | 36 | 37 | query = gql(f''' 38 | query UpsertProducts {{ 39 | upsertProducts(ids: {ids_json}, names: {names_json}, categories: {categories_json}, prices: {selling_prices_json}, descriptions: {descriptions_json}, images: {images_json}, stars: {stars_json}, isStockedArray: {in_stock_json}) 40 | }} 41 | ''') 42 | 43 | start_time = time.time() 44 | response = client.execute(query) 45 | end_time = time.time() 46 | 47 | return response, end_time - start_time 48 | 49 | 50 | # Read the CSV file 51 | df = pd.read_csv('hyper_toys.csv') 52 | 53 | # Convert the columns to arrays 54 | product_ids = df['Uniq Id'].values 55 | product_names = df['Product Name'].values 56 | product_categories = df['Category'].values 57 | selling_prices = df['Selling Price'].values 58 | about_products = df['About Product'].values 59 | images = df['Image'].values 60 | stars = df['Num Stars'].values 61 | in_stock = df['In Stock'].values 62 | 63 | selling_prices = selling_prices.astype(float) 64 | stars = stars.astype(float) 65 | in_stock = in_stock.astype(bool) 66 | 67 | # Create batches of size 25 68 | product_ids_batches = np.array_split(product_ids, len(product_ids) // 25) 69 | product_names_batches = np.array_split(product_names, len(product_names) // 25) 70 | product_categories_batches = np.array_split(product_categories, len(product_categories) // 25) 71 | selling_prices_batches = np.array_split(selling_prices, len(selling_prices) // 25) 72 | about_products_batches = np.array_split(about_products, len(about_products) // 25) 73 | images_batches = np.array_split(images, len(images) // 25) 74 | stars_batches = np.array_split(stars, len(stars) // 25) 75 | in_stock_batches = np.array_split(in_stock, len(in_stock) // 25) 76 | 77 | totalTimes = [] 78 | 79 | # sequential 80 | for i in range(len(product_names_batches)): 81 | print(i) 82 | resp, duration = post_mutation_to_runtime(product_ids_batches[i].tolist(), product_names_batches[i].tolist(), product_categories_batches[i].tolist(), selling_prices_batches[i].tolist(), about_products_batches[i].tolist(), images_batches[i].tolist(), stars_batches[i].tolist(), in_stock_batches[i].tolist()) 83 | print(duration) 84 | totalTimes.append(duration) 85 | 86 | print(f"Total time combined: {sum(totalTimes)}") 87 | 88 | print(f"median total time: {statistics.median(totalTimes)}") 89 | -------------------------------------------------------------------------------- /backend/extras/requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | pandas 3 | numpy 4 | gql 5 | requests_toolbelt 6 | -------------------------------------------------------------------------------- /backend/modus.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.hypermode.com/modus.json", 3 | "endpoints": { 4 | "default": { 5 | "type": "graphql", 6 | "path": "/graphql", 7 | "auth": "bearer-token" 8 | } 9 | }, 10 | "models": { 11 | "minilm": { 12 | "sourceModel": "sentence-transformers/all-MiniLM-L6-v2", 13 | "provider": "hugging-face", 14 | "connection": "hypermode" 15 | } 16 | }, 17 | "collections": { 18 | "productNames": { 19 | "searchMethods": { 20 | "searchMethod1": { 21 | "embedder": "miniLMEmbed", 22 | "index": { 23 | "type": "sequential" 24 | } 25 | } 26 | } 27 | }, 28 | "productDescriptions": { 29 | "searchMethods": {} 30 | }, 31 | "productCategories": { 32 | "searchMethods": {} 33 | }, 34 | "productPrices": { 35 | "searchMethods": {} 36 | }, 37 | "productImages": { 38 | "searchMethods": {} 39 | }, 40 | "productStars": { 41 | "searchMethods": {} 42 | }, 43 | "isProductStocked": { 44 | "searchMethods": {} 45 | }, 46 | "carts": { 47 | "searchMethods": {} 48 | }, 49 | "cartItems": { 50 | "searchMethods": {} 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /backend/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hyper-commerce", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "hyper-commerce", 8 | "license": "MIT", 9 | "dependencies": { 10 | "@hypermode/modus-sdk-as": "0.17.5", 11 | "json-as": "1.1.14" 12 | }, 13 | "devDependencies": { 14 | "@eslint/js": "9.27.0", 15 | "assemblyscript": "0.27.36", 16 | "assemblyscript-prettier": "3.0.1", 17 | "eslint": "9.27.0", 18 | "prettier": "3.5.3", 19 | "typescript": "5.8.3", 20 | "typescript-eslint": "8.32.1" 21 | } 22 | }, 23 | "node_modules/@assemblyscript/wasi-shim": { 24 | "version": "0.1.0", 25 | "resolved": "https://registry.npmjs.org/@assemblyscript/wasi-shim/-/wasi-shim-0.1.0.tgz", 26 | "integrity": "sha512-fSLH7MdJHf2uDW5llA5VCF/CG62Jp2WkYGui9/3vIWs3jDhViGeQF7nMYLUjpigluM5fnq61I6obtCETy39FZw==", 27 | "license": "Apache-2.0", 28 | "funding": { 29 | "type": "opencollective", 30 | "url": "https://opencollective.com/assemblyscript" 31 | } 32 | }, 33 | "node_modules/@eslint-community/eslint-utils": { 34 | "version": "4.7.0", 35 | "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", 36 | "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", 37 | "dev": true, 38 | "license": "MIT", 39 | "dependencies": { 40 | "eslint-visitor-keys": "^3.4.3" 41 | }, 42 | "engines": { 43 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 44 | }, 45 | "funding": { 46 | "url": "https://opencollective.com/eslint" 47 | }, 48 | "peerDependencies": { 49 | "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" 50 | } 51 | }, 52 | "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { 53 | "version": "3.4.3", 54 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", 55 | "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", 56 | "dev": true, 57 | "license": "Apache-2.0", 58 | "engines": { 59 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 60 | }, 61 | "funding": { 62 | "url": "https://opencollective.com/eslint" 63 | } 64 | }, 65 | "node_modules/@eslint-community/regexpp": { 66 | "version": "4.12.1", 67 | "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", 68 | "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", 69 | "dev": true, 70 | "license": "MIT", 71 | "engines": { 72 | "node": "^12.0.0 || ^14.0.0 || >=16.0.0" 73 | } 74 | }, 75 | "node_modules/@eslint/config-array": { 76 | "version": "0.20.0", 77 | "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", 78 | "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", 79 | "dev": true, 80 | "license": "Apache-2.0", 81 | "dependencies": { 82 | "@eslint/object-schema": "^2.1.6", 83 | "debug": "^4.3.1", 84 | "minimatch": "^3.1.2" 85 | }, 86 | "engines": { 87 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 88 | } 89 | }, 90 | "node_modules/@eslint/config-helpers": { 91 | "version": "0.2.1", 92 | "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", 93 | "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", 94 | "dev": true, 95 | "license": "Apache-2.0", 96 | "engines": { 97 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 98 | } 99 | }, 100 | "node_modules/@eslint/core": { 101 | "version": "0.14.0", 102 | "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", 103 | "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", 104 | "dev": true, 105 | "license": "Apache-2.0", 106 | "dependencies": { 107 | "@types/json-schema": "^7.0.15" 108 | }, 109 | "engines": { 110 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 111 | } 112 | }, 113 | "node_modules/@eslint/eslintrc": { 114 | "version": "3.3.1", 115 | "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", 116 | "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", 117 | "dev": true, 118 | "license": "MIT", 119 | "dependencies": { 120 | "ajv": "^6.12.4", 121 | "debug": "^4.3.2", 122 | "espree": "^10.0.1", 123 | "globals": "^14.0.0", 124 | "ignore": "^5.2.0", 125 | "import-fresh": "^3.2.1", 126 | "js-yaml": "^4.1.0", 127 | "minimatch": "^3.1.2", 128 | "strip-json-comments": "^3.1.1" 129 | }, 130 | "engines": { 131 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 132 | }, 133 | "funding": { 134 | "url": "https://opencollective.com/eslint" 135 | } 136 | }, 137 | "node_modules/@eslint/js": { 138 | "version": "9.27.0", 139 | "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", 140 | "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", 141 | "dev": true, 142 | "license": "MIT", 143 | "engines": { 144 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 145 | }, 146 | "funding": { 147 | "url": "https://eslint.org/donate" 148 | } 149 | }, 150 | "node_modules/@eslint/object-schema": { 151 | "version": "2.1.6", 152 | "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", 153 | "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", 154 | "dev": true, 155 | "license": "Apache-2.0", 156 | "engines": { 157 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 158 | } 159 | }, 160 | "node_modules/@eslint/plugin-kit": { 161 | "version": "0.3.1", 162 | "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", 163 | "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", 164 | "dev": true, 165 | "license": "Apache-2.0", 166 | "dependencies": { 167 | "@eslint/core": "^0.14.0", 168 | "levn": "^0.4.1" 169 | }, 170 | "engines": { 171 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 172 | } 173 | }, 174 | "node_modules/@humanfs/core": { 175 | "version": "0.19.1", 176 | "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", 177 | "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", 178 | "dev": true, 179 | "license": "Apache-2.0", 180 | "engines": { 181 | "node": ">=18.18.0" 182 | } 183 | }, 184 | "node_modules/@humanfs/node": { 185 | "version": "0.16.6", 186 | "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", 187 | "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", 188 | "dev": true, 189 | "license": "Apache-2.0", 190 | "dependencies": { 191 | "@humanfs/core": "^0.19.1", 192 | "@humanwhocodes/retry": "^0.3.0" 193 | }, 194 | "engines": { 195 | "node": ">=18.18.0" 196 | } 197 | }, 198 | "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { 199 | "version": "0.3.1", 200 | "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", 201 | "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", 202 | "dev": true, 203 | "license": "Apache-2.0", 204 | "engines": { 205 | "node": ">=18.18" 206 | }, 207 | "funding": { 208 | "type": "github", 209 | "url": "https://github.com/sponsors/nzakas" 210 | } 211 | }, 212 | "node_modules/@humanwhocodes/module-importer": { 213 | "version": "1.0.1", 214 | "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", 215 | "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", 216 | "dev": true, 217 | "license": "Apache-2.0", 218 | "engines": { 219 | "node": ">=12.22" 220 | }, 221 | "funding": { 222 | "type": "github", 223 | "url": "https://github.com/sponsors/nzakas" 224 | } 225 | }, 226 | "node_modules/@humanwhocodes/retry": { 227 | "version": "0.4.2", 228 | "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", 229 | "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", 230 | "dev": true, 231 | "license": "Apache-2.0", 232 | "engines": { 233 | "node": ">=18.18" 234 | }, 235 | "funding": { 236 | "type": "github", 237 | "url": "https://github.com/sponsors/nzakas" 238 | } 239 | }, 240 | "node_modules/@hypermode/modus-sdk-as": { 241 | "version": "0.17.5", 242 | "resolved": "https://registry.npmjs.org/@hypermode/modus-sdk-as/-/modus-sdk-as-0.17.5.tgz", 243 | "integrity": "sha512-QyfIXRZ/fsUXJERASIW3A/Cmpch/nvhFM1eOUneADvlPeNvPu9nQcHzPSKzC9CIPRXrvL9aiamIO813bgxCFdA==", 244 | "license": "Apache-2.0", 245 | "dependencies": { 246 | "@assemblyscript/wasi-shim": "^0.1.0", 247 | "as-base64": "^0.2.0", 248 | "chalk": "^5.4.1", 249 | "json-as": "^1.0.2", 250 | "semver": "^7.7.1", 251 | "xid-ts": "^1.1.4" 252 | }, 253 | "bin": { 254 | "modus-as-build": "bin/build-plugin.js" 255 | }, 256 | "engines": { 257 | "node": ">=22" 258 | } 259 | }, 260 | "node_modules/@nodelib/fs.scandir": { 261 | "version": "2.1.5", 262 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 263 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 264 | "dev": true, 265 | "license": "MIT", 266 | "dependencies": { 267 | "@nodelib/fs.stat": "2.0.5", 268 | "run-parallel": "^1.1.9" 269 | }, 270 | "engines": { 271 | "node": ">= 8" 272 | } 273 | }, 274 | "node_modules/@nodelib/fs.stat": { 275 | "version": "2.0.5", 276 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 277 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 278 | "dev": true, 279 | "license": "MIT", 280 | "engines": { 281 | "node": ">= 8" 282 | } 283 | }, 284 | "node_modules/@nodelib/fs.walk": { 285 | "version": "1.2.8", 286 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 287 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 288 | "dev": true, 289 | "license": "MIT", 290 | "dependencies": { 291 | "@nodelib/fs.scandir": "2.1.5", 292 | "fastq": "^1.6.0" 293 | }, 294 | "engines": { 295 | "node": ">= 8" 296 | } 297 | }, 298 | "node_modules/@types/estree": { 299 | "version": "1.0.6", 300 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", 301 | "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", 302 | "dev": true, 303 | "license": "MIT" 304 | }, 305 | "node_modules/@types/json-schema": { 306 | "version": "7.0.15", 307 | "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", 308 | "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", 309 | "dev": true, 310 | "license": "MIT" 311 | }, 312 | "node_modules/@typescript-eslint/eslint-plugin": { 313 | "version": "8.32.1", 314 | "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz", 315 | "integrity": "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg==", 316 | "dev": true, 317 | "license": "MIT", 318 | "dependencies": { 319 | "@eslint-community/regexpp": "^4.10.0", 320 | "@typescript-eslint/scope-manager": "8.32.1", 321 | "@typescript-eslint/type-utils": "8.32.1", 322 | "@typescript-eslint/utils": "8.32.1", 323 | "@typescript-eslint/visitor-keys": "8.32.1", 324 | "graphemer": "^1.4.0", 325 | "ignore": "^7.0.0", 326 | "natural-compare": "^1.4.0", 327 | "ts-api-utils": "^2.1.0" 328 | }, 329 | "engines": { 330 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 331 | }, 332 | "funding": { 333 | "type": "opencollective", 334 | "url": "https://opencollective.com/typescript-eslint" 335 | }, 336 | "peerDependencies": { 337 | "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", 338 | "eslint": "^8.57.0 || ^9.0.0", 339 | "typescript": ">=4.8.4 <5.9.0" 340 | } 341 | }, 342 | "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { 343 | "version": "7.0.4", 344 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", 345 | "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", 346 | "dev": true, 347 | "license": "MIT", 348 | "engines": { 349 | "node": ">= 4" 350 | } 351 | }, 352 | "node_modules/@typescript-eslint/parser": { 353 | "version": "8.32.1", 354 | "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.1.tgz", 355 | "integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==", 356 | "dev": true, 357 | "license": "MIT", 358 | "dependencies": { 359 | "@typescript-eslint/scope-manager": "8.32.1", 360 | "@typescript-eslint/types": "8.32.1", 361 | "@typescript-eslint/typescript-estree": "8.32.1", 362 | "@typescript-eslint/visitor-keys": "8.32.1", 363 | "debug": "^4.3.4" 364 | }, 365 | "engines": { 366 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 367 | }, 368 | "funding": { 369 | "type": "opencollective", 370 | "url": "https://opencollective.com/typescript-eslint" 371 | }, 372 | "peerDependencies": { 373 | "eslint": "^8.57.0 || ^9.0.0", 374 | "typescript": ">=4.8.4 <5.9.0" 375 | } 376 | }, 377 | "node_modules/@typescript-eslint/scope-manager": { 378 | "version": "8.32.1", 379 | "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz", 380 | "integrity": "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==", 381 | "dev": true, 382 | "license": "MIT", 383 | "dependencies": { 384 | "@typescript-eslint/types": "8.32.1", 385 | "@typescript-eslint/visitor-keys": "8.32.1" 386 | }, 387 | "engines": { 388 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 389 | }, 390 | "funding": { 391 | "type": "opencollective", 392 | "url": "https://opencollective.com/typescript-eslint" 393 | } 394 | }, 395 | "node_modules/@typescript-eslint/type-utils": { 396 | "version": "8.32.1", 397 | "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.32.1.tgz", 398 | "integrity": "sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA==", 399 | "dev": true, 400 | "license": "MIT", 401 | "dependencies": { 402 | "@typescript-eslint/typescript-estree": "8.32.1", 403 | "@typescript-eslint/utils": "8.32.1", 404 | "debug": "^4.3.4", 405 | "ts-api-utils": "^2.1.0" 406 | }, 407 | "engines": { 408 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 409 | }, 410 | "funding": { 411 | "type": "opencollective", 412 | "url": "https://opencollective.com/typescript-eslint" 413 | }, 414 | "peerDependencies": { 415 | "eslint": "^8.57.0 || ^9.0.0", 416 | "typescript": ">=4.8.4 <5.9.0" 417 | } 418 | }, 419 | "node_modules/@typescript-eslint/types": { 420 | "version": "8.32.1", 421 | "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.1.tgz", 422 | "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==", 423 | "dev": true, 424 | "license": "MIT", 425 | "engines": { 426 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 427 | }, 428 | "funding": { 429 | "type": "opencollective", 430 | "url": "https://opencollective.com/typescript-eslint" 431 | } 432 | }, 433 | "node_modules/@typescript-eslint/typescript-estree": { 434 | "version": "8.32.1", 435 | "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz", 436 | "integrity": "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==", 437 | "dev": true, 438 | "license": "MIT", 439 | "dependencies": { 440 | "@typescript-eslint/types": "8.32.1", 441 | "@typescript-eslint/visitor-keys": "8.32.1", 442 | "debug": "^4.3.4", 443 | "fast-glob": "^3.3.2", 444 | "is-glob": "^4.0.3", 445 | "minimatch": "^9.0.4", 446 | "semver": "^7.6.0", 447 | "ts-api-utils": "^2.1.0" 448 | }, 449 | "engines": { 450 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 451 | }, 452 | "funding": { 453 | "type": "opencollective", 454 | "url": "https://opencollective.com/typescript-eslint" 455 | }, 456 | "peerDependencies": { 457 | "typescript": ">=4.8.4 <5.9.0" 458 | } 459 | }, 460 | "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { 461 | "version": "2.0.1", 462 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 463 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 464 | "dev": true, 465 | "license": "MIT", 466 | "dependencies": { 467 | "balanced-match": "^1.0.0" 468 | } 469 | }, 470 | "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { 471 | "version": "9.0.5", 472 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", 473 | "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", 474 | "dev": true, 475 | "license": "ISC", 476 | "dependencies": { 477 | "brace-expansion": "^2.0.1" 478 | }, 479 | "engines": { 480 | "node": ">=16 || 14 >=14.17" 481 | }, 482 | "funding": { 483 | "url": "https://github.com/sponsors/isaacs" 484 | } 485 | }, 486 | "node_modules/@typescript-eslint/utils": { 487 | "version": "8.32.1", 488 | "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.1.tgz", 489 | "integrity": "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==", 490 | "dev": true, 491 | "license": "MIT", 492 | "dependencies": { 493 | "@eslint-community/eslint-utils": "^4.7.0", 494 | "@typescript-eslint/scope-manager": "8.32.1", 495 | "@typescript-eslint/types": "8.32.1", 496 | "@typescript-eslint/typescript-estree": "8.32.1" 497 | }, 498 | "engines": { 499 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 500 | }, 501 | "funding": { 502 | "type": "opencollective", 503 | "url": "https://opencollective.com/typescript-eslint" 504 | }, 505 | "peerDependencies": { 506 | "eslint": "^8.57.0 || ^9.0.0", 507 | "typescript": ">=4.8.4 <5.9.0" 508 | } 509 | }, 510 | "node_modules/@typescript-eslint/visitor-keys": { 511 | "version": "8.32.1", 512 | "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz", 513 | "integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==", 514 | "dev": true, 515 | "license": "MIT", 516 | "dependencies": { 517 | "@typescript-eslint/types": "8.32.1", 518 | "eslint-visitor-keys": "^4.2.0" 519 | }, 520 | "engines": { 521 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 522 | }, 523 | "funding": { 524 | "type": "opencollective", 525 | "url": "https://opencollective.com/typescript-eslint" 526 | } 527 | }, 528 | "node_modules/acorn": { 529 | "version": "8.14.0", 530 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", 531 | "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", 532 | "dev": true, 533 | "license": "MIT", 534 | "bin": { 535 | "acorn": "bin/acorn" 536 | }, 537 | "engines": { 538 | "node": ">=0.4.0" 539 | } 540 | }, 541 | "node_modules/acorn-jsx": { 542 | "version": "5.3.2", 543 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 544 | "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 545 | "dev": true, 546 | "license": "MIT", 547 | "peerDependencies": { 548 | "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" 549 | } 550 | }, 551 | "node_modules/ajv": { 552 | "version": "6.12.6", 553 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 554 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 555 | "dev": true, 556 | "license": "MIT", 557 | "dependencies": { 558 | "fast-deep-equal": "^3.1.1", 559 | "fast-json-stable-stringify": "^2.0.0", 560 | "json-schema-traverse": "^0.4.1", 561 | "uri-js": "^4.2.2" 562 | }, 563 | "funding": { 564 | "type": "github", 565 | "url": "https://github.com/sponsors/epoberezkin" 566 | } 567 | }, 568 | "node_modules/ansi-styles": { 569 | "version": "4.3.0", 570 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 571 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 572 | "dev": true, 573 | "license": "MIT", 574 | "dependencies": { 575 | "color-convert": "^2.0.1" 576 | }, 577 | "engines": { 578 | "node": ">=8" 579 | }, 580 | "funding": { 581 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 582 | } 583 | }, 584 | "node_modules/argparse": { 585 | "version": "2.0.1", 586 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 587 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 588 | "dev": true, 589 | "license": "Python-2.0" 590 | }, 591 | "node_modules/as-base64": { 592 | "version": "0.2.0", 593 | "resolved": "https://registry.npmjs.org/as-base64/-/as-base64-0.2.0.tgz", 594 | "integrity": "sha512-j6JxprAVN4SXUcZiSChuMBf0JJOCdh5OfrHAepluxmFWuDZZm+YjmfNSk8djUHRPEB+Ui5HGvrz46GLvTJf3ig==", 595 | "license": "MIT" 596 | }, 597 | "node_modules/assemblyscript": { 598 | "version": "0.27.36", 599 | "resolved": "https://registry.npmjs.org/assemblyscript/-/assemblyscript-0.27.36.tgz", 600 | "integrity": "sha512-1qX2zf6p7l/mNYv8r21jC/Yft7kX7XKR3xUHw41zvV4xad5lyC8w7jZiwZBGoy64VKZLc+bTDJDWi8Kb70YrHA==", 601 | "dev": true, 602 | "license": "Apache-2.0", 603 | "dependencies": { 604 | "binaryen": "116.0.0-nightly.20240114", 605 | "long": "^5.2.4" 606 | }, 607 | "bin": { 608 | "asc": "bin/asc.js", 609 | "asinit": "bin/asinit.js" 610 | }, 611 | "engines": { 612 | "node": ">=18", 613 | "npm": ">=10" 614 | }, 615 | "funding": { 616 | "type": "opencollective", 617 | "url": "https://opencollective.com/assemblyscript" 618 | } 619 | }, 620 | "node_modules/assemblyscript-prettier": { 621 | "version": "3.0.1", 622 | "resolved": "https://registry.npmjs.org/assemblyscript-prettier/-/assemblyscript-prettier-3.0.1.tgz", 623 | "integrity": "sha512-q5D9rJOr1xG8p2MdcAF3wvIBPrmHaKgenJbmYrwM1T96sHKFeTOfCtgHHmrDnrqoly7wSGfWtbE84omC1IUJEA==", 624 | "dev": true, 625 | "license": "MIT", 626 | "dependencies": { 627 | "assemblyscript": "~0.27.0" 628 | }, 629 | "peerDependencies": { 630 | "prettier": "^3.0.0" 631 | } 632 | }, 633 | "node_modules/balanced-match": { 634 | "version": "1.0.2", 635 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 636 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 637 | "dev": true, 638 | "license": "MIT" 639 | }, 640 | "node_modules/binaryen": { 641 | "version": "116.0.0-nightly.20240114", 642 | "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-116.0.0-nightly.20240114.tgz", 643 | "integrity": "sha512-0GZrojJnuhoe+hiwji7QFaL3tBlJoA+KFUN7ouYSDGZLSo9CKM8swQX8n/UcbR0d1VuZKU+nhogNzv423JEu5A==", 644 | "dev": true, 645 | "license": "Apache-2.0", 646 | "bin": { 647 | "wasm-opt": "bin/wasm-opt", 648 | "wasm2js": "bin/wasm2js" 649 | } 650 | }, 651 | "node_modules/brace-expansion": { 652 | "version": "1.1.11", 653 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 654 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 655 | "dev": true, 656 | "license": "MIT", 657 | "dependencies": { 658 | "balanced-match": "^1.0.0", 659 | "concat-map": "0.0.1" 660 | } 661 | }, 662 | "node_modules/braces": { 663 | "version": "3.0.3", 664 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 665 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 666 | "dev": true, 667 | "license": "MIT", 668 | "dependencies": { 669 | "fill-range": "^7.1.1" 670 | }, 671 | "engines": { 672 | "node": ">=8" 673 | } 674 | }, 675 | "node_modules/callsites": { 676 | "version": "3.1.0", 677 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 678 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 679 | "dev": true, 680 | "license": "MIT", 681 | "engines": { 682 | "node": ">=6" 683 | } 684 | }, 685 | "node_modules/chalk": { 686 | "version": "5.4.1", 687 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", 688 | "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", 689 | "engines": { 690 | "node": "^12.17.0 || ^14.13 || >=16.0.0" 691 | }, 692 | "funding": { 693 | "url": "https://github.com/chalk/chalk?sponsor=1" 694 | } 695 | }, 696 | "node_modules/color-convert": { 697 | "version": "2.0.1", 698 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 699 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 700 | "dev": true, 701 | "license": "MIT", 702 | "dependencies": { 703 | "color-name": "~1.1.4" 704 | }, 705 | "engines": { 706 | "node": ">=7.0.0" 707 | } 708 | }, 709 | "node_modules/color-name": { 710 | "version": "1.1.4", 711 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 712 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 713 | "dev": true, 714 | "license": "MIT" 715 | }, 716 | "node_modules/concat-map": { 717 | "version": "0.0.1", 718 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 719 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 720 | "dev": true, 721 | "license": "MIT" 722 | }, 723 | "node_modules/cross-spawn": { 724 | "version": "7.0.6", 725 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 726 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 727 | "dev": true, 728 | "dependencies": { 729 | "path-key": "^3.1.0", 730 | "shebang-command": "^2.0.0", 731 | "which": "^2.0.1" 732 | }, 733 | "engines": { 734 | "node": ">= 8" 735 | } 736 | }, 737 | "node_modules/debug": { 738 | "version": "4.4.0", 739 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 740 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 741 | "dev": true, 742 | "license": "MIT", 743 | "dependencies": { 744 | "ms": "^2.1.3" 745 | }, 746 | "engines": { 747 | "node": ">=6.0" 748 | }, 749 | "peerDependenciesMeta": { 750 | "supports-color": { 751 | "optional": true 752 | } 753 | } 754 | }, 755 | "node_modules/deep-is": { 756 | "version": "0.1.4", 757 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 758 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 759 | "dev": true, 760 | "license": "MIT" 761 | }, 762 | "node_modules/escape-string-regexp": { 763 | "version": "4.0.0", 764 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 765 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 766 | "dev": true, 767 | "license": "MIT", 768 | "engines": { 769 | "node": ">=10" 770 | }, 771 | "funding": { 772 | "url": "https://github.com/sponsors/sindresorhus" 773 | } 774 | }, 775 | "node_modules/eslint": { 776 | "version": "9.27.0", 777 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", 778 | "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", 779 | "dev": true, 780 | "license": "MIT", 781 | "dependencies": { 782 | "@eslint-community/eslint-utils": "^4.2.0", 783 | "@eslint-community/regexpp": "^4.12.1", 784 | "@eslint/config-array": "^0.20.0", 785 | "@eslint/config-helpers": "^0.2.1", 786 | "@eslint/core": "^0.14.0", 787 | "@eslint/eslintrc": "^3.3.1", 788 | "@eslint/js": "9.27.0", 789 | "@eslint/plugin-kit": "^0.3.1", 790 | "@humanfs/node": "^0.16.6", 791 | "@humanwhocodes/module-importer": "^1.0.1", 792 | "@humanwhocodes/retry": "^0.4.2", 793 | "@types/estree": "^1.0.6", 794 | "@types/json-schema": "^7.0.15", 795 | "ajv": "^6.12.4", 796 | "chalk": "^4.0.0", 797 | "cross-spawn": "^7.0.6", 798 | "debug": "^4.3.2", 799 | "escape-string-regexp": "^4.0.0", 800 | "eslint-scope": "^8.3.0", 801 | "eslint-visitor-keys": "^4.2.0", 802 | "espree": "^10.3.0", 803 | "esquery": "^1.5.0", 804 | "esutils": "^2.0.2", 805 | "fast-deep-equal": "^3.1.3", 806 | "file-entry-cache": "^8.0.0", 807 | "find-up": "^5.0.0", 808 | "glob-parent": "^6.0.2", 809 | "ignore": "^5.2.0", 810 | "imurmurhash": "^0.1.4", 811 | "is-glob": "^4.0.0", 812 | "json-stable-stringify-without-jsonify": "^1.0.1", 813 | "lodash.merge": "^4.6.2", 814 | "minimatch": "^3.1.2", 815 | "natural-compare": "^1.4.0", 816 | "optionator": "^0.9.3" 817 | }, 818 | "bin": { 819 | "eslint": "bin/eslint.js" 820 | }, 821 | "engines": { 822 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 823 | }, 824 | "funding": { 825 | "url": "https://eslint.org/donate" 826 | }, 827 | "peerDependencies": { 828 | "jiti": "*" 829 | }, 830 | "peerDependenciesMeta": { 831 | "jiti": { 832 | "optional": true 833 | } 834 | } 835 | }, 836 | "node_modules/eslint-scope": { 837 | "version": "8.3.0", 838 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", 839 | "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", 840 | "dev": true, 841 | "license": "BSD-2-Clause", 842 | "dependencies": { 843 | "esrecurse": "^4.3.0", 844 | "estraverse": "^5.2.0" 845 | }, 846 | "engines": { 847 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 848 | }, 849 | "funding": { 850 | "url": "https://opencollective.com/eslint" 851 | } 852 | }, 853 | "node_modules/eslint-visitor-keys": { 854 | "version": "4.2.0", 855 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", 856 | "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", 857 | "dev": true, 858 | "license": "Apache-2.0", 859 | "engines": { 860 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 861 | }, 862 | "funding": { 863 | "url": "https://opencollective.com/eslint" 864 | } 865 | }, 866 | "node_modules/eslint/node_modules/chalk": { 867 | "version": "4.1.2", 868 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 869 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 870 | "dev": true, 871 | "license": "MIT", 872 | "dependencies": { 873 | "ansi-styles": "^4.1.0", 874 | "supports-color": "^7.1.0" 875 | }, 876 | "engines": { 877 | "node": ">=10" 878 | }, 879 | "funding": { 880 | "url": "https://github.com/chalk/chalk?sponsor=1" 881 | } 882 | }, 883 | "node_modules/espree": { 884 | "version": "10.3.0", 885 | "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", 886 | "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", 887 | "dev": true, 888 | "license": "BSD-2-Clause", 889 | "dependencies": { 890 | "acorn": "^8.14.0", 891 | "acorn-jsx": "^5.3.2", 892 | "eslint-visitor-keys": "^4.2.0" 893 | }, 894 | "engines": { 895 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 896 | }, 897 | "funding": { 898 | "url": "https://opencollective.com/eslint" 899 | } 900 | }, 901 | "node_modules/esquery": { 902 | "version": "1.6.0", 903 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", 904 | "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", 905 | "dev": true, 906 | "license": "BSD-3-Clause", 907 | "dependencies": { 908 | "estraverse": "^5.1.0" 909 | }, 910 | "engines": { 911 | "node": ">=0.10" 912 | } 913 | }, 914 | "node_modules/esrecurse": { 915 | "version": "4.3.0", 916 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 917 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 918 | "dev": true, 919 | "license": "BSD-2-Clause", 920 | "dependencies": { 921 | "estraverse": "^5.2.0" 922 | }, 923 | "engines": { 924 | "node": ">=4.0" 925 | } 926 | }, 927 | "node_modules/estraverse": { 928 | "version": "5.3.0", 929 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 930 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 931 | "dev": true, 932 | "license": "BSD-2-Clause", 933 | "engines": { 934 | "node": ">=4.0" 935 | } 936 | }, 937 | "node_modules/esutils": { 938 | "version": "2.0.3", 939 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 940 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 941 | "dev": true, 942 | "license": "BSD-2-Clause", 943 | "engines": { 944 | "node": ">=0.10.0" 945 | } 946 | }, 947 | "node_modules/fast-deep-equal": { 948 | "version": "3.1.3", 949 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 950 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 951 | "dev": true, 952 | "license": "MIT" 953 | }, 954 | "node_modules/fast-glob": { 955 | "version": "3.3.3", 956 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", 957 | "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", 958 | "dev": true, 959 | "license": "MIT", 960 | "dependencies": { 961 | "@nodelib/fs.stat": "^2.0.2", 962 | "@nodelib/fs.walk": "^1.2.3", 963 | "glob-parent": "^5.1.2", 964 | "merge2": "^1.3.0", 965 | "micromatch": "^4.0.8" 966 | }, 967 | "engines": { 968 | "node": ">=8.6.0" 969 | } 970 | }, 971 | "node_modules/fast-glob/node_modules/glob-parent": { 972 | "version": "5.1.2", 973 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 974 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 975 | "dev": true, 976 | "license": "ISC", 977 | "dependencies": { 978 | "is-glob": "^4.0.1" 979 | }, 980 | "engines": { 981 | "node": ">= 6" 982 | } 983 | }, 984 | "node_modules/fast-json-stable-stringify": { 985 | "version": "2.1.0", 986 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 987 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 988 | "dev": true, 989 | "license": "MIT" 990 | }, 991 | "node_modules/fast-levenshtein": { 992 | "version": "2.0.6", 993 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 994 | "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", 995 | "dev": true, 996 | "license": "MIT" 997 | }, 998 | "node_modules/fastq": { 999 | "version": "1.19.1", 1000 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", 1001 | "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", 1002 | "dev": true, 1003 | "license": "ISC", 1004 | "dependencies": { 1005 | "reusify": "^1.0.4" 1006 | } 1007 | }, 1008 | "node_modules/file-entry-cache": { 1009 | "version": "8.0.0", 1010 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", 1011 | "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", 1012 | "dev": true, 1013 | "license": "MIT", 1014 | "dependencies": { 1015 | "flat-cache": "^4.0.0" 1016 | }, 1017 | "engines": { 1018 | "node": ">=16.0.0" 1019 | } 1020 | }, 1021 | "node_modules/fill-range": { 1022 | "version": "7.1.1", 1023 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 1024 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 1025 | "dev": true, 1026 | "license": "MIT", 1027 | "dependencies": { 1028 | "to-regex-range": "^5.0.1" 1029 | }, 1030 | "engines": { 1031 | "node": ">=8" 1032 | } 1033 | }, 1034 | "node_modules/find-up": { 1035 | "version": "5.0.0", 1036 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 1037 | "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 1038 | "dev": true, 1039 | "license": "MIT", 1040 | "dependencies": { 1041 | "locate-path": "^6.0.0", 1042 | "path-exists": "^4.0.0" 1043 | }, 1044 | "engines": { 1045 | "node": ">=10" 1046 | }, 1047 | "funding": { 1048 | "url": "https://github.com/sponsors/sindresorhus" 1049 | } 1050 | }, 1051 | "node_modules/flat-cache": { 1052 | "version": "4.0.1", 1053 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", 1054 | "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", 1055 | "dev": true, 1056 | "license": "MIT", 1057 | "dependencies": { 1058 | "flatted": "^3.2.9", 1059 | "keyv": "^4.5.4" 1060 | }, 1061 | "engines": { 1062 | "node": ">=16" 1063 | } 1064 | }, 1065 | "node_modules/flatted": { 1066 | "version": "3.3.1", 1067 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", 1068 | "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", 1069 | "dev": true, 1070 | "license": "ISC" 1071 | }, 1072 | "node_modules/glob-parent": { 1073 | "version": "6.0.2", 1074 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 1075 | "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 1076 | "dev": true, 1077 | "license": "ISC", 1078 | "dependencies": { 1079 | "is-glob": "^4.0.3" 1080 | }, 1081 | "engines": { 1082 | "node": ">=10.13.0" 1083 | } 1084 | }, 1085 | "node_modules/globals": { 1086 | "version": "14.0.0", 1087 | "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", 1088 | "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", 1089 | "dev": true, 1090 | "license": "MIT", 1091 | "engines": { 1092 | "node": ">=18" 1093 | }, 1094 | "funding": { 1095 | "url": "https://github.com/sponsors/sindresorhus" 1096 | } 1097 | }, 1098 | "node_modules/graphemer": { 1099 | "version": "1.4.0", 1100 | "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", 1101 | "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", 1102 | "dev": true 1103 | }, 1104 | "node_modules/has-flag": { 1105 | "version": "4.0.0", 1106 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 1107 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 1108 | "dev": true, 1109 | "license": "MIT", 1110 | "engines": { 1111 | "node": ">=8" 1112 | } 1113 | }, 1114 | "node_modules/ignore": { 1115 | "version": "5.3.2", 1116 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", 1117 | "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", 1118 | "dev": true, 1119 | "license": "MIT", 1120 | "engines": { 1121 | "node": ">= 4" 1122 | } 1123 | }, 1124 | "node_modules/import-fresh": { 1125 | "version": "3.3.0", 1126 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", 1127 | "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", 1128 | "dev": true, 1129 | "license": "MIT", 1130 | "dependencies": { 1131 | "parent-module": "^1.0.0", 1132 | "resolve-from": "^4.0.0" 1133 | }, 1134 | "engines": { 1135 | "node": ">=6" 1136 | }, 1137 | "funding": { 1138 | "url": "https://github.com/sponsors/sindresorhus" 1139 | } 1140 | }, 1141 | "node_modules/imurmurhash": { 1142 | "version": "0.1.4", 1143 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 1144 | "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 1145 | "dev": true, 1146 | "license": "MIT", 1147 | "engines": { 1148 | "node": ">=0.8.19" 1149 | } 1150 | }, 1151 | "node_modules/is-extglob": { 1152 | "version": "2.1.1", 1153 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1154 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 1155 | "dev": true, 1156 | "license": "MIT", 1157 | "engines": { 1158 | "node": ">=0.10.0" 1159 | } 1160 | }, 1161 | "node_modules/is-glob": { 1162 | "version": "4.0.3", 1163 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1164 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1165 | "dev": true, 1166 | "license": "MIT", 1167 | "dependencies": { 1168 | "is-extglob": "^2.1.1" 1169 | }, 1170 | "engines": { 1171 | "node": ">=0.10.0" 1172 | } 1173 | }, 1174 | "node_modules/is-number": { 1175 | "version": "7.0.0", 1176 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1177 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1178 | "dev": true, 1179 | "license": "MIT", 1180 | "engines": { 1181 | "node": ">=0.12.0" 1182 | } 1183 | }, 1184 | "node_modules/isexe": { 1185 | "version": "2.0.0", 1186 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1187 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 1188 | "dev": true 1189 | }, 1190 | "node_modules/js-yaml": { 1191 | "version": "4.1.0", 1192 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 1193 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 1194 | "dev": true, 1195 | "license": "MIT", 1196 | "dependencies": { 1197 | "argparse": "^2.0.1" 1198 | }, 1199 | "bin": { 1200 | "js-yaml": "bin/js-yaml.js" 1201 | } 1202 | }, 1203 | "node_modules/json-as": { 1204 | "version": "1.1.14", 1205 | "resolved": "https://registry.npmjs.org/json-as/-/json-as-1.1.14.tgz", 1206 | "integrity": "sha512-aVnYGPyO7v3AufF1ayyiGXXUQhfoCVabhhExhvEKadDHEuABN4MW9WCjZOmhwttD2rWxc9yj5lTaVIqNSwi+Ww==", 1207 | "license": "MIT" 1208 | }, 1209 | "node_modules/json-buffer": { 1210 | "version": "3.0.1", 1211 | "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", 1212 | "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", 1213 | "dev": true, 1214 | "license": "MIT" 1215 | }, 1216 | "node_modules/json-schema-traverse": { 1217 | "version": "0.4.1", 1218 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 1219 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 1220 | "dev": true, 1221 | "license": "MIT" 1222 | }, 1223 | "node_modules/json-stable-stringify-without-jsonify": { 1224 | "version": "1.0.1", 1225 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 1226 | "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", 1227 | "dev": true, 1228 | "license": "MIT" 1229 | }, 1230 | "node_modules/keyv": { 1231 | "version": "4.5.4", 1232 | "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", 1233 | "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", 1234 | "dev": true, 1235 | "license": "MIT", 1236 | "dependencies": { 1237 | "json-buffer": "3.0.1" 1238 | } 1239 | }, 1240 | "node_modules/levn": { 1241 | "version": "0.4.1", 1242 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 1243 | "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 1244 | "dev": true, 1245 | "license": "MIT", 1246 | "dependencies": { 1247 | "prelude-ls": "^1.2.1", 1248 | "type-check": "~0.4.0" 1249 | }, 1250 | "engines": { 1251 | "node": ">= 0.8.0" 1252 | } 1253 | }, 1254 | "node_modules/locate-path": { 1255 | "version": "6.0.0", 1256 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 1257 | "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 1258 | "dev": true, 1259 | "license": "MIT", 1260 | "dependencies": { 1261 | "p-locate": "^5.0.0" 1262 | }, 1263 | "engines": { 1264 | "node": ">=10" 1265 | }, 1266 | "funding": { 1267 | "url": "https://github.com/sponsors/sindresorhus" 1268 | } 1269 | }, 1270 | "node_modules/lodash.merge": { 1271 | "version": "4.6.2", 1272 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 1273 | "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 1274 | "dev": true, 1275 | "license": "MIT" 1276 | }, 1277 | "node_modules/long": { 1278 | "version": "5.3.0", 1279 | "resolved": "https://registry.npmjs.org/long/-/long-5.3.0.tgz", 1280 | "integrity": "sha512-5vvY5yF1zF/kXk+L94FRiTDa1Znom46UjPCH6/XbSvS8zBKMFBHTJk8KDMqJ+2J6QezQFi7k1k8v21ClJYHPaw==", 1281 | "dev": true, 1282 | "license": "Apache-2.0" 1283 | }, 1284 | "node_modules/merge2": { 1285 | "version": "1.4.1", 1286 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 1287 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 1288 | "dev": true, 1289 | "license": "MIT", 1290 | "engines": { 1291 | "node": ">= 8" 1292 | } 1293 | }, 1294 | "node_modules/micromatch": { 1295 | "version": "4.0.8", 1296 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", 1297 | "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", 1298 | "dev": true, 1299 | "license": "MIT", 1300 | "dependencies": { 1301 | "braces": "^3.0.3", 1302 | "picomatch": "^2.3.1" 1303 | }, 1304 | "engines": { 1305 | "node": ">=8.6" 1306 | } 1307 | }, 1308 | "node_modules/minimatch": { 1309 | "version": "3.1.2", 1310 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1311 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1312 | "dev": true, 1313 | "license": "ISC", 1314 | "dependencies": { 1315 | "brace-expansion": "^1.1.7" 1316 | }, 1317 | "engines": { 1318 | "node": "*" 1319 | } 1320 | }, 1321 | "node_modules/ms": { 1322 | "version": "2.1.3", 1323 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1324 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1325 | "dev": true, 1326 | "license": "MIT" 1327 | }, 1328 | "node_modules/natural-compare": { 1329 | "version": "1.4.0", 1330 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 1331 | "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", 1332 | "dev": true, 1333 | "license": "MIT" 1334 | }, 1335 | "node_modules/optionator": { 1336 | "version": "0.9.4", 1337 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", 1338 | "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", 1339 | "dev": true, 1340 | "license": "MIT", 1341 | "dependencies": { 1342 | "deep-is": "^0.1.3", 1343 | "fast-levenshtein": "^2.0.6", 1344 | "levn": "^0.4.1", 1345 | "prelude-ls": "^1.2.1", 1346 | "type-check": "^0.4.0", 1347 | "word-wrap": "^1.2.5" 1348 | }, 1349 | "engines": { 1350 | "node": ">= 0.8.0" 1351 | } 1352 | }, 1353 | "node_modules/p-limit": { 1354 | "version": "3.1.0", 1355 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 1356 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 1357 | "dev": true, 1358 | "license": "MIT", 1359 | "dependencies": { 1360 | "yocto-queue": "^0.1.0" 1361 | }, 1362 | "engines": { 1363 | "node": ">=10" 1364 | }, 1365 | "funding": { 1366 | "url": "https://github.com/sponsors/sindresorhus" 1367 | } 1368 | }, 1369 | "node_modules/p-locate": { 1370 | "version": "5.0.0", 1371 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 1372 | "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 1373 | "dev": true, 1374 | "license": "MIT", 1375 | "dependencies": { 1376 | "p-limit": "^3.0.2" 1377 | }, 1378 | "engines": { 1379 | "node": ">=10" 1380 | }, 1381 | "funding": { 1382 | "url": "https://github.com/sponsors/sindresorhus" 1383 | } 1384 | }, 1385 | "node_modules/parent-module": { 1386 | "version": "1.0.1", 1387 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 1388 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 1389 | "dev": true, 1390 | "license": "MIT", 1391 | "dependencies": { 1392 | "callsites": "^3.0.0" 1393 | }, 1394 | "engines": { 1395 | "node": ">=6" 1396 | } 1397 | }, 1398 | "node_modules/path-exists": { 1399 | "version": "4.0.0", 1400 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 1401 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 1402 | "dev": true, 1403 | "license": "MIT", 1404 | "engines": { 1405 | "node": ">=8" 1406 | } 1407 | }, 1408 | "node_modules/path-key": { 1409 | "version": "3.1.1", 1410 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1411 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1412 | "dev": true, 1413 | "engines": { 1414 | "node": ">=8" 1415 | } 1416 | }, 1417 | "node_modules/picomatch": { 1418 | "version": "2.3.1", 1419 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1420 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1421 | "dev": true, 1422 | "license": "MIT", 1423 | "engines": { 1424 | "node": ">=8.6" 1425 | }, 1426 | "funding": { 1427 | "url": "https://github.com/sponsors/jonschlinkert" 1428 | } 1429 | }, 1430 | "node_modules/prelude-ls": { 1431 | "version": "1.2.1", 1432 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 1433 | "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 1434 | "dev": true, 1435 | "license": "MIT", 1436 | "engines": { 1437 | "node": ">= 0.8.0" 1438 | } 1439 | }, 1440 | "node_modules/prettier": { 1441 | "version": "3.5.3", 1442 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", 1443 | "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", 1444 | "dev": true, 1445 | "license": "MIT", 1446 | "bin": { 1447 | "prettier": "bin/prettier.cjs" 1448 | }, 1449 | "engines": { 1450 | "node": ">=14" 1451 | }, 1452 | "funding": { 1453 | "url": "https://github.com/prettier/prettier?sponsor=1" 1454 | } 1455 | }, 1456 | "node_modules/punycode": { 1457 | "version": "2.3.1", 1458 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 1459 | "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 1460 | "dev": true, 1461 | "license": "MIT", 1462 | "engines": { 1463 | "node": ">=6" 1464 | } 1465 | }, 1466 | "node_modules/queue-microtask": { 1467 | "version": "1.2.3", 1468 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 1469 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 1470 | "dev": true, 1471 | "funding": [ 1472 | { 1473 | "type": "github", 1474 | "url": "https://github.com/sponsors/feross" 1475 | }, 1476 | { 1477 | "type": "patreon", 1478 | "url": "https://www.patreon.com/feross" 1479 | }, 1480 | { 1481 | "type": "consulting", 1482 | "url": "https://feross.org/support" 1483 | } 1484 | ], 1485 | "license": "MIT" 1486 | }, 1487 | "node_modules/resolve-from": { 1488 | "version": "4.0.0", 1489 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 1490 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 1491 | "dev": true, 1492 | "license": "MIT", 1493 | "engines": { 1494 | "node": ">=4" 1495 | } 1496 | }, 1497 | "node_modules/reusify": { 1498 | "version": "1.1.0", 1499 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", 1500 | "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", 1501 | "dev": true, 1502 | "license": "MIT", 1503 | "engines": { 1504 | "iojs": ">=1.0.0", 1505 | "node": ">=0.10.0" 1506 | } 1507 | }, 1508 | "node_modules/run-parallel": { 1509 | "version": "1.2.0", 1510 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 1511 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 1512 | "dev": true, 1513 | "funding": [ 1514 | { 1515 | "type": "github", 1516 | "url": "https://github.com/sponsors/feross" 1517 | }, 1518 | { 1519 | "type": "patreon", 1520 | "url": "https://www.patreon.com/feross" 1521 | }, 1522 | { 1523 | "type": "consulting", 1524 | "url": "https://feross.org/support" 1525 | } 1526 | ], 1527 | "license": "MIT", 1528 | "dependencies": { 1529 | "queue-microtask": "^1.2.2" 1530 | } 1531 | }, 1532 | "node_modules/semver": { 1533 | "version": "7.7.1", 1534 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", 1535 | "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", 1536 | "license": "ISC", 1537 | "bin": { 1538 | "semver": "bin/semver.js" 1539 | }, 1540 | "engines": { 1541 | "node": ">=10" 1542 | } 1543 | }, 1544 | "node_modules/shebang-command": { 1545 | "version": "2.0.0", 1546 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1547 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1548 | "dev": true, 1549 | "dependencies": { 1550 | "shebang-regex": "^3.0.0" 1551 | }, 1552 | "engines": { 1553 | "node": ">=8" 1554 | } 1555 | }, 1556 | "node_modules/shebang-regex": { 1557 | "version": "3.0.0", 1558 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1559 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1560 | "dev": true, 1561 | "engines": { 1562 | "node": ">=8" 1563 | } 1564 | }, 1565 | "node_modules/strip-json-comments": { 1566 | "version": "3.1.1", 1567 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 1568 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 1569 | "dev": true, 1570 | "license": "MIT", 1571 | "engines": { 1572 | "node": ">=8" 1573 | }, 1574 | "funding": { 1575 | "url": "https://github.com/sponsors/sindresorhus" 1576 | } 1577 | }, 1578 | "node_modules/supports-color": { 1579 | "version": "7.2.0", 1580 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 1581 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 1582 | "dev": true, 1583 | "license": "MIT", 1584 | "dependencies": { 1585 | "has-flag": "^4.0.0" 1586 | }, 1587 | "engines": { 1588 | "node": ">=8" 1589 | } 1590 | }, 1591 | "node_modules/to-regex-range": { 1592 | "version": "5.0.1", 1593 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1594 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1595 | "dev": true, 1596 | "license": "MIT", 1597 | "dependencies": { 1598 | "is-number": "^7.0.0" 1599 | }, 1600 | "engines": { 1601 | "node": ">=8.0" 1602 | } 1603 | }, 1604 | "node_modules/ts-api-utils": { 1605 | "version": "2.1.0", 1606 | "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", 1607 | "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", 1608 | "dev": true, 1609 | "license": "MIT", 1610 | "engines": { 1611 | "node": ">=18.12" 1612 | }, 1613 | "peerDependencies": { 1614 | "typescript": ">=4.8.4" 1615 | } 1616 | }, 1617 | "node_modules/type-check": { 1618 | "version": "0.4.0", 1619 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 1620 | "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 1621 | "dev": true, 1622 | "license": "MIT", 1623 | "dependencies": { 1624 | "prelude-ls": "^1.2.1" 1625 | }, 1626 | "engines": { 1627 | "node": ">= 0.8.0" 1628 | } 1629 | }, 1630 | "node_modules/typescript": { 1631 | "version": "5.8.3", 1632 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", 1633 | "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", 1634 | "dev": true, 1635 | "license": "Apache-2.0", 1636 | "bin": { 1637 | "tsc": "bin/tsc", 1638 | "tsserver": "bin/tsserver" 1639 | }, 1640 | "engines": { 1641 | "node": ">=14.17" 1642 | } 1643 | }, 1644 | "node_modules/typescript-eslint": { 1645 | "version": "8.32.1", 1646 | "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.32.1.tgz", 1647 | "integrity": "sha512-D7el+eaDHAmXvrZBy1zpzSNIRqnCOrkwTgZxTu3MUqRWk8k0q9m9Ho4+vPf7iHtgUfrK/o8IZaEApsxPlHTFCg==", 1648 | "dev": true, 1649 | "license": "MIT", 1650 | "dependencies": { 1651 | "@typescript-eslint/eslint-plugin": "8.32.1", 1652 | "@typescript-eslint/parser": "8.32.1", 1653 | "@typescript-eslint/utils": "8.32.1" 1654 | }, 1655 | "engines": { 1656 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1657 | }, 1658 | "funding": { 1659 | "type": "opencollective", 1660 | "url": "https://opencollective.com/typescript-eslint" 1661 | }, 1662 | "peerDependencies": { 1663 | "eslint": "^8.57.0 || ^9.0.0", 1664 | "typescript": ">=4.8.4 <5.9.0" 1665 | } 1666 | }, 1667 | "node_modules/uri-js": { 1668 | "version": "4.4.1", 1669 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 1670 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 1671 | "dev": true, 1672 | "license": "BSD-2-Clause", 1673 | "dependencies": { 1674 | "punycode": "^2.1.0" 1675 | } 1676 | }, 1677 | "node_modules/which": { 1678 | "version": "2.0.2", 1679 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1680 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1681 | "dev": true, 1682 | "dependencies": { 1683 | "isexe": "^2.0.0" 1684 | }, 1685 | "bin": { 1686 | "node-which": "bin/node-which" 1687 | }, 1688 | "engines": { 1689 | "node": ">= 8" 1690 | } 1691 | }, 1692 | "node_modules/word-wrap": { 1693 | "version": "1.2.5", 1694 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", 1695 | "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", 1696 | "dev": true, 1697 | "license": "MIT", 1698 | "engines": { 1699 | "node": ">=0.10.0" 1700 | } 1701 | }, 1702 | "node_modules/xid-ts": { 1703 | "version": "1.1.4", 1704 | "resolved": "https://registry.npmjs.org/xid-ts/-/xid-ts-1.1.4.tgz", 1705 | "integrity": "sha512-bdBn9nW1e8O/dcrNYhAHJ+Iabe384mLoPkRypAylyazI+AfJnV0JMIoH/1DUnsdcCHLn/ZEH7Pt5kRUH4xnJdA==", 1706 | "license": "MIT", 1707 | "engines": { 1708 | "node": ">=18.0.0" 1709 | } 1710 | }, 1711 | "node_modules/yocto-queue": { 1712 | "version": "0.1.0", 1713 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 1714 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 1715 | "dev": true, 1716 | "license": "MIT", 1717 | "engines": { 1718 | "node": ">=10" 1719 | }, 1720 | "funding": { 1721 | "url": "https://github.com/sponsors/sindresorhus" 1722 | } 1723 | } 1724 | } 1725 | } 1726 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hyper-commerce", 3 | "private": true, 4 | "description": "Hypermode Commerce", 5 | "author": "Hypermode Inc.", 6 | "license": "MIT", 7 | "type": "module", 8 | "scripts": { 9 | "build": "modus-as-build", 10 | "lint": "eslint .", 11 | "pretty": "prettier --write .", 12 | "pretty:check": "prettier --check ." 13 | }, 14 | "dependencies": { 15 | "@hypermode/modus-sdk-as": "0.17.5", 16 | "json-as": "1.1.14" 17 | }, 18 | "devDependencies": { 19 | "@eslint/js": "9.27.0", 20 | "assemblyscript": "0.27.36", 21 | "assemblyscript-prettier": "3.0.1", 22 | "eslint": "9.27.0", 23 | "prettier": "3.5.3", 24 | "typescript": "5.8.3", 25 | "typescript-eslint": "8.32.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /frontend/.env.example: -------------------------------------------------------------------------------- 1 | HYPERMODE_API_TOKEN=XXXX 2 | HYPERMODE_API_ENDPOINT=XXXX -------------------------------------------------------------------------------- /frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | .env 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Hypermode Commerce Frontend 2 | 3 | This frontend for Hypermode's [composable-commerce storefront demo](https://github.com/hypermodeinc/hyper-commerce) highlights its vector search capabilities. It provides a seamless user experience for searching a 10,000-item catalog with lightning-fast semantic search, delivering results in under 200ms. The search functionality ranks results by relevance, ratings, and stock status, and offers options to limit results and filter by minimum star rating. You can view the demo site [here](https://www.hypermode-commerce.com/). 4 | 5 | ## Running Locally 6 | 7 | To run this project locally, start by setting up your environment variables. Copy the definitions from [`.env.example`](https://github.com/hypermodeinc/hyper-commerce-frontend/blob/main/.env.example) into a new file named `.env.local` at the root of your project, and provide the values from your Hypermode dashboard. 8 | 9 | Once your environment variables are configured, install the necessary dependencies with: 10 | 11 | ``` 12 | pnpm install 13 | ``` 14 | 15 | Then, start the development server using: 16 | 17 | ``` 18 | pnpm dev 19 | ``` 20 | 21 | Your app should be up and running at [localhost:3000](http://localhost:3000/) 22 | 23 | ## Deploy with Vercel 24 | 25 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/hypermodeinc/hyper-commerce-frontend) 26 | 27 | *NOTES*: Make sure your environment variables are added to your Vercel project. 28 | -------------------------------------------------------------------------------- /frontend/app/actions.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { revalidateTag } from "next/cache"; 3 | import { cookies } from "next/headers"; 4 | import { TAGS } from "@/lib/constants"; 5 | 6 | type FetchQueryProps = { 7 | query: string; 8 | variables?: any; 9 | }; 10 | 11 | const fetchQuery = async ({ query, variables }: FetchQueryProps) => { 12 | try { 13 | const res = await fetch(process.env.HYPERMODE_API_ENDPOINT as string, { 14 | method: "POST", 15 | headers: { 16 | "Content-Type": "application/json", 17 | Authorization: `Bearer ${process.env.HYPERMODE_API_TOKEN}`, 18 | }, 19 | body: JSON.stringify({ 20 | query, 21 | variables, 22 | }), 23 | // cache: "no-store", 24 | }); 25 | 26 | if (res.status < 200 || res.status >= 300) { 27 | throw new Error(res.statusText); 28 | } 29 | 30 | const { data, error, errors } = await res.json(); 31 | return { data, error: error || errors }; 32 | } catch (err) { 33 | console.error("error in fetchQuery:", err); 34 | return { data: null, error: err }; 35 | } 36 | }; 37 | 38 | export async function searchProducts( 39 | query: string, 40 | maxItems: number, 41 | thresholdStars: number, 42 | inStockOnly: boolean = false 43 | ) { 44 | const graphqlQuery = ` 45 | query searchProducts($query: String!, $maxItems: Int!, $thresholdStars: Float!, $inStockOnly: Boolean!) { 46 | searchProducts(query: $query, maxItems: $maxItems, thresholdStars: $thresholdStars, inStockOnly: $inStockOnly) { 47 | searchObjs { 48 | product { 49 | name 50 | id 51 | image 52 | description 53 | stars 54 | price 55 | isStocked 56 | category 57 | } 58 | } 59 | } 60 | } 61 | `; 62 | 63 | const { error, data } = await fetchQuery({ 64 | query: graphqlQuery, 65 | variables: { 66 | query, 67 | maxItems, 68 | thresholdStars, 69 | inStockOnly, 70 | }, 71 | }); 72 | 73 | if (error) { 74 | return { error: Array.isArray(error) ? error[0] : error }; 75 | } else { 76 | return { data }; 77 | } 78 | } 79 | 80 | export async function getProduct(id: string) { 81 | const graphqlQuery = ` 82 | query getProduct($id: String!) { 83 | product(id: $id) { 84 | name 85 | description 86 | stars 87 | price 88 | image 89 | isStocked 90 | } 91 | } 92 | `; 93 | 94 | const { error, data } = await fetchQuery({ 95 | query: graphqlQuery, 96 | variables: { id }, 97 | }); 98 | 99 | if (error) { 100 | return { error: Array.isArray(error) ? error[0] : error }; 101 | } else { 102 | return { data }; 103 | } 104 | } 105 | 106 | export async function generateSearchObjectFromLLM(text: string) { 107 | const graphqlQuery = ` 108 | query generateSearchObjectFromLLM($text: String!) { 109 | generateSearchObjectFromLLM(text: $text) { 110 | userResponse 111 | } 112 | } 113 | `; 114 | 115 | const { error, data } = await fetchQuery({ 116 | query: graphqlQuery, 117 | variables: { text }, 118 | }); 119 | 120 | if (error) { 121 | return { error: Array.isArray(error) ? error[0] : error }; 122 | } else { 123 | return { data }; 124 | } 125 | } 126 | 127 | export async function addToCart(productId: string) { 128 | const cookieStore = await cookies(); 129 | let cartId = cookieStore.get("cartId")?.value; 130 | if (!cartId) { 131 | cartId = Math.random().toString(36).substring(2, 15); 132 | (await cookies()).set("cartId", cartId); 133 | } 134 | const graphqlQuery = ` 135 | query addToCart($cartId: String!, $productId: String!) { 136 | addToCart(cartId: $cartId, productId: $productId) 137 | } 138 | `; 139 | 140 | const { error, data } = await fetchQuery({ 141 | query: graphqlQuery, 142 | variables: { cartId, productId }, 143 | }); 144 | 145 | if (error) { 146 | return { error: Array.isArray(error) ? error[0] : error }; 147 | } else { 148 | revalidateTag(TAGS.cart); 149 | return { data }; 150 | } 151 | } 152 | 153 | export async function getCart(cartId: string) { 154 | const graphqlQuery = ` 155 | query getCart($cartId: String!) { 156 | getCart(cartId: $cartId) { 157 | cartId 158 | totalCartQuantity 159 | items { 160 | Product { 161 | name 162 | description 163 | stars 164 | price 165 | image 166 | } 167 | quantity 168 | cartItemID 169 | } 170 | } 171 | } 172 | `; 173 | 174 | const { error, data } = await fetchQuery({ 175 | query: graphqlQuery, 176 | variables: { cartId }, 177 | }); 178 | 179 | if (error) { 180 | return { error: Array.isArray(error) ? error[0] : error }; 181 | } else { 182 | return { data }; 183 | } 184 | } 185 | 186 | export async function removeFromCart(productId: string) { 187 | const cookieStore = await cookies(); 188 | let cartId = cookieStore.get("cartId")?.value; 189 | 190 | const graphqlQuery = ` 191 | query removeFromCart($cartId: String!, $productId: String!) { 192 | removeFromCart(cartId: $cartId, productId: $productId) 193 | } 194 | `; 195 | 196 | const { error, data } = await fetchQuery({ 197 | query: graphqlQuery, 198 | variables: { cartId, productId }, 199 | }); 200 | 201 | if (error) { 202 | return { error: Array.isArray(error) ? error[0] : error }; 203 | } else { 204 | revalidateTag(TAGS.cart); 205 | return { data }; 206 | } 207 | } 208 | 209 | export async function decreaseItemQuantity(productId: string) { 210 | const cookieStore = await cookies(); 211 | let cartId = cookieStore.get("cartId")?.value; 212 | const graphqlQuery = ` 213 | query decreaseQuantity($cartId: String!, $productId: String!) { 214 | decreaseQuantity(cartId: $cartId, productId: $productId) 215 | } 216 | `; 217 | 218 | const { error, data } = await fetchQuery({ 219 | query: graphqlQuery, 220 | variables: { cartId, productId }, 221 | }); 222 | 223 | if (error) { 224 | return { error: Array.isArray(error) ? error[0] : error }; 225 | } else { 226 | revalidateTag(TAGS.cart); 227 | return { data }; 228 | } 229 | } 230 | 231 | export async function recommendProductByCart(cartId: string, maxItems: number) { 232 | const graphqlQuery = ` 233 | query recommendProductByCart($cartId: String!, $maxItems: Int!) { 234 | recommendProductByCart(cartId: $cartId, maxItems: $maxItems) { 235 | searchObjs { 236 | product { 237 | name 238 | id 239 | image 240 | description 241 | stars 242 | price 243 | isStocked 244 | category 245 | } 246 | } 247 | } 248 | } 249 | `; 250 | const { error, data } = await fetchQuery({ 251 | query: graphqlQuery, 252 | variables: { cartId, maxItems }, 253 | }); 254 | if (error) { 255 | return { error: Array.isArray(error) ? error[0] : error }; 256 | } else { 257 | return { data }; 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /frontend/app/components/advanced-search.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRouter, useSearchParams, usePathname } from "next/navigation"; 4 | import { useOptimistic, useTransition } from "react"; 5 | import { StarRatingFilter } from "./rating-filter"; 6 | import { XCircleIcon } from "@heroicons/react/24/outline"; 7 | 8 | export function AdvancedSearch() { 9 | const searchParams = useSearchParams(); 10 | const { replace } = useRouter(); 11 | const pathname = usePathname(); 12 | 13 | const [optimisticItemsPerPage, setOptimisticItemsPerPage] = useOptimistic( 14 | searchParams?.get("itemsPerPage") || "10", 15 | ); 16 | const [pending, startTransition] = useTransition(); 17 | 18 | const handleItemsPerPageChange = (value: string) => { 19 | const params = new URLSearchParams(searchParams); 20 | params.set("itemsPerPage", value); 21 | 22 | setOptimisticItemsPerPage(value); 23 | 24 | startTransition(() => { 25 | replace(`${pathname}?${params?.toString()}`); 26 | }); 27 | }; 28 | 29 | function clearRatingSearch() { 30 | const params = new URLSearchParams(searchParams); 31 | params.set("rating", "0"); 32 | 33 | startTransition(() => { 34 | replace(`${pathname}?${params?.toString()}`); 35 | }); 36 | } 37 | 38 | const rating = searchParams?.get("rating"); 39 | const textQuery = searchParams?.get("q"); 40 | 41 | return ( 42 | <> 43 |
44 | Advanced Search 45 |
46 |
47 | Showing results for{" "} 48 | "{textQuery}" 49 |
50 |
51 |

Customer Reviews

52 | 53 |
54 | 55 | & up 56 | {rating && rating !== "0" && ( 57 | 64 | )} 65 |
66 |
67 |
68 |

Items per page

69 |
70 | {["9", "12", "15"].map((value) => ( 71 | 79 | ))} 80 |
81 |
82 | 83 | ); 84 | } 85 | -------------------------------------------------------------------------------- /frontend/app/components/carousel.tsx: -------------------------------------------------------------------------------- 1 | import { ProductTile } from "./tile"; 2 | import { searchProducts } from "../actions"; 3 | import { Suspense } from "react"; 4 | import { TileSkeleton } from "./skeletons"; 5 | 6 | export async function Carousel() { 7 | const response = await searchProducts("Top rated toys", 15, 1, true); 8 | 9 | const products = response?.data?.searchProducts?.searchObjs || []; 10 | // Purposefully duplicating products to make the carousel loop and not run out of products on wide screens. 11 | const carouselProducts = [...products, ...products, ...products]; 12 | 13 | return ( 14 |
15 |
    16 | {carouselProducts.map((product, i) => ( 17 |
  • 21 |
    22 | }> 23 | 24 | 25 |
    26 |
  • 27 | ))} 28 |
29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /frontend/app/components/cart/RecommendedProducts.tsx: -------------------------------------------------------------------------------- 1 | import { addToCart } from "@/app/actions"; 2 | import Link from "next/link"; 3 | 4 | export function RecommendedProducts({ 5 | recommendedItems, 6 | }: { 7 | recommendedItems: any; 8 | }) { 9 | return ( 10 |
11 |
We think you might also like:
12 |
13 | {recommendedItems.map((item: any, i: number) => ( 14 |
15 | 16 | {item.product.name} 21 | 22 |
23 |
${item.product.price}
24 | 30 |
31 |
32 | ))} 33 |
34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /frontend/app/components/cart/cart-modal.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ShoppingCartIcon, XMarkIcon } from "@heroicons/react/16/solid"; 4 | import { useEffect, useRef, useState } from "react"; 5 | import clsx from "clsx"; 6 | import { EditItemQuantityButton } from "./edit-quantity"; 7 | import { DeleteItemButton } from "./delete-item"; 8 | import { RecommendedProducts } from "./RecommendedProducts"; 9 | 10 | function Price({ amount, className }: { amount: number; className: string }) { 11 | return $ {amount}; 12 | } 13 | 14 | function CloseCart() { 15 | return ( 16 |
17 | 20 |
21 | ); 22 | } 23 | 24 | function OpenCart({ quantity }: { quantity: number }) { 25 | return ( 26 |
27 | 28 |
29 | {{quantity}} 30 |
31 |
32 | ); 33 | } 34 | 35 | export default function CartModal({ 36 | cart, 37 | recommendedItems, 38 | }: { 39 | cart: any; 40 | recommendedItems: Array; 41 | }) { 42 | const cartItems = cart?.data?.getCart?.items; 43 | 44 | const [isOpen, setIsOpen] = useState(false); 45 | const quantityRef = useRef(cart?.data?.getCart?.totalCartQuantity); 46 | const openCart = () => setIsOpen(true); 47 | const closeCart = () => setIsOpen(false); 48 | useEffect(() => { 49 | if (cart?.data?.getCart?.totalCartQuantity !== quantityRef.current) { 50 | if (!isOpen) { 51 | setIsOpen(true); 52 | } 53 | quantityRef.current = cart?.data?.getCart?.totalCartQuantity; 54 | } 55 | }, [isOpen, cart?.data?.getCart?.totalCartQuantity, quantityRef]); 56 | 57 | return ( 58 | <> 59 | 62 | {isOpen && ( 63 |
64 | 138 | )} 139 | 140 | ); 141 | } 142 | -------------------------------------------------------------------------------- /frontend/app/components/cart/delete-item.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { XMarkIcon } from "@heroicons/react/16/solid"; 3 | import { removeFromCart } from "../../actions"; 4 | 5 | export function DeleteItemButton({ item }: { item: any }) { 6 | const handleSubmit = async (e: React.FormEvent) => { 7 | e.preventDefault(); 8 | await removeFromCart(item.cartItemID); 9 | }; 10 | return ( 11 |
12 | 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /frontend/app/components/cart/edit-quantity.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { addToCart, decreaseItemQuantity } from "../../actions"; 4 | import { SubmitButton } from "./submit-button"; 5 | 6 | export function EditItemQuantityButton({ 7 | item, 8 | type, 9 | }: { 10 | item: any; 11 | type: "plus" | "minus"; 12 | }) { 13 | const handleSubmit = async (e: React.FormEvent) => { 14 | e.preventDefault(); 15 | 16 | if (type === "plus") { 17 | await addToCart(item.cartItemID); 18 | } else { 19 | await decreaseItemQuantity(item.cartItemID); 20 | } 21 | }; 22 | 23 | return ( 24 |
25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /frontend/app/components/cart/index.tsx: -------------------------------------------------------------------------------- 1 | import { getCart, recommendProductByCart } from "../../actions"; 2 | import { cookies } from "next/headers"; 3 | import CartModal from "./cart-modal"; 4 | 5 | export default async function Cart() { 6 | const cartId = (await cookies()).get("cartId")?.value; 7 | let cart; 8 | let recommended; 9 | if (cartId) { 10 | cart = await getCart(cartId); 11 | recommended = await recommendProductByCart(cartId, 3); 12 | } 13 | 14 | return ( 15 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /frontend/app/components/cart/submit-button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { MinusIcon, PlusIcon } from "@heroicons/react/24/solid"; 4 | import clsx from "clsx"; 5 | import { useFormStatus } from "react-dom"; 6 | 7 | export function SubmitButton({ type }: { type: "plus" | "minus" }) { 8 | const { pending } = useFormStatus(); 9 | 10 | return ( 11 | 33 | ); 34 | } 35 | 36 | export default SubmitButton; 37 | -------------------------------------------------------------------------------- /frontend/app/components/footer.tsx: -------------------------------------------------------------------------------- 1 | export default function Footer() { 2 | return ( 3 |
4 | 41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /frontend/app/components/grid.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react"; 2 | import { searchProducts } from "../actions"; 3 | import { TileSkeleton } from "./skeletons"; 4 | import { ProductTile } from "./tile"; 5 | 6 | function ThreeItemGridItem({ 7 | item, 8 | size, 9 | }: { 10 | item: { 11 | product: { 12 | description: string; 13 | id: string; 14 | name: string; 15 | image: string; 16 | price: string; 17 | stars: number; 18 | isStocked: string; 19 | }; 20 | }; 21 | size: "full" | "half"; 22 | }) { 23 | return ( 24 |
31 |
32 | }> 33 | 34 | 35 |
36 |
37 | ); 38 | } 39 | 40 | export async function ThreeItemGrid() { 41 | const response = await searchProducts( 42 | "Items that people of all ages would enjoy", 43 | 3, 44 | 1, 45 | true 46 | ); 47 | 48 | const topThreeProducts = response?.data?.searchProducts?.searchObjs || []; 49 | return ( 50 |
51 | 52 | 53 | 54 |
55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /frontend/app/components/header.tsx: -------------------------------------------------------------------------------- 1 | import Search from "./search"; 2 | import { ShoppingCartIcon } from "@heroicons/react/24/outline"; 3 | import { Suspense } from "react"; 4 | import { SearchInputSkeleton } from "./skeletons"; 5 | import Logo from "./logo"; 6 | import Cart from "./cart"; 7 | 8 | export default function Header() { 9 | return ( 10 | <> 11 |
12 | Loading
}> 13 | 14 | 15 | }> 16 | 17 | 18 | {/* */} 21 | 22 |
23 |
24 |
25 | Loading
}> 26 | 27 | 28 | 29 | 32 |
33 | }> 34 | 35 | 36 | 37 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /frontend/app/components/logo.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Link from "next/link"; 4 | 5 | export default function Logo() { 6 | return ( 7 | 8 |
9 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | yper-toys 30 |
31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /frontend/app/components/product-details.tsx: -------------------------------------------------------------------------------- 1 | import { getProduct, addToCart } from "../actions"; 2 | import Image from "next/image"; 3 | import StarRating from "./product-rating"; 4 | import { SubmitButton } from "./submit-button"; 5 | 6 | export async function ProductDetails({ id }: { id: string }) { 7 | const response = await getProduct(id); 8 | const product = response?.data?.product; 9 | 10 | return ( 11 |
12 |
13 | {product?.name} 20 |
21 |
22 |
23 |

24 | {product?.name} 25 |

26 |
27 | ${product?.price} 28 |
29 |
30 |
31 | 32 |
33 |
34 | {product?.isStocked ? ( 35 |
In Stock
36 | ) : ( 37 |
Out of Stock
38 | )} 39 |
40 |
{product?.description}
41 | 42 |
43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /frontend/app/components/product-rating.tsx: -------------------------------------------------------------------------------- 1 | import { StarIcon as SolidStar } from "@heroicons/react/24/solid"; 2 | import { StarIcon as OutlineStar } from "@heroicons/react/24/outline"; 3 | 4 | export default function StarRating({ 5 | rating, 6 | color, 7 | }: { 8 | rating: number; 9 | color?: string; 10 | }) { 11 | const totalStars = 5; 12 | 13 | const fullStars = Math.floor(rating); 14 | const fractionalStar = rating - fullStars; 15 | 16 | return ( 17 |
18 | {Array.from({ length: totalStars }).map((_, index) => { 19 | const isFullStar = index < fullStars; 20 | const isPartialStar = index === fullStars && fractionalStar > 0; 21 | const isEmptyStar = index > fullStars; 22 | 23 | return ( 24 |
34 | {/* Partial Star */} 35 | {isPartialStar && ( 36 |
47 | 57 |
67 | 74 |
75 |
76 | )} 77 | 78 | {/* Filled Star */} 79 | {isFullStar && !isPartialStar && ( 80 | 90 | )} 91 | 92 | {/* Outline Star */} 93 | {isEmptyStar && !isPartialStar && ( 94 | 104 | )} 105 |
106 | ); 107 | })} 108 |
109 | ); 110 | } 111 | -------------------------------------------------------------------------------- /frontend/app/components/rating-filter.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter, useSearchParams, usePathname } from "next/navigation"; 2 | import { useOptimistic, useTransition } from "react"; 3 | 4 | interface StarProps { 5 | filled: boolean; 6 | onClick: () => void; 7 | } 8 | 9 | function Star({ filled, onClick }: StarProps) { 10 | const searchParams = useSearchParams(); 11 | return ( 12 | 16 | ★ 17 | 24 | 25 | ); 26 | } 27 | 28 | export function StarRatingFilter() { 29 | const searchParams = useSearchParams(); 30 | const ratingFromParams = parseInt(searchParams.get("rating") || "0", 10); 31 | const { replace } = useRouter(); 32 | const pathname = usePathname(); 33 | 34 | // Initialize optimistic state with current rating 35 | const [optimisticRating, setOptimisticRating] = 36 | useOptimistic(ratingFromParams); 37 | const [pending, startTransition] = useTransition(); 38 | 39 | const handleClick = (index: number) => { 40 | const newRating = index + 1; 41 | 42 | // Optimistically update the rating 43 | setOptimisticRating(newRating); 44 | 45 | const params = new URLSearchParams(searchParams); 46 | params.set("rating", newRating?.toString()); 47 | 48 | startTransition(() => { 49 | replace(`${pathname}?${params?.toString()}`); 50 | }); 51 | }; 52 | 53 | return ( 54 |
55 | {[...Array(5)].map((_, index) => ( 56 | handleClick(index)} 60 | /> 61 | ))} 62 | 67 |
68 | ); 69 | } 70 | 71 | export default StarRatingFilter; 72 | -------------------------------------------------------------------------------- /frontend/app/components/search-results-grid.tsx: -------------------------------------------------------------------------------- 1 | import { searchProducts } from "../actions"; 2 | import { ProductTile } from "./tile"; 3 | 4 | export async function SearchResultsGrid({ 5 | searchValue, 6 | thresholdStars, 7 | maxItems, 8 | }: { 9 | searchValue: string; 10 | thresholdStars: string; 11 | maxItems: string; 12 | }) { 13 | const response = await searchProducts( 14 | searchValue, 15 | parseInt(maxItems) || 9, 16 | parseInt(thresholdStars) || 0 17 | ); 18 | 19 | const products = response?.data?.searchProducts?.searchObjs || []; 20 | 21 | return ( 22 |
23 |
24 | {products?.length > 0 ? ( 25 |
26 | {products.map( 27 | ( 28 | item: { 29 | product: { 30 | description: string; 31 | id: string; 32 | name: string; 33 | image: string; 34 | price: string; 35 | stars: number; 36 | isStocked: string; 37 | }; 38 | }, 39 | i: number 40 | ) => ( 41 |
42 | 43 |
44 | ) 45 | )} 46 |
47 | ) : ( 48 |
49 |
50 | No items matching your search 51 |
52 |
53 | Try searching again, such as: "Halloween decorations" 54 |
55 |
56 | )} 57 |
58 |
59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /frontend/app/components/search.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; 4 | import { useRouter, useSearchParams } from "next/navigation"; 5 | import { useDebouncedCallback } from "use-debounce"; 6 | import { ReadonlyURLSearchParams } from "next/navigation"; 7 | 8 | const createUrl = ( 9 | pathname: string, 10 | params: URLSearchParams | ReadonlyURLSearchParams, 11 | ) => { 12 | const paramsString = params?.toString(); 13 | const queryString = `${paramsString.length ? "?" : ""}${paramsString}`; 14 | 15 | return `${pathname}${queryString}`; 16 | }; 17 | 18 | export default function Search() { 19 | const searchParams = useSearchParams(); 20 | const router = useRouter(); 21 | 22 | const handleSearch = useDebouncedCallback((term: string) => { 23 | const params = new URLSearchParams(searchParams); 24 | 25 | if (term) { 26 | params.set("q", term); 27 | router.push(createUrl("/search", params)); 28 | } else { 29 | router.push("/"); 30 | } 31 | }, 200); 32 | 33 | return ( 34 |
35 | 38 | { 43 | handleSearch(e.target.value); 44 | }} 45 | /> 46 | 47 |
48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /frontend/app/components/skeletons.tsx: -------------------------------------------------------------------------------- 1 | import { PhotoIcon } from "@heroicons/react/24/outline"; 2 | 3 | const shimmer = 4 | "before:absolute before:inset-0 before:-translate-x-full before:animate-[shimmer_2s_infinite] before:bg-linear-to-r before:from-transparent dark:before:via-white/20 before:via-stone-100 before:to-transparent"; 5 | 6 | export function TileSkeleton() { 7 | return ( 8 |
11 |
12 | 13 |
14 |
15 |
16 |
17 |
18 |
19 | ); 20 | } 21 | 22 | export function SearchSkeleton() { 23 | return ( 24 |
25 |
26 | 27 |
28 |
29 | 30 |
31 |
32 | 33 |
34 |
35 | 36 |
37 |
38 | 39 |
40 |
41 | 42 |
43 |
44 | 45 |
46 |
47 | 48 |
49 |
50 | 51 |
52 |
53 | ); 54 | } 55 | 56 | export function ProductSkeleton() { 57 | return ( 58 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | 72 | 76 |
77 |
78 | ); 79 | } 80 | 81 | export function AdvancedSearchSkeleton() { 82 | return
; 83 | } 84 | 85 | export function SearchInputSkeleton() { 86 | return ( 87 |
88 |
89 |
90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /frontend/app/components/submit-button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { addToCart, getCart } from "../actions"; 3 | 4 | export function SubmitButton({ id }: { id: string }) { 5 | const handleSubmit = (event: React.FormEvent) => { 6 | event.preventDefault(); 7 | 8 | addToCart(id) 9 | .then((result) => { 10 | console.log(result); 11 | // getCart(cartId) 12 | // .then((result) => { 13 | // console.log("Product added to cart successfully:", result); 14 | // }) 15 | // .catch((error) => { 16 | // console.error("Failed to add to cart:", error); 17 | // }); 18 | // console.log("Product added to cart successfully:", result); 19 | }) 20 | .catch((error) => { 21 | console.error("Failed to add to cart:", error); 22 | }); 23 | }; 24 | return ( 25 |
26 | {/* */} 37 | 44 |
45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /frontend/app/components/tile.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import Link from "next/link"; 3 | import StarRating from "./product-rating"; 4 | 5 | export async function ProductTile({ 6 | product, 7 | size, 8 | }: { 9 | product: { 10 | description: string; 11 | id: string; 12 | image: string; 13 | name: string; 14 | price: string; 15 | stars: number; 16 | isStocked: string; 17 | }; 18 | size?: string; 19 | }) { 20 | return ( 21 |
22 | 23 |
24 | 25 |
26 | {!product?.isStocked ?
Out of Stock
:
} 27 |
28 |
29 | {product?.name} 35 |
36 |
{product?.name}
37 |
38 | $ 39 | {product?.price} 40 |
41 |
42 | 43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /frontend/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermodeinc/hyper-commerce/1726346944b050ffc49c9a3ee4675bba00eebcee/frontend/app/favicon.ico -------------------------------------------------------------------------------- /frontend/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import "../globals.css"; 3 | import Header from "./components/header"; 4 | import Footer from "./components/footer"; 5 | 6 | export const metadata: Metadata = { 7 | title: "Hypermode Commerce", 8 | description: "An e-commerce app built with Hypermode", 9 | }; 10 | 11 | export default function RootLayout({ 12 | children, 13 | }: Readonly<{ 14 | children: React.ReactNode; 15 | }>) { 16 | return ( 17 | 18 | 19 |
20 |
{children}
21 |