diff --git a/compose.yml b/compose.yml index 0df461d..cee6192 100644 --- a/compose.yml +++ b/compose.yml @@ -1,12 +1,12 @@ services: - api-service: + tapiro-api-internal: build: - context: ./api-service + context: ./tapiro-api-internal dockerfile: dockerfile target: development - container_name: tapiro-API + container_name: tapiro-api-internal volumes: - - ./api-service:/app + - ./tapiro-api-internal:/app - /app/node_modules environment: - NODE_ENV=development @@ -30,6 +30,7 @@ services: - AI_SERVICE_URL=http://ml-service:8000/api - AI_SERVICE_API_KEY=${AI_SERVICE_API_KEY} - AUTH0_DOMAIN=${AUTH0_DOMAIN} + - EXTERNAL_API_URL=http://tapiro-api-external:3001 ports: - "3000:3000" depends_on: @@ -44,6 +45,41 @@ services: retries: 3 start_period: 15s + tapiro-api-external: + build: + context: ./tapiro-api-external + dockerfile: dockerfile + target: development + container_name: tapiro-api-external + volumes: + - ./tapiro-api-external:/app + - /app/node_modules + environment: + - NODE_ENV=development + - MONGODB_URI=${MONGODB_URI} + - DB_NAME=tapiro + - REDIS_HOST=redis + - REDIS_PORT=6379 + - PORT=3001 + - AI_SERVICE_URL=http://ml-service:8000/api + - AI_SERVICE_API_KEY=${AI_SERVICE_API_KEY} + - ALLOWED_ORIGINS=http://localhost:5174 + ports: + - "3001:3001" + depends_on: + redis: + condition: service_healthy + tapiro-api-internal: + condition: service_started + networks: + - tapiro-net + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3001/ping"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 15s + ml-service: build: context: ./ml-service @@ -55,9 +91,9 @@ services: - ml-model-cache:/app/model_cache environment: - MONGODB_URI=${MONGODB_URI} - - API_BASE_URL=http://api-service:3000 + - API_BASE_URL=http://tapiro-api-internal:3000 + - EXTERNAL_API_URL=http://tapiro-api-external:3001 - MONGODB_DB_NAME=tapiro - - BACKEND_API_URL=http://api-service:3000 - SECRET_KEY=${AI_SERVICE_API_KEY} - DEBUG=true - REDIS_HOST=redis @@ -97,7 +133,7 @@ services: networks: - tapiro-net depends_on: - - api-service + - tapiro-api-internal healthcheck: test: ["CMD", "wget", "-O", "/dev/null", "http://localhost:5173"] interval: 30s @@ -105,6 +141,20 @@ services: retries: 3 start_period: 15s + demo-store: + build: + context: ./demo + dockerfile: Dockerfile + container_name: demo + environment: + - VITE_STORE_API_URL=http://localhost:3001 + ports: + - "5174:80" + networks: + - tapiro-net + depends_on: + - tapiro-api-external + redis: image: redis:alpine container_name: tapiro-redis @@ -122,18 +172,6 @@ services: retries: 5 start_period: 10s - demo-store: - build: - context: ./demo - dockerfile: Dockerfile - container_name: demo - ports: - - "5174:80" - networks: - - tapiro-net - depends_on: - - api-service - networks: tapiro-net: diff --git a/demo/public/products/apples.webp b/demo/public/products/apples.webp new file mode 100644 index 0000000..162c984 Binary files /dev/null and b/demo/public/products/apples.webp differ diff --git a/demo/public/products/babystroller.webp b/demo/public/products/babystroller.webp new file mode 100644 index 0000000..3223549 Binary files /dev/null and b/demo/public/products/babystroller.webp differ diff --git a/demo/public/products/boots.webp b/demo/public/products/boots.webp new file mode 100644 index 0000000..8e5f7fb Binary files /dev/null and b/demo/public/products/boots.webp differ diff --git a/demo/public/products/buildingblocks.webp b/demo/public/products/buildingblocks.webp new file mode 100644 index 0000000..fe6b32e Binary files /dev/null and b/demo/public/products/buildingblocks.webp differ diff --git a/demo/public/products/cleanser.webp b/demo/public/products/cleanser.webp new file mode 100644 index 0000000..5bcdae5 Binary files /dev/null and b/demo/public/products/cleanser.webp differ diff --git a/demo/public/products/coffee.webp b/demo/public/products/coffee.webp new file mode 100644 index 0000000..33d84a0 Binary files /dev/null and b/demo/public/products/coffee.webp differ diff --git a/demo/public/products/dell-laptop.webp b/demo/public/products/dell-laptop.webp new file mode 100644 index 0000000..6011ccb Binary files /dev/null and b/demo/public/products/dell-laptop.webp differ diff --git a/demo/public/products/dress.webp b/demo/public/products/dress.webp new file mode 100644 index 0000000..8260991 Binary files /dev/null and b/demo/public/products/dress.webp differ diff --git a/demo/public/products/drill.webp b/demo/public/products/drill.webp new file mode 100644 index 0000000..16b4660 Binary files /dev/null and b/demo/public/products/drill.webp differ diff --git a/demo/public/products/fictionnoval.webp b/demo/public/products/fictionnoval.webp new file mode 100644 index 0000000..1ab3665 Binary files /dev/null and b/demo/public/products/fictionnoval.webp differ diff --git a/demo/public/products/gamgingmouse.webp b/demo/public/products/gamgingmouse.webp new file mode 100644 index 0000000..bc22e80 Binary files /dev/null and b/demo/public/products/gamgingmouse.webp differ diff --git a/demo/public/products/gaming-laptop.webp b/demo/public/products/gaming-laptop.webp new file mode 100644 index 0000000..6f7b38d Binary files /dev/null and b/demo/public/products/gaming-laptop.webp differ diff --git a/demo/public/products/garden-tools.webp b/demo/public/products/garden-tools.webp new file mode 100644 index 0000000..133ec22 Binary files /dev/null and b/demo/public/products/garden-tools.webp differ diff --git a/demo/public/products/gardening.jpg b/demo/public/products/gardening.jpg new file mode 100644 index 0000000..b09a88b Binary files /dev/null and b/demo/public/products/gardening.jpg differ diff --git a/demo/public/products/genpens.webp b/demo/public/products/genpens.webp new file mode 100644 index 0000000..e975187 Binary files /dev/null and b/demo/public/products/genpens.webp differ diff --git a/demo/public/products/giftbasket.webp b/demo/public/products/giftbasket.webp new file mode 100644 index 0000000..bc17439 Binary files /dev/null and b/demo/public/products/giftbasket.webp differ diff --git a/demo/public/products/headphones.webp b/demo/public/products/headphones.webp new file mode 100644 index 0000000..366c157 Binary files /dev/null and b/demo/public/products/headphones.webp differ diff --git a/demo/public/products/indiegame.webp b/demo/public/products/indiegame.webp new file mode 100644 index 0000000..0e77fb6 Binary files /dev/null and b/demo/public/products/indiegame.webp differ diff --git a/demo/public/products/ipad.webp b/demo/public/products/ipad.webp new file mode 100644 index 0000000..f6b1213 Binary files /dev/null and b/demo/public/products/ipad.webp differ diff --git a/demo/public/products/iphonex.webp b/demo/public/products/iphonex.webp new file mode 100644 index 0000000..739c375 Binary files /dev/null and b/demo/public/products/iphonex.webp differ diff --git a/demo/public/products/jeans.webp b/demo/public/products/jeans.webp new file mode 100644 index 0000000..b035bb5 Binary files /dev/null and b/demo/public/products/jeans.webp differ diff --git a/demo/public/products/kidstshirt.webp b/demo/public/products/kidstshirt.webp new file mode 100644 index 0000000..508916c Binary files /dev/null and b/demo/public/products/kidstshirt.webp differ diff --git a/demo/public/products/lipstick.webp b/demo/public/products/lipstick.webp new file mode 100644 index 0000000..3f988be Binary files /dev/null and b/demo/public/products/lipstick.webp differ diff --git a/demo/public/products/mattfoundation.webp b/demo/public/products/mattfoundation.webp new file mode 100644 index 0000000..738b221 Binary files /dev/null and b/demo/public/products/mattfoundation.webp differ diff --git a/demo/public/products/necklace.webp b/demo/public/products/necklace.webp new file mode 100644 index 0000000..da5d657 Binary files /dev/null and b/demo/public/products/necklace.webp differ diff --git a/demo/public/products/productivitysuite.webp b/demo/public/products/productivitysuite.webp new file mode 100644 index 0000000..f07c8a0 Binary files /dev/null and b/demo/public/products/productivitysuite.webp differ diff --git a/demo/public/products/samsung.webp b/demo/public/products/samsung.webp new file mode 100644 index 0000000..f02747d Binary files /dev/null and b/demo/public/products/samsung.webp differ diff --git a/demo/public/products/sciencefiction.webp b/demo/public/products/sciencefiction.webp new file mode 100644 index 0000000..cf31860 Binary files /dev/null and b/demo/public/products/sciencefiction.webp differ diff --git a/demo/public/products/smartwatch.webp b/demo/public/products/smartwatch.webp new file mode 100644 index 0000000..6c29c01 Binary files /dev/null and b/demo/public/products/smartwatch.webp differ diff --git a/demo/public/products/sneakers.jpg.webp b/demo/public/products/sneakers.jpg.webp new file mode 100644 index 0000000..6c38007 Binary files /dev/null and b/demo/public/products/sneakers.jpg.webp differ diff --git a/demo/public/products/sofa.webp b/demo/public/products/sofa.webp new file mode 100644 index 0000000..aab0e19 Binary files /dev/null and b/demo/public/products/sofa.webp differ diff --git a/demo/public/products/steelpan.webp b/demo/public/products/steelpan.webp new file mode 100644 index 0000000..b8de2cb Binary files /dev/null and b/demo/public/products/steelpan.webp differ diff --git a/demo/public/products/suit.webp b/demo/public/products/suit.webp new file mode 100644 index 0000000..9474011 Binary files /dev/null and b/demo/public/products/suit.webp differ diff --git a/demo/public/products/suitecase.webp b/demo/public/products/suitecase.webp new file mode 100644 index 0000000..2af3e75 Binary files /dev/null and b/demo/public/products/suitecase.webp differ diff --git a/demo/public/products/tabel.webp b/demo/public/products/tabel.webp new file mode 100644 index 0000000..86464f0 Binary files /dev/null and b/demo/public/products/tabel.webp differ diff --git a/demo/public/products/toothbrush.webp b/demo/public/products/toothbrush.webp new file mode 100644 index 0000000..79db270 Binary files /dev/null and b/demo/public/products/toothbrush.webp differ diff --git a/demo/public/products/tshirt.webp b/demo/public/products/tshirt.webp new file mode 100644 index 0000000..e011947 Binary files /dev/null and b/demo/public/products/tshirt.webp differ diff --git a/demo/public/products/tuna.webp b/demo/public/products/tuna.webp new file mode 100644 index 0000000..ab1dae3 Binary files /dev/null and b/demo/public/products/tuna.webp differ diff --git a/demo/public/products/vitamin.webp b/demo/public/products/vitamin.webp new file mode 100644 index 0000000..c0171d1 Binary files /dev/null and b/demo/public/products/vitamin.webp differ diff --git a/demo/public/products/vitaminc.webp b/demo/public/products/vitaminc.webp new file mode 100644 index 0000000..83c7c45 Binary files /dev/null and b/demo/public/products/vitaminc.webp differ diff --git a/demo/src/App.tsx b/demo/src/App.tsx index 4d9419e..b233477 100644 --- a/demo/src/App.tsx +++ b/demo/src/App.tsx @@ -37,8 +37,7 @@ function App() { ); // Get API details from environment variables (only URL now) - const apiUrl = import.meta.env.VITE_TAPIRO_API_URL; - // const apiKey = import.meta.env.VITE_STORE_API_KEY; // Removed: Get from state + const apiUrl = import.meta.env.VITE_STORE_API_URL || "http://localhost:3001"; // --- Effects --- useEffect(() => { @@ -90,27 +89,48 @@ function App() { // Sort by preference score if preferences are loaded if (preferences && preferences.length > 0) { - const prefMap = new Map( - preferences.map((p) => [p.category, p.score ?? 0]) - ); const getScore = (product: Product): number => { - // Basic score: category match - let score = prefMap.get(product.categoryId) ?? 0; - - // Bonus for attribute matches (simple example) - if (product.attributes && prefMap.has(product.categoryId)) { - const categoryPrefs = preferences.find( - (p) => p.category === product.categoryId - ); - if (categoryPrefs?.attributes) { - for (const attrKey in product.attributes) { - if ( - categoryPrefs.attributes[attrKey]?.[product.attributes[attrKey]] - ) { - score += 0.1; // Small bonus for each matching attribute value + const categoryPreference = preferences.find( + (p) => p.category === product.categoryId + ); + + if (!categoryPreference) { + return 0; // No preference for this category + } + + let score = categoryPreference.score; // Base score from category + + // Add bonus for attribute matches + if (product.attributes && categoryPreference.attributes) { + let attributeBonus = 0; + let matchingAttributes = 0; + const prefAttributes = categoryPreference.attributes; + + for (const attrKey in product.attributes) { + const productAttrValue = String( + product.attributes[attrKey] + ).toLowerCase(); + if (prefAttributes[attrKey]) { + // Check if the specific product attribute value has a score + if (prefAttributes[attrKey][productAttrValue]) { + attributeBonus += prefAttributes[attrKey][productAttrValue]; // Add the specific attribute value's score + matchingAttributes++; + } else { + // If the exact value isn't scored, check if the attribute key itself has a general preference + // This handles cases where preference is for "brand: Apple" but product has "brand: Apple, color: Red" + // and "brand" itself might have a score in preferences if not specific value. + // For simplicity, let's assume if the attribute key (e.g., "brand") exists in preferences, it's a partial match. + // A more complex logic could assign a smaller bonus here. + // For now, we only reward exact value matches from prefAttributes. } } } + // Normalize bonus by number of matching attributes to avoid overly penalizing products with many attributes + if (matchingAttributes > 0) { + // Example: Add average bonus of matched attributes. + // You might want a different logic, e.g., sum of bonuses, or cap the bonus. + score += attributeBonus / matchingAttributes; + } } return score; }; @@ -334,20 +354,65 @@ function App() { // --- Recommended Product IDs --- const recommendedProductIds = useMemo(() => { const ids = new Set(); - if (preferences && preferences.length > 0) { - const prefMap = new Map( - preferences.map((p) => [p.category, p.score ?? 0]) - ); - displayedProducts.forEach((p) => { - const score = prefMap.get(p.categoryId) ?? 0; - if (score > 0.5) { - // Example threshold for "recommended" - ids.add(p.id); + if (preferences && preferences.length > 0 && displayedProducts.length > 0) { + // Get scores for all currently displayed products + const productScores = displayedProducts.map((product) => { + const categoryPreference = preferences.find( + (p) => p.category === product.categoryId + ); + if (!categoryPreference) return { id: product.id, score: 0 }; + + let score = categoryPreference.score; + if (product.attributes && categoryPreference.attributes) { + let attributeBonus = 0; + let matchingAttributes = 0; + const prefAttributes = categoryPreference.attributes; + for (const attrKey in product.attributes) { + const productAttrValue = String( + product.attributes[attrKey] + ).toLowerCase(); + if ( + prefAttributes[attrKey] && + prefAttributes[attrKey][productAttrValue] + ) { + attributeBonus += prefAttributes[attrKey][productAttrValue]; + matchingAttributes++; + } + } + if (matchingAttributes > 0) { + score += attributeBonus / matchingAttributes; // Average bonus + } } + return { id: product.id, score }; }); + + // Sort products by score to find the top ones + productScores.sort((a, b) => b.score - a.score); + + // Recommend top N products or products above a certain score threshold + // For example, recommend products with a score > 0.6 (adjust as needed) + // Or recommend the top 3-5 products if there are enough. + const recommendationThreshold = 0.6; // Adjusted threshold + productScores.forEach((ps) => { + if (ps.score > recommendationThreshold) { + ids.add(ps.id); + } + }); + + // If no products are above the threshold, maybe recommend the top 1-2 anyway if scores are positive + if ( + ids.size === 0 && + productScores.length > 0 && + productScores[0].score > 0.1 + ) { + ids.add(productScores[0].id); + if (productScores.length > 1 && productScores[1].score > 0.1) { + ids.add(productScores[1].id); + } + } } return ids; - }, [preferences, displayedProducts]); + }, [preferences, displayedProducts]); // Recalculate when preferences or displayed products change // Helper to display API Key (show prefix only for brevity/security) const displayApiKey = apiKey ? `${apiKey.substring(0, 8)}...` : "Not Set"; diff --git a/demo/src/data/products.ts b/demo/src/data/products.ts index ca1c1f6..485fe8b 100644 --- a/demo/src/data/products.ts +++ b/demo/src/data/products.ts @@ -3,7 +3,7 @@ export interface Product { id: string; name: string; price: number; - imageUrl: string; // Using placeholders + imageUrl: string; // Using actual images categoryId: string; // Crucial: Match IDs from ml-service/app/data/taxonomy.yaml attributes?: Record; // Optional attributes description?: string; // Optional description @@ -16,7 +16,7 @@ export const sampleProducts: Product[] = [ id: "p1", name: "Pro Smartphone X", price: 999, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Smartphone", + imageUrl: "/products/iphonex.webp", categoryId: "101", attributes: { brand: "Apple", color: "black", storage: "256GB", os: "iOS" }, description: "Latest generation smartphone with advanced camera.", @@ -25,7 +25,7 @@ export const sampleProducts: Product[] = [ id: "p2", name: "Budget Android Phone", price: 249, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Budget+Phone", + imageUrl: "/products/samsung.webp", categoryId: "101", attributes: { brand: "Samsung", @@ -39,7 +39,7 @@ export const sampleProducts: Product[] = [ id: "p3", name: 'UltraBook Pro 14"', price: 1399, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Laptop", + imageUrl: "/products/dell-laptop.webp", categoryId: "102", attributes: { brand: "Dell", @@ -55,7 +55,7 @@ export const sampleProducts: Product[] = [ id: "p4", name: 'Gaming Beast Laptop 16"', price: 1999, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Gaming+Laptop", + imageUrl: "/products/gaming-laptop.webp", categoryId: "102", attributes: { brand: "Asus", @@ -71,7 +71,7 @@ export const sampleProducts: Product[] = [ id: "p5", name: 'Creative Tablet 11"', price: 799, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Tablet", + imageUrl: "/products/ipad.webp", categoryId: "103", attributes: { brand: "Apple", @@ -85,7 +85,7 @@ export const sampleProducts: Product[] = [ id: "p6", name: "Fitness Tracker Watch", price: 149, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Smartwatch", + imageUrl: "/products/smartwatch.webp", categoryId: "104", attributes: { brand: "Fitbit", @@ -98,7 +98,7 @@ export const sampleProducts: Product[] = [ id: "p7", name: "Noise Cancelling Headphones", price: 349, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Headphones", + imageUrl: "/products/headphones.webp", categoryId: "105", attributes: { brand: "Sony", @@ -113,7 +113,7 @@ export const sampleProducts: Product[] = [ id: "p8", name: "Portable Bluetooth Speaker", price: 89, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Speaker", + imageUrl: "/products/genpens.webp", categoryId: "105", attributes: { brand: "JBL", @@ -129,7 +129,7 @@ export const sampleProducts: Product[] = [ id: "p9", name: "Classic Cotton T-Shirt", price: 25, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=T-Shirt", + imageUrl: "/products/tshirt.webp", categoryId: "201", attributes: { type: "t-shirts", @@ -144,7 +144,7 @@ export const sampleProducts: Product[] = [ id: "p10", name: "Slim Fit Jeans", price: 69, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Jeans", + imageUrl: "/products/jeans.webp", categoryId: "201", attributes: { type: "jeans", @@ -159,7 +159,7 @@ export const sampleProducts: Product[] = [ id: "p11", name: "Summer Maxi Dress", price: 89, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Dress", + imageUrl: "/products/dress.webp", categoryId: "201", attributes: { type: "dresses", @@ -175,7 +175,7 @@ export const sampleProducts: Product[] = [ id: "p12", name: "Leather Ankle Boots", price: 149, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Boots", + imageUrl: "/products/boots.webp", categoryId: "202", attributes: { type: "boots", @@ -190,7 +190,7 @@ export const sampleProducts: Product[] = [ id: "p13", name: "Running Sneakers", price: 119, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Sneakers", + imageUrl: "/products/sneakers.jpg.webp", categoryId: "202", attributes: { type: "athletic", @@ -205,7 +205,7 @@ export const sampleProducts: Product[] = [ id: "p14", name: "Kids Graphic Tee", price: 15, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Kids+Tee", + imageUrl: "/products/kidstshirt.webp", categoryId: "702", attributes: { age_group: "kids", @@ -214,23 +214,23 @@ export const sampleProducts: Product[] = [ type: "t-shirt", }, description: "Fun graphic t-shirt for kids.", - }, // Kids Clothing + }, { id: "p15", name: "Men's Business Suit", price: 399, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Suit", + imageUrl: "/products/suit.webp", categoryId: "802", attributes: { type: "suit", gender: "men", occasion: "business_formal" }, description: "Classic two-piece suit for formal business settings.", - }, // Business Wear + }, // --- Home (300) --- { id: "p16", name: "Modern Sofa", price: 899, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Sofa", + imageUrl: "/products/sofa.webp", categoryId: "301", attributes: { type: "sofa", @@ -245,13 +245,13 @@ export const sampleProducts: Product[] = [ id: "p17", name: "Wooden Dining Table", price: 499, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Table", + imageUrl: "/products/tabel.webp", categoryId: "301", attributes: { type: "table", material: "wood", color: "brown", - room: "kitchen", + room: "dining_room", style: "farmhouse", }, description: "Solid wood dining table for family meals.", @@ -260,7 +260,7 @@ export const sampleProducts: Product[] = [ id: "p18", name: "Stainless Steel Cookware Set", price: 199, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Cookware", + imageUrl: "/products/steelpan.webp", categoryId: "302", attributes: { type: "cookware", @@ -273,7 +273,7 @@ export const sampleProducts: Product[] = [ id: "p19", name: "Smart Coffee Maker", price: 129, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Coffee+Maker", + imageUrl: "/products/coffee.webp", categoryId: "302", attributes: { type: "small_appliance", @@ -286,7 +286,7 @@ export const sampleProducts: Product[] = [ id: "p20", name: "Gardening Tool Set", price: 45, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Garden+Tools", + imageUrl: "/products/garden-tools.webp", categoryId: "303", attributes: { type: "tools" }, description: "Essential tools for your gardening tasks.", @@ -295,7 +295,7 @@ export const sampleProducts: Product[] = [ id: "p21", name: "Cordless Power Drill", price: 99, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Drill", + imageUrl: "/products/drill.webp", categoryId: "304", attributes: { type: "power_tool", brand: "DeWalt" }, description: "Versatile cordless drill for home improvement.", @@ -306,7 +306,7 @@ export const sampleProducts: Product[] = [ id: "p22", name: "Hydrating Face Cleanser", price: 15, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Cleanser", + imageUrl: "/products/cleanser.webp", categoryId: "401", attributes: { type: "cleanser", @@ -319,7 +319,7 @@ export const sampleProducts: Product[] = [ id: "p23", name: "Vitamin C Serum", price: 35, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Serum", + imageUrl: "/products/vitaminc.webp", categoryId: "401", attributes: { type: "serum", concern: "dullness", ingredient: "vitamin_c" }, description: "Brightening serum to improve skin radiance.", @@ -328,7 +328,7 @@ export const sampleProducts: Product[] = [ id: "p24", name: "Matte Liquid Foundation", price: 40, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Foundation", + imageUrl: "/products/mattfoundation.webp", categoryId: "402", attributes: { type: "foundation", @@ -342,7 +342,7 @@ export const sampleProducts: Product[] = [ id: "p25", name: "Red Lipstick", price: 22, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Lipstick", + imageUrl: "/products/lipstick.webp", categoryId: "402", attributes: { type: "lipstick", @@ -358,7 +358,7 @@ export const sampleProducts: Product[] = [ id: "p26", name: "Bestseller Fiction Novel", price: 18, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Book", + imageUrl: "/products/fictionnoval.webp", categoryId: "501", attributes: { format: "paperback", genre: "mystery", bestseller: "yes" }, description: "A thrilling mystery novel topping the charts.", @@ -367,7 +367,7 @@ export const sampleProducts: Product[] = [ id: "p27", name: "Classic Sci-Fi Movie (Blu-ray)", price: 25, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Movie", + imageUrl: "/products/sciencefiction.webp", categoryId: "502", attributes: { format: "blu_ray", @@ -376,31 +376,22 @@ export const sampleProducts: Product[] = [ }, description: "A timeless science fiction masterpiece on Blu-ray.", }, - { - id: "p28", - name: "Video Streaming Subscription", - price: 15, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Streaming", - categoryId: "503", - attributes: { service_type: "video", provider: "Netflix" }, - description: "Monthly subscription for unlimited movies and TV shows.", - }, // --- Health & Wellness (600) --- { - id: "p29", + id: "p28", name: "Daily Multivitamin Gummies", price: 20, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Vitamins", + imageUrl: "/products/vitamin.webp", categoryId: "601", attributes: { type: "multivitamin", form: "gummy" }, description: "Easy-to-take daily multivitamins.", }, { - id: "p30", + id: "p29", name: "Electric Toothbrush", price: 75, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Toothbrush", + imageUrl: "/products/toothbrush.webp", categoryId: "602", attributes: { type: "oral_care" }, description: "Advanced cleaning for healthier gums.", @@ -408,19 +399,19 @@ export const sampleProducts: Product[] = [ // --- Toys & Games (700) --- { - id: "p31", + id: "p30", name: "Building Blocks Set (Large)", price: 55, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Blocks", + imageUrl: "/products/buildingblocks.webp", categoryId: "700", attributes: { type: "building_blocks", age_range: "3-5" }, description: "Large building blocks set for creative play.", }, { - id: "p32", + id: "p31", name: "Baby Stroller System", price: 299, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Stroller", + imageUrl: "/products/babystroller.webp", categoryId: "701", attributes: { type: "stroller", brand: "Graco" }, description: "Versatile stroller system for infants and toddlers.", @@ -428,10 +419,10 @@ export const sampleProducts: Product[] = [ // --- Office Supplies (800) --- { - id: "p33", + id: "p32", name: "Gel Pens (12-Pack)", price: 12, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Pens", + imageUrl: "/products/genpens.webp", categoryId: "801", attributes: { type: "pen" }, description: "Smooth writing gel pens in assorted colors.", @@ -439,19 +430,19 @@ export const sampleProducts: Product[] = [ // --- Gaming (900) --- { - id: "p34", + id: "p33", name: "Wireless Gaming Mouse", price: 59, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Gaming+Mouse", + imageUrl: "/products/gamgingmouse.webp", categoryId: "900", attributes: { type: "accessory", platform: "pc" }, description: "High-precision wireless mouse for gaming.", }, { - id: "p35", + id: "p34", name: "Indie Puzzle Game (PC)", price: 19, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=PC+Game", + imageUrl: "/products/indiegame.webp", categoryId: "900", attributes: { type: "game", platform: "pc", genre: "puzzle" }, description: "Challenging and unique indie puzzle game.", @@ -459,10 +450,10 @@ export const sampleProducts: Product[] = [ // --- Travel (1000) --- { - id: "p36", + id: "p35", name: "Carry-On Suitcase", price: 120, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Suitcase", + imageUrl: "/products/suitecase.webp", categoryId: "1000", attributes: { type: "luggage", travel_style: "business" }, description: "Durable carry-on suitcase for business trips.", @@ -470,30 +461,30 @@ export const sampleProducts: Product[] = [ // --- Grocery (1100) --- { - id: "p37", + id: "p36", name: "Organic Apples (3lb Bag)", price: 6, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Apples", + imageUrl: "/products/apples.webp", categoryId: "1100", attributes: { category: "fresh_produce", dietary_preference: "organic" }, description: "Fresh, crisp organic apples.", }, { - id: "p38", + id: "p37", name: "Canned Tuna Value Pack", price: 5, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Tuna", + imageUrl: "/products/tuna.webp", categoryId: "1101", attributes: { type: "canned_goods" }, description: "Value pack of canned tuna.", - }, // Budget Food + }, // --- Jewelry & Watches (1200) --- { - id: "p39", + id: "p38", name: "Silver Necklace", price: 75, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Necklace", + imageUrl: "/products/necklace.webp", categoryId: "1200", attributes: { type: "necklace", @@ -506,10 +497,10 @@ export const sampleProducts: Product[] = [ // --- Gifts (1300) --- { - id: "p40", + id: "p39", name: "Birthday Gift Basket", price: 50, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Gift+Basket", + imageUrl: "/products/giftbasket.webp", categoryId: "1300", attributes: { occasion: "birthday", @@ -521,10 +512,10 @@ export const sampleProducts: Product[] = [ // --- Software (1400) --- { - id: "p41", + id: "p40", name: "Productivity Suite (Subscription)", price: 99, - imageUrl: "https://via.placeholder.com/300x200/eee/aaa?text=Software", + imageUrl: "/products/productivitysuite.webp", categoryId: "1400", attributes: { type: "productivity", diff --git a/ml-service/app/core/config.py b/ml-service/app/core/config.py index 4280809..8b8dd4b 100644 --- a/ml-service/app/core/config.py +++ b/ml-service/app/core/config.py @@ -23,14 +23,11 @@ class Settings(BaseSettings): SECRET_KEY: str = os.getenv("SECRET_KEY", "your-secret-key-for-development-only") # Node.js Backend API - BACKEND_API_URL: str = os.getenv("BACKEND_API_URL", "http://backend:3000") + BACKEND_API_URL: str = os.getenv("BACKEND_API_URL", "http://backend:3001") # CORS Settings BACKEND_CORS_ORIGINS: list = [ - "http://localhost:3000", - "http://localhost:5173", - "http://backend:3000", - "http://web:5173" + "http://localhost:3001", ] # Redis settings diff --git a/ml-service/app/services/demographicInference.py b/ml-service/app/services/demographicInference.py index 8bed9eb..b20914f 100644 --- a/ml-service/app/services/demographicInference.py +++ b/ml-service/app/services/demographicInference.py @@ -6,7 +6,7 @@ from app.utils.redis_util import invalidate_cache, CACHE_KEYS from sentence_transformers import util import numpy as np -from app.services.taxonomyService import get_taxonomy_service +from app.services.taxonomyService import TaxonomyService logger = logging.getLogger(__name__) @@ -148,40 +148,27 @@ # --- Helper Function to Extract Text (Keep as is) --- def _extract_text_from_user_data_docs(user_data_docs: List[Dict[str, Any]]) -> List[str]: - """Extracts relevant text (item names, search queries) from a list of userData documents.""" texts = [] - for doc in user_data_docs: - # Each doc has an 'entries' list - for entry in doc.get("entries", []): - # Process purchase items within each entry - if doc.get("dataType") == "purchase": # Check dataType at the document level - for item in entry.get("items", []): + for doc in user_data_docs: # Each doc is a userData document from DB + doc_data_type = doc.get("dataType") # dataType is at doc level + for entry in doc.get("entries", []): # entries is a list within the doc + if doc_data_type == "purchase": + for item in entry.get("items", []): # entry here is a purchase entry if item.get("name"): - texts.append(item["name"].lower()) - # Process search query within each entry - elif doc.get("dataType") == "search": # Check dataType at the document level - if entry.get("query"): - texts.append(entry["query"].lower()) - return [text for text in texts if text] # Filter out empty strings + texts.append(item["name"]) + elif doc_data_type == "search": + if entry.get("query"): # entry here is a search entry + texts.append(entry["query"]) + return [text for text in texts if text and isinstance(text, str) and len(text.strip()) > 0] # Filter out empty/invalid strings + # --- Hybrid Inference Helper (Modified) --- -async def _run_hybrid_inference_for_attribute( # Renamed for clarity +async def _run_hybrid_inference_for_attribute( attribute_name: str, user_data_docs: List[Dict[str, Any]], taxonomy_service # Pass the service instance ) -> Optional[Any]: - """ - Generic function to infer a demographic attribute using HYBRID (rule + semantic) approach. - - Args: - attribute_name: The key from SEMANTIC_TARGETS/KEYWORD_RULES. - user_data_docs: List of user data documents. - taxonomy_service: Initialized TaxonomyService instance. - - Returns: - The inferred value or None if insufficient evidence. - """ texts = _extract_text_from_user_data_docs(user_data_docs) if not texts: logger.debug(f"Hybrid Inference ({attribute_name}): No text entries found.") @@ -189,86 +176,81 @@ async def _run_hybrid_inference_for_attribute( # Renamed for clarity logger.debug(f"Hybrid Inference ({attribute_name}): Processing {len(texts)} text entries.") - # --- Prepare Semantic Targets (if needed) --- semantic_target_map = SEMANTIC_TARGETS.get(attribute_name, {}) target_embeddings_tensor = None - semantic_target_value_map = {} - all_semantic_target_texts = [] + semantic_target_value_map = {} # Maps index in target_embeddings_tensor to actual value + all_semantic_target_texts_for_encoding = [] can_do_semantic = False if semantic_target_map and taxonomy_service and taxonomy_service.embedding_model: - idx = 0 - for value, descriptions in semantic_target_map.items(): - for desc in descriptions: - all_semantic_target_texts.append(desc) - semantic_target_value_map[idx] = value - idx += 1 - - if all_semantic_target_texts: - try: - target_embeddings_tensor = taxonomy_service.embedding_model.encode(all_semantic_target_texts, convert_to_tensor=True) - can_do_semantic = True - logger.debug(f"Hybrid Inference ({attribute_name}): Prepared semantic targets.") - except Exception as e: - logger.error(f"Hybrid Inference ({attribute_name}): Failed to encode semantic target descriptions: {e}", exc_info=True) + idx_counter = 0 + for actual_value, descriptions_list in semantic_target_map.items(): + for desc_text in descriptions_list: + all_semantic_target_texts_for_encoding.append(desc_text) + semantic_target_value_map[idx_counter] = actual_value + idx_counter += 1 + + if all_semantic_target_texts_for_encoding: + target_embeddings_tensor = taxonomy_service.embedding_model.encode( + all_semantic_target_texts_for_encoding, + convert_to_tensor=True, + show_progress_bar=False + ) + can_do_semantic = True + logger.debug(f"Hybrid Inference ({attribute_name}): Encoded {len(all_semantic_target_texts_for_encoding)} semantic target descriptions.") else: - logger.debug(f"Hybrid Inference ({attribute_name}): No semantic target descriptions found.") + logger.debug(f"Hybrid Inference ({attribute_name}): No semantic target descriptions to encode.") else: logger.warning(f"Hybrid Inference ({attribute_name}): Semantic inference disabled (no targets or model unavailable).") - # --- End Prepare Semantic Targets --- - # --- Prepare Keyword Rules --- keyword_rule_map = KEYWORD_RULES.get(attribute_name, {}) can_do_rules = bool(keyword_rule_map) logger.debug(f"Hybrid Inference ({attribute_name}): Keyword rules {'enabled' if can_do_rules else 'disabled'}.") - # --- End Prepare Keyword Rules --- if not can_do_rules and not can_do_semantic: logger.warning(f"Hybrid Inference ({attribute_name}): No rules or semantic targets available. Cannot infer.") return None - # --- Calculate similarities and count evidence (Hybrid Logic) --- - evidence_counts = defaultdict(float) # Use float for weighted evidence - matched_texts_per_value = defaultdict(set) # Track unique texts per value - - for text in texts: - matched_by_rule = False - # 1. Check Keyword Rules First + evidence_counts = defaultdict(float) + matched_texts_per_value = defaultdict(set) + + all_user_text_embeddings = None + if can_do_semantic and texts: # Ensure texts is not empty for encoding + all_user_text_embeddings = taxonomy_service.embedding_model.encode( + texts, # Encode all user texts in one batch + convert_to_tensor=True, + show_progress_bar=False, + batch_size=32 + ) + logger.debug(f"Hybrid Inference ({attribute_name}): Encoded {len(texts)} user texts in a batch.") + + for i, text_content in enumerate(texts): + original_text_lower = text_content.lower() + # ... (Rule matching logic remains the same, ensure it uses original_text_lower and text_content) if can_do_rules: - for value, keywords in keyword_rule_map.items(): - # Simple substring check for keywords - if any(keyword in text for keyword in keywords): - if text not in matched_texts_per_value[value]: - evidence_counts[value] += RULE_MATCH_WEIGHT - matched_texts_per_value[value].add(text) - logger.debug(f"Rule Match ({attribute_name}): '{text}' -> '{value}' (Weight: {RULE_MATCH_WEIGHT})") - matched_by_rule = True - break # Stop checking rules for this text once one matches - if matched_by_rule: - continue # Move to the next text if a rule matched - - # 2. If no rule matched, try Semantic Check - if can_do_semantic: - try: - text_embedding = taxonomy_service.embedding_model.encode(text, convert_to_tensor=True) - similarities = util.pytorch_cos_sim(text_embedding, target_embeddings_tensor)[0] - best_match_idx = similarities.argmax().item() - best_score = similarities[best_match_idx].item() - - if best_score >= SEMANTIC_SIMILARITY_THRESHOLD: - matched_value = semantic_target_value_map[best_match_idx] - if text not in matched_texts_per_value[matched_value]: - evidence_counts[matched_value] += SEMANTIC_MATCH_WEIGHT - matched_texts_per_value[matched_value].add(text) - logger.debug(f"Semantic Match ({attribute_name}): '{text}' -> '{matched_value}' (Score: {best_score:.4f}, Weight: {SEMANTIC_MATCH_WEIGHT}, Target: '{all_semantic_target_texts[best_match_idx]}')") - - except Exception as e: - logger.warning(f"Hybrid Inference ({attribute_name}): Error processing semantic check for text '{text}': {e}") - continue # Skip this text entry - # --- End Calculate similarities --- - - - # --- Determine inferred value based on evidence thresholds (Using Floats) --- + for rule_value, keywords in keyword_rule_map.items(): + if any(keyword in original_text_lower for keyword in keywords): + if text_content not in matched_texts_per_value[rule_value]: + evidence_counts[rule_value] += RULE_MATCH_WEIGHT + matched_texts_per_value[rule_value].add(text_content) + logger.debug(f"Hybrid Inference ({attribute_name}): Text '{text_content[:30]}...' matched rule for '{rule_value}'.") + # Consider if a `break` is needed if one rule match per text is enough + + if can_do_semantic and all_user_text_embeddings is not None: + current_text_embedding = all_user_text_embeddings[i] + similarities = util.pytorch_cos_sim(current_text_embedding, target_embeddings_tensor)[0] + + for target_idx, similarity_score_tensor in enumerate(similarities): + similarity_score = similarity_score_tensor.item() + actual_target_value = semantic_target_value_map[target_idx] + + if similarity_score >= SEMANTIC_SIMILARITY_THRESHOLD: + if text_content not in matched_texts_per_value[actual_target_value]: + evidence_counts[actual_target_value] += SEMANTIC_MATCH_WEIGHT * similarity_score + matched_texts_per_value[actual_target_value].add(text_content) + logger.debug(f"Hybrid Inference ({attribute_name}): Text '{text_content[:30]}...' semantically matched '{actual_target_value}' (Score: {similarity_score:.2f}).") + + # --- Determine inferred value based on evidence thresholds (logic remains the same) --- inferred_value = None highest_evidence_score = 0.0 @@ -277,62 +259,63 @@ async def _run_hybrid_inference_for_attribute( # Renamed for clarity if evidence_counts.get("married", 0.0) >= EVIDENCE_THRESHOLD_MARRIED: inferred_value = "married" highest_evidence_score = evidence_counts["married"] - elif evidence_counts.get("relationship", 0.0) >= EVIDENCE_THRESHOLD_DEFAULT: - # Only infer 'relationship' if 'married' didn't meet its threshold - if inferred_value != "married": - inferred_value = "relationship" - highest_evidence_score = evidence_counts["relationship"] - # Add 'single' check if desired - # elif evidence_counts.get("single", 0.0) >= EVIDENCE_THRESHOLD_DEFAULT + 1.0: ... - + # Check 'relationship' only if 'married' didn't meet threshold or if its score is higher + elif evidence_counts.get("relationship", 0.0) >= EVIDENCE_THRESHOLD_DEFAULT and \ + evidence_counts.get("relationship", 0.0) > highest_evidence_score : + inferred_value = "relationship" + highest_evidence_score = evidence_counts["relationship"] # Special handling for education level priority elif attribute_name == "education_level": if evidence_counts.get("doctorate", 0.0) >= EVIDENCE_THRESHOLD_EDUCATION: inferred_value = "doctorate" highest_evidence_score = evidence_counts["doctorate"] - elif evidence_counts.get("masters", 0.0) >= EVIDENCE_THRESHOLD_EDUCATION: - if inferred_value != "doctorate": - inferred_value = "masters" - highest_evidence_score = evidence_counts["masters"] - elif evidence_counts.get("bachelors", 0.0) >= EVIDENCE_THRESHOLD_DEFAULT: - if inferred_value not in ["doctorate", "masters"]: - inferred_value = "bachelors" - highest_evidence_score = evidence_counts["bachelors"] - + elif evidence_counts.get("masters", 0.0) >= EVIDENCE_THRESHOLD_EDUCATION and \ + evidence_counts.get("masters", 0.0) > highest_evidence_score: + inferred_value = "masters" + highest_evidence_score = evidence_counts["masters"] + elif evidence_counts.get("bachelors", 0.0) >= EVIDENCE_THRESHOLD_DEFAULT and \ + evidence_counts.get("bachelors", 0.0) > highest_evidence_score: + inferred_value = "bachelors" + highest_evidence_score = evidence_counts["bachelors"] else: # Default handling: pick value with highest evidence score above threshold - threshold = EVIDENCE_THRESHOLD_DEFAULT - for value, score in evidence_counts.items(): - if score >= threshold and score > highest_evidence_score: + threshold_to_use = EVIDENCE_THRESHOLD_DEFAULT # Default threshold + # Example: if attribute_name == "has_kids": threshold_to_use = SOME_OTHER_THRESHOLD + + for value_candidate, score in evidence_counts.items(): + if score >= threshold_to_use and score > highest_evidence_score: highest_evidence_score = score - inferred_value = value + inferred_value = value_candidate + # If scores are equal, you might have a tie-breaking rule or pick the first one + elif score >= threshold_to_use and score == highest_evidence_score and inferred_value is None: + highest_evidence_score = score + inferred_value = value_candidate + if inferred_value is not None: logger.info(f"Hybrid Inference Result ({attribute_name}): Inferred '{inferred_value}' (Evidence Score: {highest_evidence_score:.2f})") else: - # Log counts even if insufficient log_counts = {k: round(v, 2) for k, v in evidence_counts.items()} logger.info(f"Hybrid Inference Result ({attribute_name}): None (Insufficient evidence. Scores: {log_counts})") return inferred_value - # --- End Determine inferred value --- - # --- Main Inference Runner (Updated) --- -async def run_inference_for_user(user_id: str, email: str, db, limit: int = 50) -> bool: +async def run_inference_for_user(user: Dict[str, Any], taxonomy_service: TaxonomyService, db, limit: int = 50) -> bool: """ Runs HYBRID demographic inference based on recent user data and updates the user document if changes are found AND the user has not provided their own value. Returns True if the user document was updated, False otherwise. """ + user_id = str(user["_id"]) # Get user_id from the passed user object + email = user["email"] # Get email from the passed user object logger.info(f"Running HYBRID demographic inference for user {user_id} ({email})") updated = False try: - user_object_id = ObjectId(user_id) - user = await db.users.find_one({"_id": user_object_id}) - if not user: - logger.error(f"Inference: User not found by ID {user_id}") - return False + user_object_id = user["_id"] # user["_id"] is already an ObjectId + + # User document is now passed as an argument, no need to fetch again. + # taxonomy_service is now passed as an argument, no need to fetch again via get_taxonomy_service here. # Fetch recent userData entries recent_data = await db.userData.find( @@ -345,9 +328,6 @@ async def run_inference_for_user(user_id: str, email: str, db, limit: int = 50) logger.info(f"Inference: Found {len(recent_data)} recent data entries for user {user_id}") - # Get Taxonomy Service (needed for embeddings) - taxonomy_service = await get_taxonomy_service(db) - current_demographics = user.get("demographicData", {}) # --- Run hybrid inference functions (conditionally) --- diff --git a/ml-service/app/services/preferenceProcessor.py b/ml-service/app/services/preferenceProcessor.py index 8a022b2..ea1a3fa 100644 --- a/ml-service/app/services/preferenceProcessor.py +++ b/ml-service/app/services/preferenceProcessor.py @@ -4,56 +4,353 @@ from fastapi import HTTPException from bson import ObjectId from app.utils.redis_util import invalidate_cache, CACHE_KEYS -from typing import List, Dict, Any, Optional -from app.services.taxonomyService import TaxonomyService, get_taxonomy_service # Updated import +from typing import List, Dict, Any, Optional, Set # Added Set +from app.services.taxonomyService import TaxonomyService, get_taxonomy_service from collections import defaultdict -from app.services.demographicInference import run_inference_for_user # Import the inference runner +from app.services.demographicInference import run_inference_for_user from sentence_transformers import util # Import sentence-transformers utility for similarity import numpy as np # Import numpy logger = logging.getLogger(__name__) -# --- Configuration --- +# --- Configuration for Preference Scoring and Decay --- +MAX_INFERRED_SCORE = 0.95 # Max score for preferences derived from behavior/inference +DECAY_FACTOR = 0.98 # Factor to apply for decay (e.g., 2% decay if not reinforced) +MIN_SCORE_THRESHOLD = 0.05 # Inferred preferences below this score are removed +# --- End New Configuration --- + +# --- Original Configuration --- ATTRIBUTE_SIMILARITY_THRESHOLD = 0.55 # Configurable threshold for matching attribute values + +async def _update_preference_score( + preference_dict: Dict[str, Dict[str, Any]], + category_id: str, + increment: float, + taxonomy: TaxonomyService, # Added for potential use + demographics: Optional[Dict[str, Any]] = None # Added for potential use +): + """Helper to update score for a category, respecting new capping rules.""" + current_pref = preference_dict.get(category_id) + if not current_pref: + # Initialize if new, ensuring 'is_explicit_score' is False by default + current_pref = {"category": category_id, "score": 0.0, "attributes": {}, "is_explicit_score": False} + preference_dict[category_id] = current_pref + + is_explicit = current_pref.get("is_explicit_score", False) + + if is_explicit: + current_pref["score"] = 1.0 # Explicit 1.0 scores are not changed by increments + logger.debug(f"Category {category_id}: Score is explicit (1.0), not changing with increment.") + else: + # Apply demographic boost or other logic to increment if needed here + # Example: if demographics and "children" in taxonomy.get_category_name(category_id).lower(): increment *= 1.1 + + new_score = min(MAX_INFERRED_SCORE, current_pref["score"] + increment) + current_pref["score"] = new_score + logger.debug(f"Category {category_id}: Updated score to {new_score} (increment: {increment}, capped at {MAX_INFERRED_SCORE}).") + + # preference_dict[category_id] = current_pref # Already modifying in place if current_pref was from dict + + +async def process_purchase_data( + entries: List[Dict[str, Any]], + preference_dict: Dict[str, Dict[str, Any]], + taxonomy: TaxonomyService, + demographics: Optional[Dict[str, Any]] = None, + categories_updated_this_run: Optional[Set[str]] = None +): + """Process purchase data using rule-based system, considering demographics and buying patterns""" + if categories_updated_this_run is None: + categories_updated_this_run = set() + + category_counts = defaultdict(int) + attribute_counts = defaultdict(lambda: defaultdict(lambda: defaultdict(int))) # category -> attr_name -> attr_value -> count + # category_price_totals = defaultdict(float) # Not used in current logic, can be added if needed + # category_item_counts = defaultdict(int) # Not used in current logic, can be added if needed + + for entry in entries: + for item in entry.get("items", []): + category_input = item.get("category") + item_name = item.get("name") + # quantity = item.get("quantity", 1) # Not directly used for scoring yet + # price = item.get("price") # Not directly used for scoring yet + provided_attributes = item.get("attributes") + + if not category_input and not item_name: + logger.debug(f"Skipping item due to missing category and name: {item}") + continue + + category_id = None + if category_input: + # Try direct ID lookup first + if taxonomy.get_category_name(category_input): # Validates if category_input is an ID + category_id = category_input + else: # Assume it's a name + category_id = taxonomy.get_category_id(category_input) + + if not category_id and item_name: # Fallback to matching item name if category not resolved + match_result = await taxonomy.match_category(item_name) + if match_result and match_result.get("category") and match_result.get("threshold_met"): + category_id = match_result["category"] + logger.debug(f"Item '{item_name}' matched to category '{category_id}' via embedding.") + + if not category_id: + logger.debug(f"Could not resolve category for item '{item_name}' (input: '{category_input}'). Skipping item.") + continue + + category_counts[category_id] += 1 + categories_updated_this_run.add(category_id) # Mark category as updated + + # Process attributes + final_attributes_for_item = None + if provided_attributes and isinstance(provided_attributes, dict): + # Basic validation: ensure attributes are somewhat structured. + # More advanced validation against taxonomy.get_category_details(category_id).attributes can be added. + final_attributes_for_item = provided_attributes + elif item_name: # If no attributes provided, try to extract them + logger.debug(f"Attempting AI attribute extraction for item '{item_name}' in category '{category_id}'.") + final_attributes_for_item = await extract_attributes_with_similarity(item_name, category_id, taxonomy) + + if final_attributes_for_item: + for attr_name, attr_value in final_attributes_for_item.items(): + if isinstance(attr_value, str): # Simple case: direct value + attribute_counts[category_id][attr_name][attr_value] += 1 + # Can extend to handle list of values, etc. + + total_items_overall = sum(category_counts.values()) + if total_items_overall > 0: + for category_id, count in category_counts.items(): + increment = (count / total_items_overall) * 0.5 # Base increment factor for purchases + await _update_preference_score(preference_dict, category_id, increment, taxonomy, demographics) + + for category_id, attrs_data in attribute_counts.items(): + if category_id in preference_dict: + pref_for_attributes = preference_dict[category_id] + if "attributes" not in pref_for_attributes or pref_for_attributes["attributes"] is None: + pref_for_attributes["attributes"] = {} + + for attr_name, value_counts in attrs_data.items(): + if attr_name not in pref_for_attributes["attributes"]: + pref_for_attributes["attributes"][attr_name] = {} + + total_attr_occurrences_for_value = sum(value_counts.values()) + if total_attr_occurrences_for_value > 0: + for attr_value, count in value_counts.items(): + current_attr_score = pref_for_attributes["attributes"][attr_name].get(attr_value, 0.0) + attr_increment = (count / total_attr_occurrences_for_value) * 0.1 # Smaller increment for attributes + + # Attribute scores are always capped at MAX_INFERRED_SCORE + new_attr_score = min(MAX_INFERRED_SCORE, current_attr_score + attr_increment) + pref_for_attributes["attributes"][attr_name][attr_value] = new_attr_score + logger.debug(f"Category {category_id}, Attr {attr_name}.{attr_value}: Updated score to {new_attr_score}.") + + +async def process_search_data( + entries: List[Dict[str, Any]], + preference_dict: Dict[str, Dict[str, Any]], + taxonomy: TaxonomyService, + demographics: Optional[Dict[str, Any]] = None, + categories_updated_this_run: Optional[Set[str]] = None +): + """Process search data using embedding model, considering demographics""" + if categories_updated_this_run is None: + categories_updated_this_run = set() + + # demographics = demographics or {} # Available if needed for boosting + + for entry in entries: + query = entry.get("query") + provided_category_id = entry.get("category") # Category explicitly sent with search + matched_category_id = None + match_score = 0.0 + + if not query and not provided_category_id: + logger.debug("Search entry with no query and no category, skipping.") + continue + + if provided_category_id and taxonomy.get_category_name(provided_category_id): # Validate ID + matched_category_id = provided_category_id + match_score = 0.9 # High confidence if category provided and valid + elif query: + match_result = await taxonomy.match_category(query) + if match_result and match_result.get("category") and match_result.get("threshold_met"): + matched_category_id = match_result["category"] + match_score = match_result["score"] + + if matched_category_id: + categories_updated_this_run.add(matched_category_id) + base_increment = match_score * 0.3 # Base increment factor for search + # Example: Boost increment if search led to many results or clicks + # if entry.get("results", 0) > 5 or entry.get("clicked"): base_increment *= 1.1 + await _update_preference_score(preference_dict, matched_category_id, base_increment, taxonomy, demographics) + logger.debug(f"Search query '{query or provided_category_id}' matched category {matched_category_id} with score {match_score}, increment {base_increment}.") + + +async def process_with_embeddings( + entries: List[Dict[str, Any]], + data_type: str, + preference_dict: Dict[str, Dict[str, Any]], + taxonomy: TaxonomyService, + demographics: Optional[Dict[str, Any]] = None, + categories_updated_this_run: Optional[Set[str]] = None +): + """Fallback processing using embeddings, potentially considering demographics""" + if categories_updated_this_run is None: + categories_updated_this_run = set() + logger.info(f"Using embedding fallback processing for data_type: {data_type}") + + # demographics = demographics or {} # Available if needed + + if data_type == "purchase": + item_names_for_embedding = [] + for entry in entries: + for item in entry.get("items", []): + if item.get("name"): + item_names_for_embedding.append(item["name"]) + + for item_name in item_names_for_embedding: + match_result = await taxonomy.match_category(item_name) + if match_result and match_result.get("category") and match_result.get("threshold_met"): + category_id = match_result["category"] + categories_updated_this_run.add(category_id) + increment = match_result["score"] * 0.2 # Increment based on match score for embedding fallback + await _update_preference_score(preference_dict, category_id, increment, taxonomy, demographics) + logger.debug(f"Fallback purchase item '{item_name}' matched category {category_id}, increment {increment}.") + elif data_type == "search": + # Fallback for search can re-use process_search_data or have specific embedding logic + await process_search_data(entries, preference_dict, taxonomy, demographics, categories_updated_this_run) + + +async def normalize_categories(preferences: List[Dict[str, Any]], taxonomy: TaxonomyService) -> List[Dict[str, Any]]: + """Ensure all categories use IDs instead of names and are valid.""" + normalized = [] + if not taxonomy.taxonomy: + logger.error("Cannot normalize categories: Taxonomy not loaded.") + return preferences # Return as is if taxonomy is unavailable + + valid_category_ids = {cat.id for cat in taxonomy.taxonomy.categories} + + for pref_item in preferences: + category_key = pref_item.get("category") + resolved_category_id = None + + if category_key in valid_category_ids: + resolved_category_id = category_key + elif isinstance(category_key, str): # Could be a name + cat_id_from_name = taxonomy.get_category_id(category_key) + if cat_id_from_name and cat_id_from_name in valid_category_ids: + resolved_category_id = cat_id_from_name + logger.debug(f"Normalized category name '{category_key}' to ID '{resolved_category_id}'.") + + if resolved_category_id: + pref_item["category"] = resolved_category_id # Ensure it's the ID + normalized.append(pref_item) + else: + logger.warning(f"Skipping preference for unknown/invalid category '{category_key}' during normalization.") + + return normalized + + +async def extract_attributes_with_similarity(item_name: str, category_id: str, taxonomy_service: TaxonomyService) -> Optional[Dict[str, str]]: + """ + Uses the semantic embedding model to extract attributes for an item by comparing + the item name to potential attribute values defined in the taxonomy. + Returns a dictionary like {"color": "blue", "size": "M"} or None. + """ + if not taxonomy_service.embedding_model: + logger.warning("AI Attribute Extraction: Embedding model not available in TaxonomyService.") + return None + + logger.debug(f"AI Attribute Extraction: Processing '{item_name}' in category '{category_id}'") + extracted_attributes = {} + + category_details = taxonomy_service.get_category_details(category_id) + if not category_details or not category_details.attributes: + logger.debug(f"AI Attribute Extraction: No attributes defined for category '{category_id}'.") + return None + + item_name_embedding = taxonomy_service.embedding_model.encode(item_name.lower()) + + for attribute_def in category_details.attributes: + best_value_for_attr = None + highest_similarity = -1.0 + + for value in attribute_def.values: + # Compare item_name with "attribute_name value" for better context, or just value + # For simplicity, comparing item_name directly to value. + # Contextual phrase: f"{attribute_def.name} {value}" + value_embedding = taxonomy_service.embedding_model.encode(value.lower()) + similarity = util.pytorch_cos_sim(item_name_embedding, value_embedding).item() + + if similarity > highest_similarity: + highest_similarity = similarity + best_value_for_attr = value + + if best_value_for_attr and highest_similarity >= ATTRIBUTE_SIMILARITY_THRESHOLD: + extracted_attributes[attribute_def.name] = best_value_for_attr + logger.debug(f"AI Attribute Extraction: Matched '{item_name}' to attribute '{attribute_def.name}: {best_value_for_attr}' (Similarity: {highest_similarity:.2f})") + + if not extracted_attributes: + logger.debug(f"AI Attribute Extraction: No attributes extracted for '{item_name}' above threshold {ATTRIBUTE_SIMILARITY_THRESHOLD}.") + return None + + return extracted_attributes + + +async def mark_processing_failed(db, email: str, submission_id: Optional[str] = None): + """Marks a specific userData entry or the oldest pending one for the email as failed.""" + try: + match_criteria = {"email": email, "processedStatus": "pending"} + if submission_id and ObjectId.is_valid(submission_id): + match_criteria["_id"] = ObjectId(submission_id) + + # If no specific ID, update_one will target one matching document. + # To target the "oldest" without a specific ID, a sort would be needed with find_one_and_update, + # or a separate find with sort then update. update_one is simpler if any pending is fine. + result = await db.userData.update_one( + match_criteria, + {"$set": {"processedStatus": "failed"}}, + ) + if result.modified_count > 0: + logger.info(f"Marked userData as failed for {email} (Submission ID: {submission_id if submission_id else 'Oldest Pending'}). Modified: {result.modified_count}") + else: + logger.warning(f"No matching pending userData found to mark as failed for {email} (Submission ID: {submission_id}). Criteria: {match_criteria}") + except Exception as e: + logger.error(f"Failed to mark userData as failed for {email} (Submission ID: {submission_id}): {str(e)}") + + async def process_user_data(data: UserDataEntry, db) -> UserPreferences: - """Process user data and update their preferences""" + """Process user data and update their preferences with decay for inferred scores""" - # Extract user info user_id_from_meta = data.metadata.get("userId") if data.metadata else None email = data.email data_type = data.data_type entries = data.entries + submission_id = data.metadata.get("submissionId") if data.metadata else None # For marking specific entry - logger.info(f"Processing data for user email {email} (ID from meta: {user_id_from_meta}), type: {data_type}") + logger.info(f"Processing data for user email {email} (ID from meta: {user_id_from_meta}), type: {data_type}, submission: {submission_id}") - # Fetch the full user document from MongoDB to get demographics user = None if user_id_from_meta and ObjectId.is_valid(user_id_from_meta): user = await db.users.find_one({"_id": ObjectId(user_id_from_meta)}) if user and user.get("email") != email: - logger.warning(f"User ID {user_id_from_meta} provided in metadata maps to email {user.get('email')}, but processing request is for {email}. Proceeding with email lookup.") - user = None # Force email lookup if mismatch + logger.warning(f"User ID {user_id_from_meta} from metadata does not match email {email}. Falling back to email lookup.") + user = None if not user: - # Fallback to find by email user = await db.users.find_one({"email": email}) if not user: - logger.error(f"User not found by email: {email}") - # Mark as failed before raising - await mark_processing_failed(db, email) # Assuming mark_processing_failed exists - raise HTTPException(status_code=404, detail="User not found") + logger.error(f"User not found with email {email}. Cannot process data.") + await mark_processing_failed(db, email, submission_id) # Mark as failed before raising + raise HTTPException(status_code=404, detail=f"User not found: {email}") - user_id = str(user["_id"]) # Use the confirmed user ID from DB + user_id = str(user["_id"]) logger.info(f"Found user {email} with DB ID {user_id}") - # --- Check Inference Permission --- privacy_settings = user.get("privacySettings", {}) - allow_inference = privacy_settings.get("allowInference", True) # Default to True if missing - - # Extract demographics from the nested 'demographicData' field + allow_inference_setting = privacy_settings.get("allowInference", True) user_demographics_nested = user.get("demographicData", {}) - # Flatten the dictionary to pass to processing functions user_demographics_flat = { "gender": user_demographics_nested.get("gender"), "incomeBracket": user_demographics_nested.get("incomeBracket"), @@ -65,138 +362,148 @@ async def process_user_data(data: UserDataEntry, db) -> UserPreferences: "inferredEducationLevel": user_demographics_nested.get("inferredEducationLevel"), "inferredAgeBracket": user_demographics_nested.get("inferredAgeBracket"), } - # Filter out None values if desired, but processing functions handle None - # user_demographics_flat = {k: v for k, v in user_demographics_flat.items() if v is not None} - - logger.info(f"Using demographics for user {email}: {user_demographics_flat}") - - - # Get current preferences from the user object - user_preferences = user.get("preferences", []) - - # Convert to dictionary for easier updates {category_id: preference_object} - preference_dict = {} - for pref in user_preferences: - if isinstance(pref, dict) and "category" in pref: - preference_dict[pref["category"]] = pref + logger.debug(f"Using demographics for user {email}: {user_demographics_flat}") + + user_preferences_initial = user.get("preferences", []) + preference_dict: Dict[str, Dict[str, Any]] = {} + for pref_item in user_preferences_initial: + if isinstance(pref_item, dict) and "category" in pref_item: + attributes_data = pref_item.get("attributes", {}) + if not isinstance(attributes_data, dict): + logger.warning(f"User {email}, category {pref_item['category']}: attributes field is not a dict, re-initializing. Value: {attributes_data}") + attributes_data = {} + + current_score = pref_item.get("score", 0.0) + preference_dict[pref_item["category"]] = { + "category": pref_item["category"], + "score": current_score, + "attributes": attributes_data, + "is_explicit_score": current_score == 1.0 # Flag if score is 1.0 initially + } else: - logger.warning(f"Skipping invalid preference item for user {email}: {pref}") - + logger.warning(f"Skipping invalid preference item for user {email}: {pref_item}") - # Get taxonomy service taxonomy = await get_taxonomy_service(db) + if not taxonomy.taxonomy: # Critical check + logger.error(f"Taxonomy not loaded. Aborting preference processing for user {email}.") + await mark_processing_failed(db, email, submission_id) + raise HTTPException(status_code=500, detail="Taxonomy service not available, cannot process preferences.") + + categories_updated_this_run: Set[str] = set() - # Process entries based on data type, passing flattened demographics try: if data_type == "purchase": - await process_purchase_data(entries, preference_dict, taxonomy, user_demographics_flat) + await process_purchase_data(entries, preference_dict, taxonomy, user_demographics_flat, categories_updated_this_run) elif data_type == "search": - await process_search_data(entries, preference_dict, taxonomy, user_demographics_flat) + await process_search_data(entries, preference_dict, taxonomy, user_demographics_flat, categories_updated_this_run) else: - logger.warning(f"Unknown data type: {data_type}") - # Optionally, still try embedding fallback for unknown types - await process_with_embeddings(entries, data_type, preference_dict, taxonomy, user_demographics_flat) - + logger.warning(f"Unknown data type '{data_type}' for user {email}. Attempting embedding fallback.") + await process_with_embeddings(entries, data_type, preference_dict, taxonomy, user_demographics_flat, categories_updated_this_run) except Exception as e: logger.error(f"Error processing {data_type} data for {email}: {str(e)}", exc_info=True) - # Fall back to using embedding model for all data try: - logger.info(f"Attempting fallback embedding processing for {email} due to error.") - # Pass flattened demographics to fallback as well - await process_with_embeddings(entries, data_type, preference_dict, taxonomy, user_demographics_flat) + logger.info(f"Attempting fallback embedding processing for {data_type} data for user {email} after error.") + await process_with_embeddings(entries, data_type, preference_dict, taxonomy, user_demographics_flat, categories_updated_this_run) except Exception as fallback_error: - logger.error(f"Fallback processing also failed for {email}: {str(fallback_error)}", exc_info=True) - # Mark as failed before raising - await mark_processing_failed(db, email) # Assuming mark_processing_failed exists - raise HTTPException(status_code=500, detail=f"Processing failed: {str(e)}") - - # Convert preference_dict back to list - updated_preferences_list = list(preference_dict.values()) - - # Add normalization before database update - # Ensure normalize_categories handles potential issues gracefully + logger.error(f"Fallback embedding processing also failed for {email}: {fallback_error}", exc_info=True) + await mark_processing_failed(db, email, submission_id) + raise HTTPException(status_code=500, detail=f"Processing failed for {email}: {str(e)}") + + # --- Apply Decay and Removal Logic --- + final_preference_list_for_db = [] + for category_id, pref_data in list(preference_dict.items()): # Iterate over a copy for safe removal + current_score = pref_data["score"] + is_explicit = pref_data.get("is_explicit_score", False) + + if is_explicit: + pref_data["score"] = 1.0 # Ensure it remains 1.0 + logger.debug(f"User {email}: Preference {category_id} is explicit (score 1.0), no decay/capping.") + elif category_id not in categories_updated_this_run: + # Apply decay only if not updated in this run and not explicit 1.0 + current_score *= DECAY_FACTOR + pref_data["score"] = current_score + logger.debug(f"User {email}: Preference {category_id} not updated, decayed to {current_score:.4f}.") + + # For non-explicit preferences, ensure score is capped after update or decay + if not is_explicit: + pref_data["score"] = min(MAX_INFERRED_SCORE, pref_data["score"]) + + # Add to final list if score is above threshold OR it's an explicit 1.0 preference + if pref_data["score"] >= MIN_SCORE_THRESHOLD or is_explicit: + # Ensure attributes are cleaned up if they become empty dicts (optional) + if isinstance(pref_data.get("attributes"), dict) and not pref_data["attributes"]: + pref_data["attributes"] = None # Or remove key: del pref_data["attributes"] + + final_preference_list_for_db.append({ + "category": pref_data["category"], + "score": round(pref_data["score"], 4), # Round score for storage + "attributes": pref_data.get("attributes") # Use .get for safety + }) + else: + logger.info(f"User {email}: Preference {category_id} with score {pref_data['score']:.4f} removed (below threshold {MIN_SCORE_THRESHOLD}).") + # No need to explicitly remove from preference_dict as we build a new list + try: - normalized_preferences = await normalize_categories(updated_preferences_list, taxonomy) + normalized_preferences = await normalize_categories(final_preference_list_for_db, taxonomy) except Exception as norm_error: logger.error(f"Error normalizing categories for {email}: {norm_error}", exc_info=True) - normalized_preferences = updated_preferences_list # Use unnormalized as fallback + normalized_preferences = final_preference_list_for_db # Use unnormalized as fallback - # Update user preferences in database with normalized data update_time = datetime.now() await db.users.update_one( {"_id": user["_id"]}, { "$set": { "preferences": normalized_preferences, - "updatedAt": update_time + "updatedAt": update_time, + "demographicData.lastPreferenceUpdate": update_time # Track when prefs influenced demographics } } ) - logger.info(f"Successfully updated preferences for user {email} in DB.") + logger.info(f"Successfully updated preferences for user {email} in DB. Count: {len(normalized_preferences)}") - # Update the userData collection's processedStatus to "processed" - # Find the specific document(s) related to this submission batch if possible, - # otherwise update the oldest pending one for the user. - # This assumes the AI service passes back an ID or we can match based on content/timestamp. - # For simplicity, updating the first pending entry found for the email. try: - # Ideally, match on a unique ID for the submission batch if available - # submission_id = data.metadata.get("submissionId") - # if submission_id: - # match_criteria = {"_id": ObjectId(submission_id)} - # else: - match_criteria = {"email": email, "processedStatus": "pending"} - + match_criteria_processed = {"email": email, "processedStatus": "pending"} + if submission_id and ObjectId.is_valid(submission_id): # Prioritize specific submission ID + match_criteria_processed["_id"] = ObjectId(submission_id) + result = await db.userData.update_one( - match_criteria, + match_criteria_processed, {"$set": {"processedStatus": "processed"}} - # Consider adding sort if multiple pending exist and no ID is available ) if result.modified_count > 0: - logger.info(f"Updated userData status to 'processed' for {email}, modified: {result.modified_count}") + logger.info(f"Marked userData as processed for {email} (Submission: {submission_id or 'Oldest Pending'}). Modified: {result.modified_count}") else: - logger.warning(f"Could not find pending userData entry for {email} to mark as processed.") + logger.warning(f"No pending userData found to mark as processed for {email} with criteria: {match_criteria_processed}") except Exception as e: logger.error(f"Failed to update userData status for {email}: {str(e)}") - # --- Run Demographic Inference (Conditionally) --- inference_updated_user = False - if allow_inference: # <-- Check the flag + if allow_inference_setting: try: - logger.info(f"Starting demographic inference for user {email} ({user_id}) as allowInference is True.") - inference_updated_user = await run_inference_for_user(user_id, email, db) + logger.info(f"Running demographic inference for user {email} ({user_id})") + # Pass the fetched user document and taxonomy_service instance + inference_updated_user = await run_inference_for_user(user, taxonomy, db) if inference_updated_user: - logger.info(f"Demographic inference updated user document for {email}") - # Cache invalidation is handled within run_inference_for_user - else: - logger.info(f"Demographic inference did not result in updates for user {email}") + logger.info(f"Demographic inference updated user document for {email}.") except Exception as inference_error: logger.error(f"Demographic inference failed for user {email}: {inference_error}", exc_info=True) else: logger.info(f"Skipping demographic inference for user {email} ({user_id}) as allowInference is False.") - # --- End Demographic Inference --- - - # Invalidate relevant caches unconditionally after processing and inference attempt auth0_id = user.get("auth0Id") if auth0_id: - logger.info(f"Running post-processing cache invalidation for {auth0_id}.") - # Invalidate general user data and preferences + logger.info(f"Running post-processing cache invalidation for user {auth0_id}.") await invalidate_cache(f"{CACHE_KEYS['USER_DATA']}{auth0_id}") await invalidate_cache(f"{CACHE_KEYS['PREFERENCES']}{auth0_id}") logger.info(f"Invalidated USER_DATA and PREFERENCES caches for user {auth0_id} (post-processing)") - - # Invalidate store-specific caches if opt-in stores exist if user.get("privacySettings", {}).get("optInStores"): - user_object_id_str = str(user["_id"]) # Use user_id from the fetched user object for store_id in user["privacySettings"]["optInStores"]: - store_pref_key = f"{CACHE_KEYS['STORE_PREFERENCES']}{user_object_id_str}:{store_id}" - await invalidate_cache(store_pref_key) - logger.info(f"Invalidated STORE_PREFERENCES caches for user {auth0_id} (post-processing)") + await invalidate_cache(f"{CACHE_KEYS['STORE_PREFERENCES']}{user_id}:{store_id}") + logger.info(f"Invalidated STORE_PREFERENCES for user {auth0_id} for {len(user['privacySettings']['optInStores'])} stores.") else: logger.warning(f"Cannot invalidate caches for user {email} as auth0Id is missing.") - # Return updated preferences in the expected format return UserPreferences( user_id=user_id, preferences=[ @@ -204,536 +511,7 @@ async def process_user_data(data: UserDataEntry, db) -> UserPreferences: category=item["category"], score=item["score"], attributes=item.get("attributes") - ) for item in normalized_preferences # Use normalized preferences + ) for item in normalized_preferences ], - updated_at=update_time # Use the time of this update - ) - -# ... process_purchase_data, process_search_data, process_with_embeddings, normalize_categories ... -# (No changes needed inside these functions as they receive the flattened demographics dict) - -# --- Add helper for marking failed --- -async def mark_processing_failed(db, email: str): - """Marks the oldest pending userData entry for the email as failed.""" - try: - result = await db.userData.update_one( - {"email": email, "processedStatus": "pending"}, - {"$set": {"processedStatus": "failed"}}, - # sort={"timestamp": 1} # Optional: ensure oldest is marked if multiple exist - ) - if result.modified_count > 0: - logger.info(f"Marked a pending userData entry as 'failed' for {email}") - else: - logger.warning(f"Could not find pending userData entry for {email} to mark as failed.") - except Exception as e: - logger.error(f"Failed to mark userData as failed for {email}: {str(e)}") - -async def process_purchase_data(entries, preference_dict, taxonomy: TaxonomyService, demographics: Optional[Dict[str, Any]] = None): - """Process purchase data using rule-based system, considering demographics and buying patterns""" - category_counts = defaultdict(int) - attribute_counts = defaultdict(lambda: defaultdict(lambda: defaultdict(int))) - category_price_totals = defaultdict(float) - category_item_counts = defaultdict(int) - - # --- Demographic Usage (remains the same) --- - demographics = demographics or {} - gender = demographics.get("gender") - age = demographics.get("age") - income = demographics.get("incomeBracket") - country = demographics.get("country") - # --- Add inferred data --- - has_kids = demographics.get("inferredHasKids") # Boolean or None - relationship_status = demographics.get("inferredRelationshipStatus") # String or None - # --- Add NEW inferred data --- - employment_status = demographics.get("inferredEmploymentStatus") # String or None - education_level = demographics.get("inferredEducationLevel") # String or None - age_bracket = demographics.get("inferredAgeBracket") # String or None - # --- End Refined Demographic Usage --- - - # Count purchases, attributes, and track prices - for entry in entries: - for item in entry.get("items", []): - category_input = item.get("category") # Can be ID or Name - item_name = item.get("name") - quantity = item.get("quantity", 1) - price = item.get("price") - provided_attributes = item.get("attributes") # Attributes from the store - - if not category_input or not item_name: - logger.warning(f"Skipping item due to missing category or name: {item}") - continue - - # --- Resolve Category ID --- - category_id = None - if taxonomy.taxonomy: # Check if taxonomy is loaded - # Try direct ID lookup first - if category_input in taxonomy._id_to_name_map: - category_id = category_input - else: - # Try name lookup (case-insensitive) - category_id = taxonomy.get_category_id(category_input) - - if not category_id: - logger.warning(f"Could not resolve category '{category_input}' for item '{item_name}'. Skipping attribute processing for this item.") - # Decide if you still want to count the category score even if attributes can't be processed - # For now, we skip attribute processing but might still count category later if needed - continue # Skip attribute part if category is unresolved - # --- End Resolve Category ID --- - - - # --- Hybrid Attribute Logic --- - final_attributes = None - is_valid_provided = False - - # 1. Check if store provided valid attributes - if provided_attributes and isinstance(provided_attributes, dict): - try: - # Basic validation: Check if keys exist in taxonomy for this category - category_details = taxonomy.get_category_details(category_id) - if category_details and category_details.attributes: - valid_attr_names = {attr.name for attr in category_details.attributes} - # Check if all provided keys are valid attribute names for the category - is_valid_provided = all(key in valid_attr_names for key in provided_attributes.keys()) - if is_valid_provided: - final_attributes = provided_attributes - logger.debug(f"Using valid store-provided attributes for item: {item_name}") - else: - invalid_keys = [key for key in provided_attributes.keys() if key not in valid_attr_names] - logger.warning(f"Invalid attribute keys provided by store for item '{item_name}' in category '{category_id}': {invalid_keys}. Falling back to AI.") - else: - logger.warning(f"No attributes defined in taxonomy for category '{category_id}', cannot validate provided attributes for '{item_name}'. Falling back to AI.") - is_valid_provided = False # Cannot validate - - except Exception as val_err: - logger.warning(f"Error validating provided attributes for {item_name}: {val_err}. Falling back to AI.") - is_valid_provided = False - - # 2. Fallback to AI extraction if needed - if not final_attributes: - logger.debug(f"Attempting AI attribute extraction for item: {item_name}") - try: - # Call the AI extraction function using the embedding model - extracted_attributes = await extract_attributes_with_similarity( - item_name, category_id, taxonomy # Pass taxonomy service - ) - if extracted_attributes: - final_attributes = extracted_attributes - logger.info(f"Successfully extracted attributes via AI for '{item_name}': {final_attributes}") # Log success - else: - logger.debug(f"AI could not extract attributes for {item_name}") - except Exception as ai_err: - logger.error(f"AI attribute extraction failed for {item_name}: {ai_err}", exc_info=True) - final_attributes = None # Ensure it's None on failure - - # --- End Hybrid Attribute Logic --- - - # --- Update Category Counts (Moved here to ensure category_id is valid) --- - category_counts[category_id] += quantity - if price is not None: - category_price_totals[category_id] += price * quantity - category_item_counts[category_id] += quantity - # --- End Update Category Counts --- - - - # --- Attribute Scoring (Using final_attributes) --- - if final_attributes: - for attr_name, attr_value in final_attributes.items(): - # Ensure attr_value is a string (as expected from store or AI extraction) - if isinstance(attr_value, str): - value_str = attr_value.lower() # Normalize to lower case - attribute_counts[category_id][attr_name][value_str] += quantity - else: - logger.warning(f"Skipping attribute scoring for non-string value: {attr_name}={attr_value} in item '{item_name}'") - # --- End Attribute Scoring --- - - - # --- Update preference scores (Category level - remains the same) --- - total_items_overall = sum(category_counts.values()) - if total_items_overall > 0: - for category_id, count in category_counts.items(): - # --- Category Score Calculation (remains largely the same) --- - base_score = min(count / (total_items_overall * 0.5), 1.0) - boost_factor = 1.0 - category_name = taxonomy.get_category_name(category_id) - - # Apply demographic boosts (gender, age) - if gender == "female" and category_name in ["Fashion", "Beauty", "Skincare", "Makeup"]: - boost_factor *= 1.1 # Boost common female-associated categories - elif gender == "male" and category_name in ["Electronics", "Tools", "Laptops"]: - boost_factor *= 1.05 # Slightly boost common male-associated categories - if age: - # Boost tech/gaming for younger adults - if 18 <= age <= 30 and category_name in ["Smartphones", "Wearables", "Audio", "Gaming"]: - boost_factor *= 1.05 - # Boost health/home for older adults - elif age >= 50 and category_name in ["Health", "Home"]: - boost_factor *= 1.08 # Slightly higher boost for potential health needs - - # --- Apply inferred demographic boosts --- - if has_kids is True and category_name in ["Toys", "Baby", "Kids Clothing"]: # Add relevant categories - boost_factor *= 1.15 # Stronger boost for likely parents in child-related categories - logger.debug(f"Applying 'has_kids' boost to category {category_name}") - - if relationship_status == "married" and category_name in ["Home Goods", "Furniture", "Jewelry"]: # Example categories - boost_factor *= 1.05 # Slight boost for categories related to shared living/gifting - logger.debug(f"Applying 'married' boost to category {category_name}") - - # --- Apply NEW inferred boosts (Examples) --- - if employment_status == "student" and category_name in ["Laptops", "Books", "Stationery", "Budget Food"]: # Add relevant categories - boost_factor *= 1.1 # Boost student-related items - logger.debug(f"Applying 'student' boost to category {category_name}") - if employment_status == "employed" and category_name in ["Business Wear", "Office Supplies", "Travel"]: # Add relevant categories - boost_factor *= 1.05 # Slight boost for work-related items - logger.debug(f"Applying 'employed' boost to category {category_name}") - - if education_level in ["masters", "doctorate"] and category_name in ["Books", "Academic Journals", "Software"]: # Add relevant categories - boost_factor *= 1.08 # Speculative boost for academic/professional interests - logger.debug(f"Applying 'higher_education' boost to category {category_name}") - - # Use inferred age bracket ONLY if actual age is missing - effective_age_info = age if age is not None else age_bracket - if effective_age_info: - # Example using age bracket (less precise than actual age) - if effective_age_info == "18-24" and category_name in ["Fast Fashion", "Gaming", "Streaming Services"]: - boost_factor *= 1.05 # Boost categories popular with young adults - logger.debug(f"Applying '18-24' boost to category {category_name}") - elif effective_age_info == "65+" and category_name in ["Health", "Gardening", "Comfort Footwear"]: - boost_factor *= 1.1 # Boost categories relevant to seniors - logger.debug(f"Applying '65+' boost to category {category_name}") - # --- End NEW inferred boosts --- - - final_score = min(base_score * boost_factor, 1.0) - - # Update category score in preference_dict (using EMA) - if category_id not in preference_dict: - preference_dict[category_id] = { - "category": category_id, - "score": final_score, - "attributes": {} - } - else: - alpha = 0.3 - old_score = preference_dict[category_id]["score"] - preference_dict[category_id]["score"] = alpha * final_score + (1 - alpha) * old_score - # --- End Category Score Calculation --- - - - # --- Update preference scores (Attribute level - Adjusted) --- - for category_id, attrs in attribute_counts.items(): - if category_id in preference_dict: # Ensure category exists - if "attributes" not in preference_dict[category_id] or preference_dict[category_id]["attributes"] is None: - preference_dict[category_id]["attributes"] = {} # Initialize if missing - - for attr_name, values in attrs.items(): - if attr_name not in preference_dict[category_id]["attributes"]: - preference_dict[category_id]["attributes"][attr_name] = {} # Initialize specific attribute dict - - total_attr_count = sum(values.values()) - if total_attr_count > 0: - current_attr_prefs = preference_dict[category_id]["attributes"][attr_name] - # Decay existing scores slightly - for val, score in current_attr_prefs.items(): - current_attr_prefs[val] = max(0.0, score * 0.9) # Decay factor - - # Add new scores based on counts - for value_str, count in values.items(): - new_score_contribution = (count / total_attr_count) * 0.5 # Contribution weight - current_score = current_attr_prefs.get(value_str, 0.0) - current_attr_prefs[value_str] = min(1.0, current_score + new_score_contribution) - - # Normalize scores within the attribute so they sum roughly to 1 (optional but good practice) - total_score = sum(current_attr_prefs.values()) - if total_score > 0: - for val in current_attr_prefs: - current_attr_prefs[val] /= total_score - # --- End Update preference scores (Attribute level) --- - - -async def process_search_data(entries, preference_dict, taxonomy, demographics: Optional[Dict[str, Any]] = None): - """Process search data using embedding model, considering demographics""" - search_relevance = defaultdict(float) - - # --- Example Demographic Usage --- - demographics = demographics or {} - gender = demographics.get("gender") - age = demographics.get("age") - # --- Add inferred data --- - has_kids = demographics.get("inferredHasKids") - relationship_status = demographics.get("inferredRelationshipStatus") - # --- Add NEW inferred data --- - employment_status = demographics.get("inferredEmploymentStatus") - education_level = demographics.get("inferredEducationLevel") - age_bracket = demographics.get("inferredAgeBracket") - # --- End Example --- - - for entry in entries: - query = entry.get("query") - provided_category = entry.get("category") # Category explicitly sent with search - matched_category = None - match_score = 0.0 - - if not query and not provided_category: - continue # Skip if no query and no category provided - - # Determine the category - if provided_category: - matched_category = provided_category - match_score = 1.0 # Assume full relevance if category is provided - elif query: - # Use embeddings to match query to category - try: - match_result = await taxonomy.match_category(query) - if match_result["threshold_met"]: - matched_category = match_result["category"] - match_score = match_result["score"] - except Exception as e: - logger.error(f"Error matching query '{query}': {str(e)}") - - # If a category was determined, calculate boosted relevance - if matched_category: - relevance_boost = 1.0 - category_name = taxonomy.get_category_name(matched_category) # Get name for logic - - # --- Apply Demographic Boost to Relevance --- - if gender == 'female' and category_name in ['Fashion', 'Beauty', 'Skincare']: - relevance_boost *= 1.1 - elif gender == 'male' and category_name in ['Electronics', 'Tools', 'Laptops']: - relevance_boost *= 1.05 - - if age and 18 <= age <= 30 and category_name in ["Smartphones", "Gaming"]: # Add Gaming if exists - relevance_boost *= 1.05 - - # --- Apply inferred boosts --- - if has_kids is True and category_name in ["Toys", "Baby", "Kids Clothing"]: - relevance_boost *= 1.15 - logger.debug(f"Applying 'has_kids' boost to search relevance for {category_name}") - - if relationship_status == "married" and category_name in ["Home Goods", "Furniture", "Jewelry"]: - relevance_boost *= 1.05 - logger.debug(f"Applying 'married' boost to search relevance for {category_name}") - - # --- Apply NEW inferred boosts (Examples) --- - if employment_status == "student" and category_name in ["Laptops", "Books", "Stationery"]: - relevance_boost *= 1.1 - if education_level in ["masters", "doctorate"] and category_name in ["Books", "Academic Journals"]: - relevance_boost *= 1.08 - - effective_age_info = age if age is not None else age_bracket - if effective_age_info: - if effective_age_info == "18-24" and category_name in ["Fast Fashion", "Gaming"]: - relevance_boost *= 1.05 - elif effective_age_info == "65+" and category_name in ["Health", "Gardening"]: - relevance_boost *= 1.1 - # --- End NEW inferred boosts --- - - # Add boosted score to search relevance dict - search_relevance[matched_category] += match_score * relevance_boost - - # Normalize search relevance scores - if search_relevance: - max_relevance = max(search_relevance.values()) if search_relevance else 0 # Handle empty dict - if max_relevance > 0: - # Update preferences - for category, relevance in search_relevance.items(): - score = min(relevance / max_relevance, 1.0) - - if category not in preference_dict: - preference_dict[category] = { - "category": category, - "score": score, - "attributes": {} - } - else: - alpha = 0.2 - old_score = preference_dict[category]["score"] - preference_dict[category]["score"] = alpha * score + (1 - alpha) * old_score - -async def process_with_embeddings(entries, data_type, preference_dict, taxonomy, demographics: Optional[Dict[str, Any]] = None): - """Fallback processing using embeddings, potentially considering demographics""" - logger.info("Using embedding fallback processing") - - # --- Demographic Usage --- - demographics = demographics or {} - gender = demographics.get("gender") - age = demographics.get("age") - # --- Add inferred data --- - has_kids = demographics.get("inferredHasKids") - relationship_status = demographics.get("inferredRelationshipStatus") - # --- Add NEW inferred data --- - employment_status = demographics.get("inferredEmploymentStatus") - education_level = demographics.get("inferredEducationLevel") - age_bracket = demographics.get("inferredAgeBracket") - # --- End Demographic Usage --- - - # For purchase data - if data_type == "purchase": - items = [] - for entry in entries: - if "items" in entry: - items.extend([item.get("name", "") for item in entry["items"]]) - - for item_name in items: - try: - match_result = await taxonomy.match_category(item_name) - if match_result["threshold_met"]: - category = match_result["category"] - score = match_result["score"] - boost_factor = 1.0 - category_name = taxonomy.get_category_name(category) - - # --- Apply Demographic Boost (Similar to purchase logic) --- - if gender == "female" and category_name in ["Fashion", "Beauty"]: - boost_factor *= 1.1 - # Add other demographic boosts (age, etc.) here if desired - if age and age >= 50 and category_name == "Health": - boost_factor *= 1.08 - - # --- Apply inferred boosts --- - if has_kids is True and category_name in ["Toys", "Baby", "Kids Clothing"]: - boost_factor *= 1.15 - if relationship_status == "married" and category_name in ["Home Goods", "Furniture", "Jewelry"]: - boost_factor *= 1.05 - - # --- Apply NEW inferred boosts (Examples) --- - if employment_status == "student" and category_name in ["Laptops", "Books"]: - boost_factor *= 1.1 - if education_level in ["masters", "doctorate"] and category_name in ["Books"]: - boost_factor *= 1.08 - - effective_age_info = age if age is not None else age_bracket - if effective_age_info: - if effective_age_info == "18-24" and category_name in ["Gaming"]: - boost_factor *= 1.05 - elif effective_age_info == "65+" and category_name in ["Health"]: - boost_factor *= 1.1 - # --- End NEW inferred boosts --- - - final_score = min(score * boost_factor, 1.0) - # --- End Demographic Boost --- - - # Update preference dict (using final_score) - if category not in preference_dict: - preference_dict[category] = { - "category": category, - "score": final_score, # Use boosted score - "attributes": {} - } - else: - # Blend score (maybe use a lower weight for embedding matches?) - alpha_embed = 0.2 - old_score = preference_dict[category]["score"] - preference_dict[category]["score"] = alpha_embed * final_score + (1 - alpha_embed) * old_score - # Alternative: Just take the max? - # preference_dict[category]["score"] = max(old_score, final_score * 0.8) - except Exception as e: - logger.error(f"Error processing item '{item_name}' via embedding: {str(e)}") - - # For search data, call the updated search processor (already passes demographics) - elif data_type == "search": - await process_search_data(entries, preference_dict, taxonomy, demographics) - -async def normalize_categories(preferences, taxonomy): - """Ensure all categories use IDs instead of names""" - normalized = [] - - # Build name-to-id mapping - name_to_id = {} - id_to_name = {} # Also build reverse mapping for safety check - for cat in taxonomy.taxonomy.categories: - name_to_id[cat.name.lower()] = cat.id - id_to_name[cat.id] = cat.name # Store ID to Name mapping - - for pref in preferences: - category_key = pref["category"] - # Check if the key is a name and needs conversion - if isinstance(category_key, str) and category_key.lower() in name_to_id: - pref["category"] = name_to_id[category_key.lower()] - # Safety check: Ensure the final category key is a valid ID present in the taxonomy - elif category_key not in id_to_name: - logger.warning(f"Category '{category_key}' not found in taxonomy IDs during normalization. Skipping.") - continue # Skip this preference if the category ID is invalid - - normalized.append(pref) - - return normalized - -async def extract_attributes_with_similarity(item_name: str, category_id: str, taxonomy_service: TaxonomyService) -> Optional[Dict[str, str]]: - """ - Uses the semantic embedding model to extract attributes for an item by comparing - the item name to potential attribute values defined in the taxonomy. - Returns a dictionary like {"color": "blue", "size": "M"} or None. - """ - if not taxonomy_service.embedding_model: - logger.warning("AI Extraction: Embedding model not available in TaxonomyService.") - return None - - logger.debug(f"AI Extraction: Processing '{item_name}' in category '{category_id}'") - extracted = {} - - # 1. Get category details and expected attributes/values - category_details = taxonomy_service.get_category_details(category_id) - if not category_details or not category_details.attributes: - logger.debug(f"AI Extraction: No attributes defined in taxonomy for category {category_id}") - return None - - # Prepare list of attributes and their potential values for this category - attributes_to_check = [] - for attr in category_details.attributes: - if attr.values: # Only consider attributes with defined values - attributes_to_check.append({"name": attr.name, "values": attr.values}) - - if not attributes_to_check: - logger.debug(f"AI Extraction: No attributes with values defined for category {category_id}") - return None - - logger.debug(f"AI Extraction: Expected attributes for {category_id}: {[a['name'] for a in attributes_to_check]}") - - try: - # 2. Generate embedding for the item name - item_embedding = taxonomy_service.embedding_model.encode(item_name.lower(), convert_to_tensor=True) - - # 3. Iterate through attributes and their values - for attribute_info in attributes_to_check: - attr_name = attribute_info["name"] - possible_values = attribute_info["values"] - - if not possible_values: - continue - - # Generate embeddings for all possible values of this attribute - value_embeddings = taxonomy_service.embedding_model.encode([v.lower() for v in possible_values], convert_to_tensor=True) - - # Calculate cosine similarities between item name and all values - # Use pytorch_cos_sim for efficiency - similarities = util.pytorch_cos_sim(item_embedding, value_embeddings)[0] # Get the first row (item vs all values) - - # Find the value with the highest similarity - best_match_idx = similarities.argmax().item() # Get index of max value - highest_similarity = similarities[best_match_idx].item() # Get the max similarity score - - logger.debug(f"AI Extraction: Attribute '{attr_name}', Best match: '{possible_values[best_match_idx]}', Score: {highest_similarity:.4f}") - - # 4. Check against threshold and store if match is strong enough - if highest_similarity >= ATTRIBUTE_SIMILARITY_THRESHOLD: - best_match_value = possible_values[best_match_idx] - # Simple conflict resolution: If we already extracted a value for this attribute, - # only overwrite if the new score is significantly higher (e.g., > 0.1 difference). - # A more complex approach could consider multiple high-scoring values. - if attr_name in extracted: - # We need the previous score to compare - this simple approach just takes the first good match. - # For improvement, store scores alongside values during iteration. - logger.debug(f"AI Extraction: Attribute '{attr_name}' already extracted ('{extracted[attr_name]}'). Keeping first match above threshold.") - else: - extracted[attr_name] = best_match_value - logger.debug(f"AI Extraction: Extracted '{attr_name}' = '{best_match_value}' (Score: {highest_similarity:.4f})") - - - except Exception as e: - logger.error(f"AI Extraction: Error during embedding/similarity calculation for '{item_name}': {e}", exc_info=True) - return None - - if not extracted: - logger.debug(f"AI Extraction: No attributes met threshold for '{item_name}'") - return None - - return extracted \ No newline at end of file + updated_at=update_time + ) \ No newline at end of file diff --git a/api-service/.dockerignore b/tapiro-api-external/.dockerignore similarity index 100% rename from api-service/.dockerignore rename to tapiro-api-external/.dockerignore diff --git a/api-service/.eslintrc.js b/tapiro-api-external/.eslintrc.js similarity index 100% rename from api-service/.eslintrc.js rename to tapiro-api-external/.eslintrc.js diff --git a/api-service/.prettierignore b/tapiro-api-external/.prettierignore similarity index 100% rename from api-service/.prettierignore rename to tapiro-api-external/.prettierignore diff --git a/api-service/.prettierrc.js b/tapiro-api-external/.prettierrc.js similarity index 100% rename from api-service/.prettierrc.js rename to tapiro-api-external/.prettierrc.js diff --git a/api-service/README.md b/tapiro-api-external/README.md similarity index 100% rename from api-service/README.md rename to tapiro-api-external/README.md diff --git a/tapiro-api-external/api/openapi.yaml b/tapiro-api-external/api/openapi.yaml new file mode 100644 index 0000000..c0384bb --- /dev/null +++ b/tapiro-api-external/api/openapi.yaml @@ -0,0 +1,341 @@ +openapi: 3.0.0 +info: + title: Tapiro Store Operations API + description: API for external services to interact with Tapiro's Store Operations. + version: 1.0.0 + contact: + name: Tapiro Support + email: tapirosupport@gmail.com +tags: + - name: Health + description: Health check endpoints + - name: Data Collection + description: Endpoints for data collection and submission + - name: Data Retrieval + description: Endpoints for retrieving user preferences and data +servers: + - description: SwaggerHub API Auto Mocking + url: https://virtserver.swaggerhub.com/CHAMATHDEWMINA25/Tapiro/1.0.0 + - url: https://api.tapiro.com/v1 + description: Production API + - url: http://localhost:3001 + description: Development API +paths: + /health: + get: + tags: [Health] + summary: Health Check + description: Comprehensive health check that verifies API and dependencies + operationId: healthCheck + responses: + "200": + description: Health status information + content: + application/json: + schema: + $ref: "#/components/schemas/HealthStatus" + "500": + $ref: "#/components/responses/InternalServerError" + x-swagger-router-controller: Health + + /ping: + get: + tags: [Health] + summary: Simple Uptime Check + description: Simple ping endpoint for uptime monitoring + operationId: ping + responses: + "200": + description: Simple response indicating the API is up + content: + application/json: + schema: + $ref: "#/components/schemas/PingStatus" + x-swagger-router-controller: Health + + /users/data: + post: + tags: [Data Collection] + summary: Submit user data + description: Submit purchase/search history for a user (authenticated via API key) + operationId: submitUserData + security: + - apiKey: [] + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/UserData" + required: true + responses: + "202": + description: Data accepted for processing + "401": + $ref: "#/components/responses/UnauthorizedError" + "400": + $ref: "#/components/responses/BadRequestError" + "500": + $ref: "#/components/responses/InternalServerError" + x-swagger-router-controller: StoreOperations + + /users/{userId}/preferences: + get: + tags: [Data Retrieval] + summary: Get user preferences + description: Retrieve preferences for targeted advertising + operationId: getUserPreferences + parameters: + - name: userId + in: path + required: true + schema: + type: string + security: + - apiKey: [] + responses: + "200": + description: UserPreferences retrieved + content: + application/json: + schema: + $ref: "#/components/schemas/UserPreferences" + "403": + $ref: "#/components/responses/ForbiddenError" + "404": + $ref: "#/components/responses/NotFoundError" + "401": + $ref: "#/components/responses/UnauthorizedError" + "500": + $ref: "#/components/responses/InternalServerError" + x-swagger-router-controller: StoreOperations + +components: + schemas: + UserData: + type: object + required: + - email + - dataType + - entries + properties: + email: + type: string + format: email + description: User's email address + dataType: + type: string + enum: [purchase, search] + description: Type of data being submitted + entries: + type: array + items: + oneOf: + - $ref: "#/components/schemas/PurchaseEntry" + - $ref: "#/components/schemas/SearchEntry" + UserPreferences: + type: object + properties: + preferences: + type: array + items: + $ref: "#/components/schemas/PreferenceItem" + PurchaseEntry: + type: object + required: + - timestamp + - items + properties: + timestamp: + type: string + format: date-time + description: ISO 8601 timestamp of when the purchase occurred. + items: + type: array + description: List of items included in the purchase. + items: + $ref: "#/components/schemas/PurchaseItem" + totalValue: + type: number + format: float + description: Optional total value of the purchase event. + + PurchaseItem: + type: object + required: + - name + - category + properties: + sku: + type: string + description: Stock Keeping Unit or unique product identifier. + name: + type: string + description: Name of the purchased item. + category: + type: string + description: Category ID or name matching the taxonomy. + price: + type: number + format: float + description: Price of a single unit of the item. + quantity: + type: integer + description: Number of units purchased. + default: 1 + attributes: + type: object + description: Key-value pairs representing product attributes. + additionalProperties: + type: string + + SearchEntry: + type: object + required: + - timestamp + - query + properties: + timestamp: + type: string + format: date-time + description: ISO 8601 timestamp of when the search occurred. + query: + type: string + description: The search query string entered by the user. + category: + type: string + description: Optional category context provided during the search. + results: + type: integer + description: Optional number of results returned for the search query. + clicked: + type: array + description: Optional list of product IDs or SKUs clicked from the search results. + items: + type: string + + PreferenceItem: + type: object + required: + - category + - score + properties: + category: + type: string + description: Category ID or name. + score: + type: number + format: float + minimum: 0.0 + maximum: 1.0 + description: Preference score (0.0-1.0). + attributes: + type: object + description: Category-specific attribute preferences. + additionalProperties: + type: string + + HealthStatus: + type: object + properties: + status: + type: string + enum: [healthy, degraded, unhealthy] + description: Overall health status of the Health + timestamp: + type: string + format: date-time + description: Time of health check + service: + type: string + description: Service name + dependencies: + type: object + properties: + database: + type: string + enum: [connected, disconnected, degraded] + cache: + type: string + enum: [connected, disconnected, degraded] + auth: + type: string + enum: [connected, disconnected, degraded, unknown] + + PingStatus: + type: object + properties: + status: + type: string + enum: [ok] + timestamp: + type: string + format: date-time + + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + description: HTTP status code of the error + example: 400 + message: + type: string + description: Description of the error + example: "Bad request - invalid input" + details: + type: object + description: Additional details about the error (optional) + additionalProperties: true + example: { field: "username", issue: "already taken" } + + responses: + BadRequestError: + description: Bad request - invalid input + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + UnauthorizedError: + description: Unauthorized - authentication required + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + ForbiddenError: + description: Forbidden - insufficient permissions + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + NotFoundError: + description: Not found - resource doesn't exist + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + ConflictError: + description: Conflict - resource already exists + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + InternalServerError: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + securitySchemes: + apiKey: + type: apiKey + in: header + name: X-API-Key diff --git a/api-service/clients/AIService.js b/tapiro-api-external/clients/AIService.js similarity index 97% rename from api-service/clients/AIService.js rename to tapiro-api-external/clients/AIService.js index 39c5eb6..99c8540 100644 --- a/api-service/clients/AIService.js +++ b/tapiro-api-external/clients/AIService.js @@ -21,6 +21,7 @@ exports.processUserData = async function (userData) { email: userData.email, data_type: userData.data_type, userId: userData.metadata?.userId, + entriesHash: JSON.stringify(userData.entries) }; const cacheKey = `${CACHE_KEYS.AI_REQUEST}${JSON.stringify(keyData)}`; const cachedResponse = await getCache(cacheKey); diff --git a/tapiro-api-external/controllers/Health.js b/tapiro-api-external/controllers/Health.js new file mode 100644 index 0000000..23cf63f --- /dev/null +++ b/tapiro-api-external/controllers/Health.js @@ -0,0 +1,22 @@ +const utils = require('../utils/writer.js'); +const Health = require('../service/HealthService.js'); + +module.exports.healthCheck = function healthCheck(req, res, next) { + Health.healthCheck(req) + .then((response) => { + utils.writeJson(res, response); + }) + .catch((response) => { + utils.writeJson(res, response); + }); +}; + +module.exports.ping = function ping(req, res, next) { + Health.ping(req) + .then((response) => { + utils.writeJson(res, response); + }) + .catch((response) => { + utils.writeJson(res, response); + }); +}; diff --git a/api-service/controllers/StoreOperations.js b/tapiro-api-external/controllers/StoreOperations.js similarity index 98% rename from api-service/controllers/StoreOperations.js rename to tapiro-api-external/controllers/StoreOperations.js index d62c4aa..c45d49f 100644 --- a/api-service/controllers/StoreOperations.js +++ b/tapiro-api-external/controllers/StoreOperations.js @@ -1,5 +1,5 @@ const utils = require('../utils/writer.js'); -const StoreOperations = require('../service/StoreOperationsService'); +const StoreOperations = require('../service/StoreOperationsService.js'); module.exports.getUserPreferences = function getUserPreferences(req, res, next, userId) { StoreOperations.getUserPreferences(req, userId) diff --git a/tapiro-api-external/dockerfile b/tapiro-api-external/dockerfile new file mode 100644 index 0000000..3d2ff03 --- /dev/null +++ b/tapiro-api-external/dockerfile @@ -0,0 +1,8 @@ +FROM node:18-alpine AS development +WORKDIR /app +COPY package*.json ./ +RUN --mount=type=cache,target=/root/.npm npm install +COPY . . +RUN npm install -g nodemon +EXPOSE 3001 +CMD ["nodemon", "index.js"] diff --git a/tapiro-api-external/index.js b/tapiro-api-external/index.js new file mode 100644 index 0000000..dcee27e --- /dev/null +++ b/tapiro-api-external/index.js @@ -0,0 +1,72 @@ +require('dotenv').config(); +const path = require('path'); +const http = require('http'); +const express = require('express'); +const oas3Tools = require('oas3-tools'); +const cors = require('cors'); +const { validateApiKey } = require('./middleware/apiKeyMiddleware'); +const { connectDB } = require('./utils/mongoUtil'); +const { connectRedis } = require('./utils/redisUtil'); +const { createCorsOptions } = require('./utils/corsUtil'); + +const serverPort = process.env.PORT || 3001; + +const allowedOrigins = process.env.ALLOWED_ORIGINS ? + process.env.ALLOWED_ORIGINS.split(',') : + []; + +// Create CORS options with the whitelist +const corsOptions = createCorsOptions(allowedOrigins); + +// swaggerRouter configuration +const options = { + routing: { + controllers: path.join(__dirname, './controllers') + }, + openApiValidator: { + validateSecurity: { + handlers: { + apiKey: validateApiKey, + }, + }, + }, +}; + +// Create Express app +const app = express(); + +// Apply CORS middleware +app.use(cors(corsOptions)); + +// Configure oas3Tools with the OpenAPI spec +const expressAppConfig = oas3Tools.expressAppConfig( + path.join(__dirname, 'api/openapi.yaml'), + options, +); + +// Apply middleware +app.use(expressAppConfig.getApp()); + +// Initialize connections and start the server +connectDB() + .then(() => connectRedis()) + .then(() => { + http.createServer(app).listen(serverPort, () => { + console.log( + 'External API server is listening on port %d (http://localhost:%d)', + serverPort, + serverPort, + ); + console.log('Swagger-ui is available on http://localhost:%d/docs', serverPort); + + // Log CORS configuration + console.log(`CORS configuration: ${allowedOrigins.length > 0 ? + `Restricted to ${allowedOrigins.join(', ')}` : + 'Allowing all origins'}`); + }); + }) + .catch((err) => { + console.error('Startup error:', err); + }); + +module.exports = app; \ No newline at end of file diff --git a/api-service/jest.config.js b/tapiro-api-external/jest.config.js similarity index 100% rename from api-service/jest.config.js rename to tapiro-api-external/jest.config.js diff --git a/api-service/middleware/apiKeyMiddleware.js b/tapiro-api-external/middleware/apiKeyMiddleware.js similarity index 50% rename from api-service/middleware/apiKeyMiddleware.js rename to tapiro-api-external/middleware/apiKeyMiddleware.js index a5195c8..d12a5eb 100644 --- a/api-service/middleware/apiKeyMiddleware.js +++ b/tapiro-api-external/middleware/apiKeyMiddleware.js @@ -41,78 +41,72 @@ const validateApiKey = async (req, scopes, schema) => { throw new Error('API key required'); } - // Try to get store ID from cache first - const cacheKey = `${CACHE_KEYS.API_KEY}${apiKey}`; - const cachedStoreId = await getCache(cacheKey); - const prefix = apiKey.substring(0, 8); - - if (cachedStoreId) { - // Even if we have a cached entry, verify that the key is still active - const db = getDB(); - const storeWithKey = await db.collection('stores').findOne( - { - _id: new ObjectId(cachedStoreId), - 'apiKeys.prefix': prefix, - 'apiKeys.status': 'active' - }, - { projection: { 'apiKeys.$': 1 } } - ); - - // If key is no longer active, remove from cache and reject - if (!storeWithKey) { - console.log(`API key ${prefix} was in cache but is no longer active`); - await invalidateCache(cacheKey); + const apiKeyDetailsCacheKey = `${CACHE_KEYS.API_KEY_DETAILS}${apiKey}`; // New cache key + let cachedApiKeyDetails = await getCache(apiKeyDetailsCacheKey); + + if (cachedApiKeyDetails) { + cachedApiKeyDetails = JSON.parse(cachedApiKeyDetails); + if (cachedApiKeyDetails.status === 'active') { + req.storeId = cachedApiKeyDetails.storeId; + req.keyId = cachedApiKeyDetails.keyId; // Set keyId for tracking + trackApiUsage(req, apiKey, req.storeId, req.keyId); + return true; + } else { + // Key was cached but is not active (e.g. revoked) throw new Error('API key revoked or invalid'); } - - // Key is still valid - req.storeId = cachedStoreId; - - // Track API usage - get keyId from found key - const keyId = storeWithKey.apiKeys[0].keyId; - trackApiUsage(req, apiKey, cachedStoreId, keyId); - - return true; } - // If not in cache, look up in database + // If not in cache or not active, look up in database const db = getDB(); + const prefix = apiKey.substring(0, 8); - // Find store with matching API key prefix const store = await db.collection('stores').findOne({ 'apiKeys.prefix': prefix, - 'apiKeys.status': 'active', + 'apiKeys.status': 'active', // Query for active keys directly }); if (!store) { - throw new Error('Invalid API key'); + throw new Error('Invalid API key or store not found'); } - // Find the specific API key - const foundKey = store.apiKeys.find((key) => key.prefix === prefix && key.status === 'active'); + const foundKey = store.apiKeys.find( + (key) => key.prefix === prefix && key.status === 'active' + ); if (!foundKey) { - throw new Error('Invalid API key'); + // This case should ideally be covered by the store query if prefix is unique enough + // and status is checked. + throw new Error('Invalid API key (specific key not found or inactive)'); } const keyHash = crypto.createHash('sha256').update(apiKey).digest('hex'); - // Check if the hash matches if (keyHash !== foundKey.hashedKey) { - throw new Error('Invalid API key'); + throw new Error('Invalid API key (hash mismatch)'); } - // Set store ID in request and cache the mapping + // Set store ID and key ID in request req.storeId = store._id.toString(); - await setCache(cacheKey, req.storeId, { EX: CACHE_TTL.API_KEY || 1800 }); + req.keyId = foundKey.keyId; + + // Cache the API key details (storeId, keyId, status) + const apiKeyDetailsToCache = { + storeId: req.storeId, + keyId: req.keyId, + status: foundKey.status, // Should be 'active' here + }; + await setCache(apiKeyDetailsCacheKey, JSON.stringify(apiKeyDetailsToCache), { EX: CACHE_TTL.API_KEY || 1800 }); - // Track API usage - trackApiUsage(req, apiKey, req.storeId, foundKey.keyId); + trackApiUsage(req, apiKey, req.storeId, req.keyId); return true; } catch (error) { - console.error('API key validation failed:', error); - throw error; + console.error('API key validation failed:', error.message); // Log only message for brevity + // Re-throw to be handled by the oas3-tools error handler or a global error handler + // which should return a proper HTTP error response. + // Avoid directly sending res.status here as it bypasses standard error flow. + throw error; } }; diff --git a/api-service/package-lock.json b/tapiro-api-external/package-lock.json similarity index 100% rename from api-service/package-lock.json rename to tapiro-api-external/package-lock.json diff --git a/tapiro-api-external/package.json b/tapiro-api-external/package.json new file mode 100644 index 0000000..7120ec3 --- /dev/null +++ b/tapiro-api-external/package.json @@ -0,0 +1,47 @@ +{ + "name": "tapiro-external-api", + "version": "1.0.0", + "description": "API for AI-Driven Personalized Advertising System", + "main": "index.js", + "scripts": { + "prestart": "npm install", + "start": "node index.js", + "lint": "eslint --fix .", + "test": "jest", + "test:unit": "jest --testMatch='**/__tests__/unit/**/*.test.js'", + "test:integration": "jest --testMatch='**/__tests__/integration/**/*.test.js'", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage", + "format": "prettier --write .", + "format:check": "prettier --check ." + }, + "keywords": [ + "swagger" + ], + "license": "Unlicense", + "private": true, + "dependencies": { + "axios": "^1.7.9", + "axios-retry": "^4.5.0", + "connect": "^3.2.0", + "cors": "^2.8.5", + "express": "^4.21.2", + "js-yaml": "^3.3.0", + "jsonwebtoken": "^9.0.2", + "mongodb": "^6.13.0", + "oas3-tools": "^2.2.3", + "redis": "^4.7.0" + }, + "devDependencies": { + "dotenv": "^16.4.7", + "eslint": "^8.57.1", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-prettier": "^5.2.3", + "jest": "^29.7.0", + "nock": "^14.0.1", + "prettier": "^3.5.0", + "supertest": "^7.0.0" + } +} diff --git a/tapiro-api-external/service/HealthService.js b/tapiro-api-external/service/HealthService.js new file mode 100644 index 0000000..8a744a0 --- /dev/null +++ b/tapiro-api-external/service/HealthService.js @@ -0,0 +1,71 @@ +const { getDB } = require('../utils/mongoUtil'); +const { pingRedis } = require('../utils/redisUtil'); +const { respondWithCode } = require('../utils/writer'); +const AIService = require('../clients/AIService'); + +/** + * Simplified health check for external API + */ +exports.healthCheck = async function (req) { + try { + const response = { + status: 'healthy', + timestamp: new Date().toISOString(), + service: 'tapiro-external-api', + version: '1.0.0', + dependencies: { + database: 'disconnected', + cache: 'disconnected', + ai_service: 'disconnected' + }, + }; + + // Check MongoDB connection + try { + const db = getDB(); + await db.command({ ping: 1 }); + response.dependencies.database = 'connected'; + } catch (error) { + console.error('Database health check failed:', error); + response.status = 'degraded'; + } + + // Check Redis connection + try { + const pingResult = await pingRedis(); + response.dependencies.cache = pingResult === 'PONG' ? 'connected' : 'degraded'; + } catch (error) { + console.error('Redis health check failed:', error); + response.status = 'degraded'; + } + + // Check AI service connection + try { + const aiHealth = await AIService.checkHealth(); + response.dependencies.ai_service = aiHealth.status; + } catch (error) { + console.error('AI service health check failed:', error); + response.status = 'degraded'; + } + + return respondWithCode(200, response); + } catch (error) { + console.error('Health check failed:', error); + return respondWithCode(500, { + status: 'unhealthy', + timestamp: new Date().toISOString(), + error: 'Internal health check error', + }); + } +}; + +/** + * Simple ping endpoint for uptime monitoring + */ +exports.ping = async function (req) { + return respondWithCode(200, { + status: 'ok', + timestamp: new Date().toISOString(), + service: 'tapiro-external-api' + }); +}; diff --git a/api-service/service/StoreOperationsService.js b/tapiro-api-external/service/StoreOperationsService.js similarity index 77% rename from api-service/service/StoreOperationsService.js rename to tapiro-api-external/service/StoreOperationsService.js index f03cea6..ebe1dc6 100644 --- a/api-service/service/StoreOperationsService.js +++ b/tapiro-api-external/service/StoreOperationsService.js @@ -102,7 +102,6 @@ exports.submitUserData = async function (req, body) { const db = getDB(); const { email, dataType, entries, metadata } = body; - if (!Array.isArray(entries)) { return respondWithCode(400, { code: 400, @@ -200,44 +199,57 @@ exports.submitUserData = async function (req, body) { timestamp: new Date(), }); + const insertedId = result.insertedId; // Get the ID of the inserted document + // Invalidate the preferences cache await invalidateCache(`${CACHE_KEYS.STORE_PREFERENCES}${user._id}:${req.storeId}`); await invalidateCache(`${CACHE_KEYS.PREFERENCES}${user.auth0Id}`); - // SINGLE API CALL to the AI service - try { - const aiResponse = await AIService.processUserData({ - email, - data_type: dataType, - entries, - metadata: { - ...metadata, - storeId: req.storeId, - userId: user._id.toString(), - timestamp: new Date(), - }, - }); + // Call AI service but DO NOT await it. + // Fire-and-forget: + AIService.processUserData({ + email, + data_type: dataType, + entries, // Send original entries as AI service might have its own processing + metadata: { + ...metadata, + storeId: req.storeId, + userId: user._id.toString(), + submissionId: insertedId.toString(), // Pass the userData document ID + timestamp: new Date(), + }, + }).then(aiResponse => { + // Successfully queued with AI service. + // Optionally, update the userData document status if AI service confirms receipt, + // but this happens in the background. + // For now, the ML service handles updating its own status or the userData status. + console.log(`AI processing initiated for submission ${insertedId.toString()}: ${aiResponse.status}`); + }).catch(async aiError => { + // AI service call failed even to initiate. + // Mark the specific userData entry as 'failed' to indicate an issue with AI queuing. + console.error(`AI service initiation failed for submission ${insertedId.toString()}:`, aiError); + try { + await db.collection('userData').updateOne( + { _id: insertedId }, + { $set: { processedStatus: 'ai_submission_failed' } } // New status + ); + } catch (updateError) { + console.error(`Failed to mark userData ${insertedId.toString()} as ai_submission_failed:`, updateError); + } + }); + + // Return 202 Accepted immediately + return respondWithCode(202, { + message: 'Data accepted for processing', + submissionId: insertedId.toString(), + }); - return respondWithCode(202, { - message: 'Data accepted for processing', - aiProcessing: aiResponse.status, - }); - } catch (aiError) { - console.error('AI service processing failed, but data was stored:', aiError); - - // Update status to failed - await db - .collection('userData') - .updateOne({ _id: result.insertedId }, { $set: { processedStatus: 'failed' } }); - - // Still return 202 since we saved the data and can process it later - return respondWithCode(202, { - message: 'Data accepted but AI processing delayed', - retryScheduled: true, - }); - } } catch (error) { console.error('Submit user data failed:', error); + // Ensure a 500 is returned for unexpected errors during the synchronous part + if (error.code && error.message) { // If it's already a respondWithCode structure + return error; + } return respondWithCode(500, { code: 500, message: 'Internal server error' }); } }; diff --git a/tapiro-api-external/utils/cacheConfig.js b/tapiro-api-external/utils/cacheConfig.js new file mode 100644 index 0000000..8d19004 --- /dev/null +++ b/tapiro-api-external/utils/cacheConfig.js @@ -0,0 +1,30 @@ +/** + * Standard cache TTL values in seconds + */ +const CACHE_TTL = { + TOKEN: 3600, // Regular tokens - 1 hour + ADMIN_TOKEN: 82800, // Management tokens - 23 hours + USER_DATA: 3600, // User profiles - 1 hour + STORE_DATA: 3600, // Store profiles - 1 hour + API_KEY: 1800, // API keys - 30 minutes + INVALIDATION: 1, // Short TTL for invalidation + AI_REQUEST: 60, // AI service requests - 1 minute +}; + +/** + * Cache key prefixes + */ +const CACHE_KEYS = { + USER_DATA: 'userdata:', // User data from Auth0 + STORE_DATA: 'store:', // Store data from DB + API_KEY: 'apikey:', // Original key for storeId mapping (can be phased out or kept for other uses) + API_KEY_DETAILS: 'apikey_details:', // New key for caching full API key info + SCOPES: 'scopes:', // Token to scopes mapping + ADMIN_TOKEN: 'auth0_management_token', // Auth0 management token + PREFERENCES: 'preferences:', // User preferences + STORE_PREFERENCES: 'prefs:', // Store preferences + TAXONOMY: 'taxonomy:current', // <-- Add this line + AI_REQUEST: 'ai_request:', // AI service request cache +}; + +module.exports = { CACHE_TTL, CACHE_KEYS }; diff --git a/tapiro-api-external/utils/corsUtil.js b/tapiro-api-external/utils/corsUtil.js new file mode 100644 index 0000000..3704402 --- /dev/null +++ b/tapiro-api-external/utils/corsUtil.js @@ -0,0 +1,37 @@ +const corsUtil = { + /** + * Create dynamic CORS options with whitelist support + * @param {Array} whitelist - Optional list of allowed origins + * @return {Object} - CORS options object + */ + createCorsOptions: function(whitelist = []) { + return { + origin: function(origin, callback) { + // Allow requests with no origin (like mobile apps, curl requests) + if (!origin) { + return callback(null, true); + } + + // If whitelist is empty, allow all origins + if (whitelist.length === 0) { + return callback(null, true); + } + + // Check against whitelist + if (whitelist.indexOf(origin) !== -1 || whitelist.includes('*')) { + return callback(null, true); + } + + // Log rejected origins + console.warn(`CORS blocked request from origin: ${origin}`); + callback(null, false); + }, + methods: ['GET', 'POST', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'X-API-Key'], + credentials: true, + maxAge: 86400, + }; + } +}; + +module.exports = corsUtil; \ No newline at end of file diff --git a/tapiro-api-external/utils/mongoUtil.js b/tapiro-api-external/utils/mongoUtil.js new file mode 100644 index 0000000..19364ce --- /dev/null +++ b/tapiro-api-external/utils/mongoUtil.js @@ -0,0 +1,29 @@ +require('dotenv').config(); +const { MongoClient } = require('mongodb'); + +const uri = process.env.MONGODB_URI; +const client = new MongoClient(uri); +const dbname = process.env.DB_NAME; + +let db; + +async function connectDB() { + try { + await client.connect(); + db = client.db(dbname); + + console.log('External API connected to MongoDB Atlas'); + } catch (error) { + console.error('MongoDB connection error:', error); + process.exit(1); + } +} + +function getDB() { + if (!db) { + throw new Error('Database not initialized. Call connectDB first.'); + } + return db; +} + +module.exports = { connectDB, getDB }; \ No newline at end of file diff --git a/api-service/utils/redisUtil.js b/tapiro-api-external/utils/redisUtil.js similarity index 100% rename from api-service/utils/redisUtil.js rename to tapiro-api-external/utils/redisUtil.js diff --git a/api-service/utils/writer.js b/tapiro-api-external/utils/writer.js similarity index 100% rename from api-service/utils/writer.js rename to tapiro-api-external/utils/writer.js diff --git a/tapiro-api-internal/.dockerignore b/tapiro-api-internal/.dockerignore new file mode 100644 index 0000000..588f144 --- /dev/null +++ b/tapiro-api-internal/.dockerignore @@ -0,0 +1,46 @@ +# Dependencies +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Testing +coverage +*.lcov +.nyc_output + +# Environment +.env +.env.* + +# Development +.git +.gitignore +README.md +Dockerfile* +docker-compose* +*.md + +# OS files +.DS_Store +*.swp +*.swo + +# IDE configurations +.idea +.vscode +*.sublime-workspace +*.sublime-project + +# Logs +logs +*.log + +# Build output +dist +build + +# Test files +__tests__ +test +tests \ No newline at end of file diff --git a/tapiro-api-internal/.eslintrc.js b/tapiro-api-internal/.eslintrc.js new file mode 100644 index 0000000..0f73ffd --- /dev/null +++ b/tapiro-api-internal/.eslintrc.js @@ -0,0 +1,16 @@ +module.exports = { + extends: ['airbnb-base', 'plugin:prettier/recommended'], + Plugins: ['prettier'], + rules: { + 'no-console': 'off', + 'no-unused-vars': 'warn', + 'no-underscore-dangle': 'off', + 'import/no-unresolved': 'off', + 'import/extensions': 'off', + 'prettier/prettier': 'error', + }, + env: { + node: true, + jest: true, + }, +}; diff --git a/tapiro-api-internal/.prettierignore b/tapiro-api-internal/.prettierignore new file mode 100644 index 0000000..42c8ea7 --- /dev/null +++ b/tapiro-api-internal/.prettierignore @@ -0,0 +1,17 @@ +# Dependencies +node_modules +coverage + +# Build output +dist +build + +# Logs +logs +*.log + +# Environment variables +.env* + +# Cache +.cache \ No newline at end of file diff --git a/tapiro-api-internal/.prettierrc.js b/tapiro-api-internal/.prettierrc.js new file mode 100644 index 0000000..66388d0 --- /dev/null +++ b/tapiro-api-internal/.prettierrc.js @@ -0,0 +1,8 @@ +module.exports = { + semi: true, + trailingComma: 'all', + singleQuote: true, + printWidth: 100, + tabWidth: 2, + endOfLine: 'lf', +}; diff --git a/tapiro-api-internal/README.md b/tapiro-api-internal/README.md new file mode 100644 index 0000000..a1794e7 --- /dev/null +++ b/tapiro-api-internal/README.md @@ -0,0 +1,21 @@ +# Swagger generated server + +## Overview + +This server was generated by the [swagger-codegen](https://github.com/swagger-api/swagger-codegen) project. By using the [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub. + +### Running the server + +To run the server, run: + +``` +npm start +``` + +To view the Swagger UI interface: + +``` +open http://localhost:8080/docs +``` + +This project leverages the mega-awesome [swagger-tools](https://github.com/apigee-127/swagger-tools) middleware which does most all the work. diff --git a/api-service/__tests__/config/setup.js b/tapiro-api-internal/__tests__/config/setup.js similarity index 100% rename from api-service/__tests__/config/setup.js rename to tapiro-api-internal/__tests__/config/setup.js diff --git a/api-service/__tests__/config/setupAfterEnv.js b/tapiro-api-internal/__tests__/config/setupAfterEnv.js similarity index 100% rename from api-service/__tests__/config/setupAfterEnv.js rename to tapiro-api-internal/__tests__/config/setupAfterEnv.js diff --git a/api-service/__tests__/unit/helperUtil.test.js b/tapiro-api-internal/__tests__/unit/helperUtil.test.js similarity index 100% rename from api-service/__tests__/unit/helperUtil.test.js rename to tapiro-api-internal/__tests__/unit/helperUtil.test.js diff --git a/api-service/__tests__/unit/mongoUtil.test.js b/tapiro-api-internal/__tests__/unit/mongoUtil.test.js similarity index 100% rename from api-service/__tests__/unit/mongoUtil.test.js rename to tapiro-api-internal/__tests__/unit/mongoUtil.test.js diff --git a/api-service/__tests__/unit/redisutil.test.js b/tapiro-api-internal/__tests__/unit/redisutil.test.js similarity index 100% rename from api-service/__tests__/unit/redisutil.test.js rename to tapiro-api-internal/__tests__/unit/redisutil.test.js diff --git a/api-service/api/openapi.yaml b/tapiro-api-internal/api/openapi.yaml similarity index 96% rename from api-service/api/openapi.yaml rename to tapiro-api-internal/api/openapi.yaml index 93ebe0c..3ceb8b7 100644 --- a/api-service/api/openapi.yaml +++ b/tapiro-api-internal/api/openapi.yaml @@ -11,12 +11,8 @@ tags: description: User profile and data management operations - name: Store Management description: Store profile, API key, and integration management - - name: Data Collection - description: Operations for submitting user data for targeted advertising - name: Preference Management description: Operations for managing user preferences and opt-in/out settings - - name: Data Retrival - description: Operations for retrieving user preferences and data - name: Health description: Health and uptime monitoring - name: Admin @@ -233,62 +229,6 @@ paths: - oauth2: [store:write] x-swagger-router-controller: StoreProfile - /users/data: - post: - tags: [Data Collection] - summary: Submit user data - description: Submit purchase/search history for a user (authenticated via API key) - operationId: submitUserData - security: - - apiKey: [] - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/UserData" - required: true - responses: - "202": - description: Data accepted for processing - "401": - $ref: "#/components/responses/UnauthorizedError" - "400": - $ref: "#/components/responses/BadRequestError" - "500": - $ref: "#/components/responses/InternalServerError" - x-swagger-router-controller: StoreOperations - - /users/{userId}/preferences: - get: - tags: [Data Retrival] - summary: Get user preferences - description: Retrieve preferences for targeted advertising - operationId: getUserPreferences - parameters: - - name: userId - in: path - required: true - schema: - type: string - security: - - apiKey: [] - responses: - "200": - description: UserPreferences retrieved - content: - application/json: - schema: - $ref: "#/components/schemas/UserPreferences" - "403": - $ref: "#/components/responses/ForbiddenError" - "404": - $ref: "#/components/responses/NotFoundError" - "401": - $ref: "#/components/responses/UnauthorizedError" - "500": - $ref: "#/components/responses/InternalServerError" - x-swagger-router-controller: StoreOperations - /users/preferences: get: tags: [Preference Management] @@ -975,7 +915,14 @@ components: type: object required: - dataSharingConsent + - username # Add username to required fields properties: + username: # Add username property + type: string + minLength: 3 + maxLength: 15 + pattern: "^[a-zA-Z0-9_-]+$" + description: User's unique username, chosen during registration. preferences: type: array items: @@ -1522,7 +1469,9 @@ components: registrationComplete: type: boolean description: Whether registration process is complete - description: User metadata from Auth0 + nickname: + type: string + description: User or store display name TaxonomyAttribute: type: object @@ -1867,10 +1816,6 @@ components: message: Internal server error securitySchemes: - apiKey: - type: apiKey - in: header - name: X-API-Key oauth2: type: oauth2 flows: diff --git a/api-service/controllers/Authentication.js b/tapiro-api-internal/controllers/Authentication.js similarity index 100% rename from api-service/controllers/Authentication.js rename to tapiro-api-internal/controllers/Authentication.js diff --git a/api-service/controllers/Health.js b/tapiro-api-internal/controllers/Health.js similarity index 100% rename from api-service/controllers/Health.js rename to tapiro-api-internal/controllers/Health.js diff --git a/api-service/controllers/PreferenceManagement.js b/tapiro-api-internal/controllers/PreferenceManagement.js similarity index 100% rename from api-service/controllers/PreferenceManagement.js rename to tapiro-api-internal/controllers/PreferenceManagement.js diff --git a/api-service/controllers/StoreManagement.js b/tapiro-api-internal/controllers/StoreManagement.js similarity index 100% rename from api-service/controllers/StoreManagement.js rename to tapiro-api-internal/controllers/StoreManagement.js diff --git a/api-service/controllers/StoreProfile.js b/tapiro-api-internal/controllers/StoreProfile.js similarity index 92% rename from api-service/controllers/StoreProfile.js rename to tapiro-api-internal/controllers/StoreProfile.js index 83c2ad0..a45b727 100644 --- a/api-service/controllers/StoreProfile.js +++ b/tapiro-api-internal/controllers/StoreProfile.js @@ -31,8 +31,8 @@ module.exports.deleteStoreProfile = function deleteStoreProfile(req, res, next) }); }; -module.exports.lookupStores = function lookupStores(req, res, next, ids) { - StoreProfile.lookupStores(req, ids) +module.exports.searchStores = function searchStores(req, res, next, ids) { + StoreProfile.searchStores(req, ids) .then((response) => { utils.writeJson(res, response); }) diff --git a/api-service/controllers/Taxonomy.js b/tapiro-api-internal/controllers/Taxonomy.js similarity index 100% rename from api-service/controllers/Taxonomy.js rename to tapiro-api-internal/controllers/Taxonomy.js diff --git a/api-service/controllers/UserProfile.js b/tapiro-api-internal/controllers/UserProfile.js similarity index 100% rename from api-service/controllers/UserProfile.js rename to tapiro-api-internal/controllers/UserProfile.js diff --git a/api-service/dockerfile b/tapiro-api-internal/dockerfile similarity index 100% rename from api-service/dockerfile rename to tapiro-api-internal/dockerfile diff --git a/api-service/index.js b/tapiro-api-internal/index.js similarity index 91% rename from api-service/index.js rename to tapiro-api-internal/index.js index 425c709..71def8b 100644 --- a/api-service/index.js +++ b/tapiro-api-internal/index.js @@ -5,7 +5,6 @@ const express = require('express'); // Import express const oas3Tools = require('oas3-tools'); const cors = require('cors'); const { auth, checkJwtAndScope } = require('./middleware/authMiddleware'); -const { validateApiKey } = require('./middleware/apiKeyMiddleware'); const { connectDB } = require('./utils/mongoUtil'); const { connectRedis } = require('./utils/redisUtil'); @@ -15,7 +14,7 @@ const serverPort = process.env.PORT; const corsOptions = { origin: [process.env.FRONTEND_URL], methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], - allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key'], + allowedHeaders: ['Content-Type', 'Authorization'], credentials: true, maxAge: 86400, }; @@ -30,7 +29,6 @@ const options = { validateSecurity: { handlers: { oauth2: checkJwtAndScope, - apiKey: validateApiKey, }, }, }, diff --git a/tapiro-api-internal/jest.config.js b/tapiro-api-internal/jest.config.js new file mode 100644 index 0000000..97fecbe --- /dev/null +++ b/tapiro-api-internal/jest.config.js @@ -0,0 +1,24 @@ +module.exports = { + testEnvironment: 'node', + verbose: true, + coverageDirectory: 'coverage', + collectCoverageFrom: [ + 'utils/**/*.js', + 'middleware/**/*.js', + 'service/**/*.js', + 'controllers/**/*.js', + '!**/*.test.js', + '!**/node_modules/**', + ], + coverageThreshold: { + global: { + branches: 80, + functions: 80, + lines: 80, + statements: 80, + }, + }, + testMatch: ['**/__tests__/**/*.test.js'], + setupFiles: ['/__tests__/config/setup.js'], + setupFilesAfterEnv: ['/__tests__/config/setupAfterEnv.js'], +}; diff --git a/api-service/middleware/authMiddleware.js b/tapiro-api-internal/middleware/authMiddleware.js similarity index 100% rename from api-service/middleware/authMiddleware.js rename to tapiro-api-internal/middleware/authMiddleware.js diff --git a/tapiro-api-internal/package-lock.json b/tapiro-api-internal/package-lock.json new file mode 100644 index 0000000..78ba9f1 --- /dev/null +++ b/tapiro-api-internal/package-lock.json @@ -0,0 +1,9272 @@ +{ + "name": "tapiro-api", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tapiro-api", + "version": "1.0.0", + "license": "Unlicense", + "dependencies": { + "axios": "^1.7.9", + "axios-retry": "^4.5.0", + "connect": "^3.2.0", + "cors": "^2.8.5", + "express": "^4.21.2", + "express-oauth2-jwt-bearer": "^1.6.0", + "express-openid-connect": "^2.17.1", + "js-yaml": "^3.3.0", + "jsonwebtoken": "^9.0.2", + "mongodb": "^6.13.0", + "oas3-tools": "^2.2.3", + "redis": "^4.7.0" + }, + "devDependencies": { + "dotenv": "^16.4.7", + "eslint": "^8.57.1", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-prettier": "^5.2.3", + "jest": "^29.7.0", + "nock": "^14.0.1", + "prettier": "^3.5.0", + "supertest": "^7.0.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", + "integrity": "sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==", + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/@apidevtools/json-schema-ref-parser/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz", + "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.7.tgz", + "integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.5", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.7", + "@babel/parser": "^7.26.7", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.26.7", + "@babel/types": "^7.26.7", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/generator": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz", + "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.5", + "@babel/types": "^7.26.5", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", + "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz", + "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", + "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz", + "integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.5", + "@babel/parser": "^7.26.7", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.7", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/types": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", + "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT" + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", + "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@mswjs/interceptors": { + "version": "0.37.6", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.37.6.tgz", + "integrity": "sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.0.tgz", + "integrity": "sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/client/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", + "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", + "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", + "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/multer": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.12.tgz", + "integrity": "sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==", + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/node": { + "version": "22.13.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz", + "integrity": "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/qs": { + "version": "6.9.18", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", + "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios-retry": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-4.5.0.tgz", + "integrity": "sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==", + "license": "Apache-2.0", + "dependencies": { + "is-retry-allowed": "^2.2.0" + }, + "peerDependencies": { + "axios": "0.x || 1.x" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/bson": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.2.tgz", + "integrity": "sha512-5afhLTjqDSA3akH56E+/2J6kTDuSIlBxyXPdQslj9hcIgOUE378xdOfZvC/9q3LifJNI6KR/juZ+d0NRNYBwXg==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==", + "dependencies": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001697", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001697.tgz", + "integrity": "sha512-GwNPlWJin8E+d7Gxq96jxM6w0w+VFeyyXRsjU58emtkYqnbwHqXm5uT2uCmO0RQE9htWknOP4xtBlLmM/gWxvQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true, + "license": "MIT" + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha512-FDvbtnq7dzlPz0wyYlOExifDEZcu8h+rErEXgfxqmLfRfC/kJidEFh4+effJRO3P0xmfqyPbSMG0LveNRfTKVg==", + "dependencies": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.93", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.93.tgz", + "integrity": "sha512-M+29jTcfNNoR9NV7la4SwUqzWAxEwnc7ThA5e1m6LRSotmpfpCpLcIfgtSCVL+MllNLgAyM/5ru86iMRemPzDQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.9", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", + "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.0", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.18" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "license": "MIT", + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz", + "integrity": "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "build/bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz", + "integrity": "sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/eslint/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint/node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-oauth2-jwt-bearer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/express-oauth2-jwt-bearer/-/express-oauth2-jwt-bearer-1.6.0.tgz", + "integrity": "sha512-HXnez7vocYlOqlfF3ozPcf/WE3zxT7zfUNfeg5FHJnvNwhBYlNXiPOvuCtBalis8xcigvwtInzEKhBuH87+9ug==", + "license": "MIT", + "dependencies": { + "jose": "^4.13.1" + }, + "engines": { + "node": "^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0 || ^20.2.0" + } + }, + "node_modules/express-oauth2-jwt-bearer/node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/express-openapi-validator": { + "version": "4.13.8", + "resolved": "https://registry.npmjs.org/express-openapi-validator/-/express-openapi-validator-4.13.8.tgz", + "integrity": "sha512-89/sdkq+BKBuIyykaMl/vR9grFc3WFUPTjFo0THHbu+5g+q8rA7fKeoMfz+h84yOQIBcztmJ5ZJdk5uhEls31A==", + "license": "MIT", + "dependencies": { + "@types/multer": "^1.4.7", + "ajv": "^6.12.6", + "content-type": "^1.0.4", + "json-schema-ref-parser": "^9.0.9", + "lodash.clonedeep": "^4.5.0", + "lodash.get": "^4.4.2", + "lodash.uniq": "^4.5.0", + "lodash.zipobject": "^4.1.3", + "media-typer": "^1.1.0", + "multer": "^1.4.5-lts.1", + "ono": "^7.1.3", + "path-to-regexp": "^6.2.0" + } + }, + "node_modules/express-openapi-validator/node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/express-openapi-validator/node_modules/multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/express-openapi-validator/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "license": "MIT" + }, + "node_modules/express-openapi-validator/node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/express-openid-connect": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/express-openid-connect/-/express-openid-connect-2.17.1.tgz", + "integrity": "sha512-5pVK6PNV09x6UN29R9Mer0XF3hwQq2HxiFsjZvLuIQ9ezeTUGbqrefzBOpzciz1S/1WWVaVPDIcj4EBpD8WB3Q==", + "license": "MIT", + "dependencies": { + "base64url": "^3.0.1", + "clone": "^2.1.2", + "cookie": "^0.5.0", + "debug": "^4.3.4", + "futoin-hkdf": "^1.5.1", + "http-errors": "^1.8.1", + "joi": "^17.7.0", + "jose": "^2.0.6", + "on-headers": "^1.0.2", + "openid-client": "^4.9.1", + "url-join": "^4.0.1" + }, + "engines": { + "node": "^10.19.0 || >=12.0.0 < 13 || >=13.7.0 < 14 || >= 14.2.0" + }, + "peerDependencies": { + "express": ">= 4.17.0" + } + }, + "node_modules/express-openid-connect/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-openid-connect/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/express-openid-connect/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-openid-connect/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-openid-connect/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/express-openid-connect/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", + "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.4.tgz", + "integrity": "sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.2.tgz", + "integrity": "sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dezalgo": "^1.0.4", + "hexoid": "^2.0.0", + "once": "^1.4.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/futoin-hkdf": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/futoin-hkdf/-/futoin-hkdf-1.5.3.tgz", + "integrity": "sha512-SewY5KdMpaoCeh7jachEWFsh1nNlaDjNHZXWqL5IGwtpEYHTgkr2+AMCgNwKWkcc0wpSYrZfR7he4WdmHFtDxQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globals/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hexoid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-2.0.0.tgz", + "integrity": "sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "license": "BSD-2-Clause" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-retry-allowed": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", + "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/jose": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.7.tgz", + "integrity": "sha512-5hFWIigKqC+e/lRyQhfnirrAqUdIPMB7SJRqflJaO29dW7q5DFvH1XCSTmv6PQ6pb++0k6MJlLRoS0Wv4s38Wg==", + "license": "MIT", + "dependencies": { + "@panva/asn1.js": "^1.0.0" + }, + "engines": { + "node": ">=10.13.0 < 13 || >=13.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-ref-parser": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", + "integrity": "sha512-qcP2lmGy+JUoQJ4DOQeLaZDqH9qSkeGCK3suKWxJXS82dg728Mn3j97azDMaOUmJAN4uCq91LdPx4K7E8F1a7Q==", + "deprecated": "Please switch to @apidevtools/json-schema-ref-parser", + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "9.0.9" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, + "node_modules/lodash.zipobject": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lodash.zipobject/-/lodash.zipobject-4.1.3.tgz", + "integrity": "sha512-A9SzX4hMKWS25MyalwcOnNoplyHbkNVsjidhTp8ru0Sj23wY9GWBKS8gAIGDSAqeWjIjvE4KBEl24XXAs+v4wQ==", + "license": "MIT" + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mongodb": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.13.0.tgz", + "integrity": "sha512-KeESYR5TEaFxOuwRqkOm3XOsMqCSkdeDMjaW5u2nuKfX7rqaofp7JQGoi7sVqQcNJTKuveNbzZtWMstb8ABP6Q==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.1", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/multer": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz", + "integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==", + "deprecated": "Multer 1.x is affected by CVE-2022-24434. This is fixed in v1.4.4-lts.1 which drops support for versions of Node.js before 6. Please upgrade to at least Node.js 6 and version 1.4.4-lts.1 of Multer. If you need support for older versions of Node.js, we are open to accepting patches that would fix the CVE on the main 1.x release line, whilst maintaining compatibility with Node.js 0.10.", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^0.2.11", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "on-finished": "^2.3.0", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nock": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.1.tgz", + "integrity": "sha512-IJN4O9pturuRdn60NjQ7YkFt6Rwei7ZKaOwb1tvUIIqTgeD0SDDAX3vrqZD4wcXczeEy/AsUXxpGpP/yHqV7xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mswjs/interceptors": "^0.37.3", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">=18.20.0 <20 || >=20.12.1" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/oas3-tools": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/oas3-tools/-/oas3-tools-2.2.3.tgz", + "integrity": "sha512-3eUllm9ZfPI5OjE5zSqsexYY0CbFtgt0/z4iqpZCg3DBiN+IHdLdM+WqQrTmYR4hTydbebe2WEIIIRqoz5NRKQ==", + "license": "MIT", + "dependencies": { + "async": "^2.6.3", + "body-parser": "1.19.0", + "cookie-parser": "^1.4.4", + "debug": "^4.1.1", + "express": "^4.17.1", + "express-openapi-validator": "^4.4.3", + "js-yaml": "^3.13.1", + "lodash": "^4.17.15", + "morgan": "^1.9.1", + "multer": "^1.4.2", + "parseurl": "^1.3.3", + "path-to-regexp": "^3.2.0", + "qs": "^6.9.1", + "serve-static": "^1.14.1" + } + }, + "node_modules/oas3-tools/node_modules/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/oas3-tools/node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/oas3-tools/node_modules/body-parser/node_modules/qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/oas3-tools/node_modules/bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/oas3-tools/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/oas3-tools/node_modules/debug/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/oas3-tools/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/oas3-tools/node_modules/http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/oas3-tools/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" + }, + "node_modules/oas3-tools/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/oas3-tools/node_modules/path-to-regexp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "license": "MIT" + }, + "node_modules/oas3-tools/node_modules/raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/oas3-tools/node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "license": "ISC" + }, + "node_modules/oas3-tools/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/oas3-tools/node_modules/toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/oidc-token-hash": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", + "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-9jnfVriq7uJM4o5ganUY54ntUm+5EK21EGaQ5NWnkWg3zz5ywbbonlBguRcnmF1/HDiIe3zxNxXcO1YPBmPcQQ==", + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "7.1.3" + } + }, + "node_modules/openid-client": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-4.9.1.tgz", + "integrity": "sha512-DYUF07AHjI3QDKqKbn2F7RqozT4hyi4JvmpodLrq0HHoNP7t/AjeG/uqiBK1/N2PZSAQEThVjDLHSmJN4iqu/w==", + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.1.0", + "got": "^11.8.0", + "jose": "^2.0.5", + "lru-cache": "^6.0.0", + "make-error": "^1.3.6", + "object-hash": "^2.0.1", + "oidc-token-hash": "^5.0.1" + }, + "engines": { + "node": "^10.19.0 || >=12.0.0 < 13 || >=13.7.0 < 14 || >= 14.2.0" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/openid-client/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, + "license": "MIT" + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.0.tgz", + "integrity": "sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/redis": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.0.tgz", + "integrity": "sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==", + "license": "MIT", + "workspaces": [ + "./packages/*" + ], + "dependencies": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.6.0", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.7", + "@redis/search": "1.2.0", + "@redis/time-series": "1.1.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "license": "MIT" + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "license": "MIT" + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/superagent": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", + "integrity": "sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^3.5.1", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/superagent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/supertest": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.0.0.tgz", + "integrity": "sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "methods": "^1.1.2", + "superagent": "^9.0.1" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/synckit": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", + "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz", + "integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", + "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/api-service/package.json b/tapiro-api-internal/package.json similarity index 100% rename from api-service/package.json rename to tapiro-api-internal/package.json diff --git a/api-service/service/AuthenticationService.js b/tapiro-api-internal/service/AuthenticationService.js similarity index 50% rename from api-service/service/AuthenticationService.js rename to tapiro-api-internal/service/AuthenticationService.js index 9e9ebf8..d05dd23 100644 --- a/api-service/service/AuthenticationService.js +++ b/tapiro-api-internal/service/AuthenticationService.js @@ -1,8 +1,8 @@ const { getDB } = require('../utils/mongoUtil'); -const { setCache} = require('../utils/redisUtil'); +const { setCache } = require('../utils/redisUtil'); const { checkExistingRegistration } = require('../utils/helperUtil'); const { respondWithCode } = require('../utils/writer'); -const { assignUserRole, linkAccounts, updateUserMetadata, getUserMetadata } = require('../utils/auth0Util'); +const { assignUserRole, updateUserMetadata, getUserMetadata } = require('../utils/auth0Util'); // Removed linkAccounts const { getUserData } = require('../utils/authUtil'); const { CACHE_TTL, CACHE_KEYS } = require('../utils/cacheConfig'); @@ -13,21 +13,19 @@ const { CACHE_TTL, CACHE_KEYS } = require('../utils/cacheConfig'); exports.registerUser = async function (req, body) { try { const db = getDB(); - // Destructure new demographic fields AND allowInference const { + username, preferences, dataSharingConsent, - allowInference, // <-- Add allowInference + allowInference, gender, incomeBracket, country, age, } = body; - // Get user data - use req.user if available (from middleware) or fetch it const userData = req.user || (await getUserData(req.headers.authorization?.split(' ')[1])); - // Check if already registered as a user or store const registration = await checkExistingRegistration(userData.sub); if (registration.exists) { return respondWithCode(409, { @@ -36,45 +34,19 @@ exports.registerUser = async function (req, body) { }); } - // Check if email already exists in our database - const existingUserByEmail = await db.collection('users').findOne({ - email: userData.email, - }); - - // Handle account linking if the email exists but with a different auth0Id - if (existingUserByEmail && existingUserByEmail.auth0Id !== userData.sub) { - try { - // Link the accounts in Auth0 - await linkAccounts(existingUserByEmail.auth0Id, userData.sub); - - // Cache the linked user data - await setCache( - `${CACHE_KEYS.USER_DATA}${userData.sub}`, - JSON.stringify(existingUserByEmail), - { - EX: CACHE_TTL.USER_DATA, - }, - ); - - // Return the existing user - return respondWithCode(200, { - ...existingUserByEmail, - message: 'Account linked successfully', - accountLinked: true, - }); - } catch (linkError) { - console.error('Account linking failed:', linkError); - return respondWithCode(400, { - code: 400, - message: 'Failed to link accounts. Please contact support.', - details: linkError.message, - }); - } + // Check if email already exists in users or stores collection + const existingEmail = + (await db.collection('users').findOne({ email: userData.email })) || + (await db.collection('stores').findOne({ email: userData.email })); + if (existingEmail) { + return respondWithCode(409, { + code: 409, + message: 'Email is already registered with another account.', + }); } - // Check if username already exists const existingUserByUsername = await db.collection('users').findOne({ - username: userData.nickname, + username: username, }); if (existingUserByUsername) { @@ -84,52 +56,33 @@ exports.registerUser = async function (req, body) { }); } - // Add user role assignment - try { - await assignUserRole(userData.sub, 'user'); - } catch (error) { - console.error('Role assignment failed:', error); - return respondWithCode(500, { - code: 500, - message: 'Failed to assign role', - }); - } + await assignUserRole(userData.sub, 'user'); - // Create user in database const user = { auth0Id: userData.sub, - username: userData.username || userData.nickname || userData.sub, + username: username || null, email: userData.email, phone: userData.phone_number || null, demographicData: { - // User provided (initialize as null unless provided in registration body) gender: gender || null, incomeBracket: incomeBracket || null, country: country || null, age: age || null, - hasKids: null, // NEW user-provided, init null - relationshipStatus: null, // NEW user-provided, init null - employmentStatus: null, // NEW user-provided, init null - educationLevel: null, // NEW user-provided, init null - // Inferred (initialize as null) + hasKids: null, + relationshipStatus: null, + employmentStatus: null, + educationLevel: null, inferredHasKids: null, - // REMOVED hasKidsIsVerified inferredRelationshipStatus: null, - // REMOVED relationshipStatusIsVerified inferredEmploymentStatus: null, - // REMOVED employmentStatusIsVerified inferredEducationLevel: null, - // REMOVED educationLevelIsVerified - // REMOVED inferredAgeBracket - // REMOVED ageBracketIsVerified inferredGender: null, - // REMOVED genderIsVerified }, preferences: preferences || [], privacySettings: { dataSharingConsent, anonymizeData: false, - allowInference: allowInference !== undefined ? allowInference : true, // <-- Set allowInference, default true + allowInference: allowInference !== undefined ? allowInference : true, optInStores: [], optOutStores: [], }, @@ -141,17 +94,14 @@ exports.registerUser = async function (req, body) { const result = await db.collection('users').insertOne(user); - // Cache the newly created user data const userWithId = { ...user, _id: result.insertedId }; await setCache(`${CACHE_KEYS.USER_DATA}${userData.sub}`, JSON.stringify(userWithId), { EX: CACHE_TTL.USER_DATA, }); - // Also cache user preferences - // Note: Demographic data is NOT typically included in the preferences cache const cachePreferences = { userId: user._id.toString(), - preferences: user.preferences || [], // Fixed: consistent naming + preferences: user.preferences || [], updatedAt: user.updatedAt || new Date(), }; @@ -159,10 +109,10 @@ exports.registerUser = async function (req, body) { EX: CACHE_TTL.USER_DATA, }); - // Update user metadata await updateUserMetadata(userData.sub, { registrationType: 'user', - registrationComplete: true + registrationComplete: true, + nickname: username, }); return respondWithCode(201, { ...user, userId: result.insertedId }); @@ -181,10 +131,8 @@ exports.registerStore = async function (req, body) { const db = getDB(); const { name, address, webhooks } = body; - // Get user data - use req.user if available (from middleware) or fetch it const userData = req.user || (await getUserData(req.headers.authorization?.split(' ')[1])); - // Check if already registered const registration = await checkExistingRegistration(userData.sub); if (registration.exists) { return respondWithCode(409, { @@ -193,54 +141,19 @@ exports.registerStore = async function (req, body) { }); } - // Check if a store with the same email exists but with different auth0Id - const existingStoreByEmail = await db.collection('stores').findOne({ - email: userData.email, - }); - - // Handle account linking if the email exists but with a different auth0Id - if (existingStoreByEmail && existingStoreByEmail.auth0Id !== userData.sub) { - try { - // Link the accounts in Auth0 - await linkAccounts(existingStoreByEmail.auth0Id, userData.sub); - - // Cache the linked store data - await setCache( - `${CACHE_KEYS.STORE_DATA}${userData.sub}`, - JSON.stringify(existingStoreByEmail), - { - EX: CACHE_TTL.STORE_DATA, - }, - ); - - // Return the existing store - return respondWithCode(200, { - ...existingStoreByEmail, - message: 'Account linked successfully', - accountLinked: true, - }); - } catch (linkError) { - console.error('Account linking failed:', linkError); - return respondWithCode(400, { - code: 400, - message: 'Failed to link accounts. Please contact support.', - details: linkError.message, - }); - } - } - - // Assign store role - try { - await assignUserRole(userData.sub, 'store'); - } catch (error) { - console.error('Role assignment failed:', error); - return respondWithCode(500, { - code: 500, - message: 'Failed to assign role', + // Check if email already exists in users or stores collection + const existingEmail = + (await db.collection('users').findOne({ email: userData.email })) || + (await db.collection('stores').findOne({ email: userData.email })); + if (existingEmail) { + return respondWithCode(409, { + code: 409, + message: 'Email is already registered with another account.', }); } - // Create store in database + await assignUserRole(userData.sub, 'store'); + const store = { auth0Id: userData.sub, name, @@ -254,16 +167,15 @@ exports.registerStore = async function (req, body) { const result = await db.collection('stores').insertOne(store); - // Cache the newly created store data const storeWithId = { ...store, _id: result.insertedId }; await setCache(`${CACHE_KEYS.STORE_DATA}${userData.sub}`, JSON.stringify(storeWithId), { EX: CACHE_TTL.STORE_DATA, }); - // Update store metadata await updateUserMetadata(userData.sub, { registrationType: 'store', - registrationComplete: true + registrationComplete: true, + nickname: name, }); return respondWithCode(201, { ...store, storeId: result.insertedId }); @@ -281,7 +193,7 @@ exports.getUserMetadata = async function (req) { try { // Get user data from middleware or fetch it const userData = req.user || (await getUserData(req.headers.authorization?.split(' ')[1])); - + // Get user metadata from Auth0 using Management API const metadata = await getUserMetadata(userData.sub); diff --git a/api-service/service/HealthService.js b/tapiro-api-internal/service/HealthService.js similarity index 100% rename from api-service/service/HealthService.js rename to tapiro-api-internal/service/HealthService.js diff --git a/api-service/service/PreferenceManagementService.js b/tapiro-api-internal/service/PreferenceManagementService.js similarity index 99% rename from api-service/service/PreferenceManagementService.js rename to tapiro-api-internal/service/PreferenceManagementService.js index ed4998d..c58ba32 100644 --- a/api-service/service/PreferenceManagementService.js +++ b/tapiro-api-internal/service/PreferenceManagementService.js @@ -6,7 +6,7 @@ const { CACHE_TTL, CACHE_KEYS } = require('../utils/cacheConfig'); const { ObjectId } = require('mongodb'); // Removed AIService require as it's no longer used here // const AIService = require('../clients/AIService'); -const TaxonomyService = require('../service/TaxonomyService'); // Import TaxonomyService +const TaxonomyService = require('./TaxonomyService'); // Import TaxonomyService exports.getUserOwnPreferences = async function (req) { try { diff --git a/api-service/service/StoreManagementService.js b/tapiro-api-internal/service/StoreManagementService.js similarity index 100% rename from api-service/service/StoreManagementService.js rename to tapiro-api-internal/service/StoreManagementService.js diff --git a/api-service/service/StoreProfileService.js b/tapiro-api-internal/service/StoreProfileService.js similarity index 73% rename from api-service/service/StoreProfileService.js rename to tapiro-api-internal/service/StoreProfileService.js index 1cbdf3f..50a5df3 100644 --- a/api-service/service/StoreProfileService.js +++ b/tapiro-api-internal/service/StoreProfileService.js @@ -3,7 +3,7 @@ const { setCache, getCache, invalidateCache } = require('../utils/redisUtil'); const { respondWithCode } = require('../utils/writer'); const { getUserData } = require('../utils/authUtil'); const { CACHE_TTL, CACHE_KEYS } = require('../utils/cacheConfig'); -const { deleteAuth0User } = require('../utils/auth0Util'); +const { deleteAuth0User, updateUserMetadata } = require('../utils/auth0Util'); // <-- Import updateUserMetadata const { ObjectId } = require('mongodb'); // Import ObjectId /** @@ -52,20 +52,40 @@ exports.updateStoreProfile = async function (req, body) { // Get user data - use req.user if available (from middleware) or fetch it const userData = req.user || (await getUserData(req.headers.authorization?.split(' ')[1])); + const auth0UserId = userData.sub; // Get Auth0 user ID for metadata update // Update store const updateData = { updatedAt: new Date(), }; - if (body.name !== undefined) updateData.name = body.name; + let nameChanged = false; // Flag to check if name was updated + + if (body.name !== undefined) { + updateData.name = body.name; + // We need the current store to compare if the name actually changed + // This will be checked after fetching the store for the update operation + } if (body.address !== undefined) updateData.address = body.address; if (body.webhooks !== undefined) updateData.webhooks = body.webhooks; + // Fetch the current store profile to check if name actually changed + const currentStore = await db.collection('stores').findOne({ auth0Id: auth0UserId }); + if (!currentStore) { + return respondWithCode(404, { + code: 404, + message: 'Store not found before update.', + }); + } + + if (body.name !== undefined && body.name !== currentStore.name) { + nameChanged = true; + } + const result = await db .collection('stores') .findOneAndUpdate( - { auth0Id: userData.sub }, + { auth0Id: auth0UserId }, { $set: updateData }, { returnDocument: 'after' }, ); @@ -77,8 +97,19 @@ exports.updateStoreProfile = async function (req, body) { }); } + // If name changed, update Auth0 metadata + if (nameChanged && body.name) { + try { + await updateUserMetadata(auth0UserId, { nickname: body.name }); + console.log(`Auth0 nickname updated for store ${auth0UserId} to ${body.name}`); + } catch (auth0Error) { + // Log error but don't fail the whole operation, as local DB update succeeded + console.error(`Failed to update Auth0 nickname for store ${auth0UserId}:`, auth0Error); + } + } + // Update cache with standardized key and TTL - const cacheKey = `${CACHE_KEYS.STORE_DATA}${userData.sub}`; + const cacheKey = `${CACHE_KEYS.STORE_DATA}${auth0UserId}`; await setCache(cacheKey, JSON.stringify(result), { EX: CACHE_TTL.STORE_DATA }); return respondWithCode(200, result); } catch (error) { @@ -139,29 +170,29 @@ exports.lookupStores = async function (req, ids) { const db = getDB(); - const stores = await db.collection('stores') - .find({ _id: { $in: storeIds.map(id => new ObjectId(id)) } }) // Use ObjectId for lookup if IDs are ObjectIds + const stores = await db + .collection('stores') + .find({ _id: { $in: storeIds.map((id) => new ObjectId(id)) } }) // Use ObjectId for lookup if IDs are ObjectIds // If store IDs are stored as strings in optIn/optOut lists, use: // .find({ _id: { $in: storeIds } }) .project({ _id: 1, name: 1 }) // Project only ID and name .toArray(); // Format the response to match StoreBasicInfo schema - const formattedStores = stores.map(store => ({ + const formattedStores = stores.map((store) => ({ storeId: store._id.toString(), // Convert ObjectId back to string - name: store.name + name: store.name, })); // Caching could be considered if lookups for the same set of IDs are common, // but the cache key generation might be complex. return respondWithCode(200, formattedStores); - } catch (error) { console.error('Lookup stores failed:', error); // Handle potential ObjectId format errors if validation is strict if (error.message.includes('Argument passed in must be a single String')) { - return respondWithCode(400, { code: 400, message: 'Invalid store ID format provided.' }); + return respondWithCode(400, { code: 400, message: 'Invalid store ID format provided.' }); } return respondWithCode(500, { code: 500, message: 'Internal server error' }); } @@ -173,8 +204,12 @@ exports.lookupStores = async function (req, ids) { */ exports.searchStores = async function (req, query, limit = 10) { try { - if (!query || query.length < 2) { // Basic validation - return respondWithCode(400, { code: 400, message: 'Search query must be at least 2 characters long.' }); + if (!query || query.length < 2) { + // Basic validation + return respondWithCode(400, { + code: 400, + message: 'Search query must be at least 2 characters long.', + }); } const db = getDB(); @@ -184,22 +219,25 @@ exports.searchStores = async function (req, query, limit = 10) { // db.collection('stores').createIndex({ name: "text" }); // Then use: { $text: { $search: query } } instead of regex for better performance - const stores = await db.collection('stores') + const stores = await db + .collection('stores') .find({ name: regex }) // Using regex for simplicity here .limit(parseInt(limit)) // Ensure limit is an integer .project({ _id: 1, name: 1 }) // Project only ID and name .toArray(); // Format the response to match StoreBasicInfo schema - const formattedStores = stores.map(store => ({ + const formattedStores = stores.map((store) => ({ storeId: store._id.toString(), // Convert ObjectId back to string - name: store.name + name: store.name, })); return respondWithCode(200, formattedStores); - } catch (error) { console.error('Search stores failed:', error); - return respondWithCode(500, { code: 500, message: 'Internal server error during store search' }); + return respondWithCode(500, { + code: 500, + message: 'Internal server error during store search', + }); } }; diff --git a/api-service/service/TaxonomyService.js b/tapiro-api-internal/service/TaxonomyService.js similarity index 100% rename from api-service/service/TaxonomyService.js rename to tapiro-api-internal/service/TaxonomyService.js diff --git a/api-service/service/UserProfileService.js b/tapiro-api-internal/service/UserProfileService.js similarity index 69% rename from api-service/service/UserProfileService.js rename to tapiro-api-internal/service/UserProfileService.js index 940d3cb..dbd8122 100644 --- a/api-service/service/UserProfileService.js +++ b/tapiro-api-internal/service/UserProfileService.js @@ -3,8 +3,8 @@ const { setCache, getCache, invalidateCache } = require('../utils/redisUtil'); const { respondWithCode } = require('../utils/writer'); const { getUserData } = require('../utils/authUtil'); const { CACHE_TTL, CACHE_KEYS } = require('../utils/cacheConfig'); -const {updateUserPhone, updateAuth0Username, deleteAuth0User } = require('../utils/auth0Util'); -const { ObjectId } = require('mongodb'); // Ensure ObjectId is imported +const { updateUserPhone, deleteAuth0User, updateUserMetadata } = require('../utils/auth0Util'); +const { ObjectId } = require('mongodb'); /** * Get User Profile @@ -30,7 +30,7 @@ exports.getUserProfile = async function (req) { // Get from database, excluding preferences field const user = await db.collection('users').findOne( { auth0Id: userData.sub }, - { projection: { preferences: 0 } } // Exclude preferences + { projection: { preferences: 0 } }, // Exclude preferences ); if (!user) { return respondWithCode(404, { @@ -66,7 +66,10 @@ exports.updateUserProfile = async function (req, body) { auth0Id: { $ne: auth0UserId }, }); if (existingUser) { - return respondWithCode(409, { code: 409, message: 'Username already taken in application' }); + return respondWithCode(409, { + code: 409, + message: 'Username already taken in application', + }); } } @@ -75,14 +78,12 @@ exports.updateUserProfile = async function (req, body) { // Auth0 will enforce its own uniqueness rules per connection. if (body.username) { try { - await updateAuth0Username(auth0UserId, body.username); await updateUserMetadata(auth0UserId, { nickname: body.username }); } catch (auth0Error) { console.error(`Auth0 username update failed for ${auth0UserId}:`, auth0Error); - return respondWithCode(409, { // Use 409 Conflict or appropriate code + return respondWithCode(409, { code: 409, message: 'Failed to update username with identity provider. It might already be taken.', - // Optionally include details: details: auth0Error.message }); } } @@ -113,50 +114,54 @@ exports.updateUserProfile = async function (req, body) { // User-provided fields if (body.demographicData?.gender !== undefined) { - updateData['demographicData.gender'] = body.demographicData.gender; - updateData['demographicData.inferredGender'] = null; // Clear inferred on user update - demographicsChanged = true; + updateData['demographicData.gender'] = body.demographicData.gender; + updateData['demographicData.inferredGender'] = null; // Clear inferred on user update + demographicsChanged = true; } if (body.demographicData?.incomeBracket !== undefined) { - updateData['demographicData.incomeBracket'] = body.demographicData.incomeBracket; - demographicsChanged = true; + updateData['demographicData.incomeBracket'] = body.demographicData.incomeBracket; + demographicsChanged = true; } if (body.demographicData?.country !== undefined) { - updateData['demographicData.country'] = body.demographicData.country; - demographicsChanged = true; + updateData['demographicData.country'] = body.demographicData.country; + demographicsChanged = true; } if (body.demographicData?.age !== undefined) { - const ageValue = body.demographicData.age === null ? null : parseInt(body.demographicData.age); - if (ageValue === null || (!isNaN(ageValue) && ageValue >= 0)) { // Added age >= 0 check - updateData['demographicData.age'] = ageValue; - // No inferred age bracket to clear anymore - demographicsChanged = true; - } else { - console.warn(`Invalid age value provided for user ${auth0UserId}: ${body.demographicData.age}`); - // Optionally return a 400 error here - // return respondWithCode(400, { code: 400, message: 'Invalid age provided.' }); - } + const ageValue = + body.demographicData.age === null ? null : parseInt(body.demographicData.age); + if (ageValue === null || (!isNaN(ageValue) && ageValue >= 0)) { + // Added age >= 0 check + updateData['demographicData.age'] = ageValue; + // No inferred age bracket to clear anymore + demographicsChanged = true; + } else { + console.warn( + `Invalid age value provided for user ${auth0UserId}: ${body.demographicData.age}`, + ); + // Optionally return a 400 error here + // return respondWithCode(400, { code: 400, message: 'Invalid age provided.' }); + } } // --- NEW User-Provided Fields --- if (body.demographicData?.hasKids !== undefined) { - updateData['demographicData.hasKids'] = body.demographicData.hasKids; - updateData['demographicData.inferredHasKids'] = null; // Clear inferred on user update - demographicsChanged = true; + updateData['demographicData.hasKids'] = body.demographicData.hasKids; + updateData['demographicData.inferredHasKids'] = null; // Clear inferred on user update + demographicsChanged = true; } if (body.demographicData?.relationshipStatus !== undefined) { - updateData['demographicData.relationshipStatus'] = body.demographicData.relationshipStatus; - updateData['demographicData.inferredRelationshipStatus'] = null; // Clear inferred on user update - demographicsChanged = true; + updateData['demographicData.relationshipStatus'] = body.demographicData.relationshipStatus; + updateData['demographicData.inferredRelationshipStatus'] = null; // Clear inferred on user update + demographicsChanged = true; } if (body.demographicData?.employmentStatus !== undefined) { - updateData['demographicData.employmentStatus'] = body.demographicData.employmentStatus; - updateData['demographicData.inferredEmploymentStatus'] = null; // Clear inferred on user update - demographicsChanged = true; + updateData['demographicData.employmentStatus'] = body.demographicData.employmentStatus; + updateData['demographicData.inferredEmploymentStatus'] = null; // Clear inferred on user update + demographicsChanged = true; } if (body.demographicData?.educationLevel !== undefined) { - updateData['demographicData.educationLevel'] = body.demographicData.educationLevel; - updateData['demographicData.inferredEducationLevel'] = null; // Clear inferred on user update - demographicsChanged = true; + updateData['demographicData.educationLevel'] = body.demographicData.educationLevel; + updateData['demographicData.inferredEducationLevel'] = null; // Clear inferred on user update + demographicsChanged = true; } // --- REMOVED Verification Flag Handling --- @@ -164,7 +169,6 @@ exports.updateUserProfile = async function (req, body) { // --- End Update Demographic Data --- - // Only update allowed privacy settings let privacySettingsChanged = false; // Flag for privacy changes if (body.privacySettings !== undefined) { @@ -173,23 +177,22 @@ exports.updateUserProfile = async function (req, body) { updateData['privacySettings.dataSharingConsent'] = body.privacySettings.dataSharingConsent; privacySettingsChanged = true; } - if (body.privacySettings.allowInference !== undefined) { // <-- Add check for allowInference + if (body.privacySettings.allowInference !== undefined) { + // <-- Add check for allowInference updateData['privacySettings.allowInference'] = body.privacySettings.allowInference; privacySettingsChanged = true; } // DO NOT update optInStores or optOutStores here } - // Check if there's anything to update (excluding updatedAt) - const updateKeys = Object.keys(updateData).filter(key => key !== 'updatedAt'); + const updateKeys = Object.keys(updateData).filter((key) => key !== 'updatedAt'); if (updateKeys.length === 0) { - // Nothing changed - const currentUser = await db.collection('users').findOne( - { auth0Id: auth0UserId }, - { projection: { preferences: 0 } } - ); - return respondWithCode(200, currentUser || { message: "No changes detected." }); + // Nothing changed + const currentUser = await db + .collection('users') + .findOne({ auth0Id: auth0UserId }, { projection: { preferences: 0 } }); + return respondWithCode(200, currentUser || { message: 'No changes detected.' }); } console.log(`Updating user ${auth0UserId} with data:`, updateData); @@ -212,21 +215,26 @@ exports.updateUserProfile = async function (req, body) { // Invalidate general preferences cache if demographics changed if (demographicsChanged) { - await invalidateCache(`${CACHE_KEYS.PREFERENCES}${auth0UserId}`); - console.log(`Invalidated general preferences cache for ${auth0UserId} due to demographic update.`); + await invalidateCache(`${CACHE_KEYS.PREFERENCES}${auth0UserId}`); + console.log( + `Invalidated general preferences cache for ${auth0UserId} due to demographic update.`, + ); } // Invalidate store-specific preferences if demographics or relevant privacy settings changed // (Keep existing logic, as privacySettingsChanged flag now includes allowInference) const updatedUserDoc = result; // Use the returned document from findOneAndUpdate - if ((demographicsChanged || privacySettingsChanged) && updatedUserDoc.privacySettings?.optInStores) { - const userObjectId = updatedUserDoc._id; // Use the _id from the updated result - console.log(`Invalidating store preferences for user ${userObjectId} due to update.`); - for (const storeId of updatedUserDoc.privacySettings.optInStores) { - const storePrefCacheKey = `${CACHE_KEYS.STORE_PREFERENCES}${userObjectId}:${storeId}`; - await invalidateCache(storePrefCacheKey); - console.log(`Invalidated cache: ${storePrefCacheKey}`); - } + if ( + (demographicsChanged || privacySettingsChanged) && + updatedUserDoc.privacySettings?.optInStores + ) { + const userObjectId = updatedUserDoc._id; // Use the _id from the updated result + console.log(`Invalidating store preferences for user ${userObjectId} due to update.`); + for (const storeId of updatedUserDoc.privacySettings.optInStores) { + const storePrefCacheKey = `${CACHE_KEYS.STORE_PREFERENCES}${userObjectId}:${storeId}`; + await invalidateCache(storePrefCacheKey); + console.log(`Invalidated cache: ${storePrefCacheKey}`); + } } // Update cache with the new data (without preferences) @@ -236,7 +244,10 @@ exports.updateUserProfile = async function (req, body) { } catch (error) { console.error('Update profile failed:', error); // Check for specific MongoDB errors if needed (e.g., validation errors) - return respondWithCode(500, { code: 500, message: 'Internal server error during profile update' }); + return respondWithCode(500, { + code: 500, + message: 'Internal server error during profile update', + }); } }; @@ -252,8 +263,10 @@ exports.deleteUserProfile = async function (req) { const userData = req.user || (await getUserData(req.headers.authorization?.split(' ')[1])); // Find user to get ID for cache invalidation later - const user = await db.collection('users').findOne({ auth0Id: userData.sub }, { projection: { _id: 1, privacySettings: 1 } }); - if (!user) { + const user = await db + .collection('users') + .findOne({ auth0Id: userData.sub }, { projection: { _id: 1, privacySettings: 1 } }); + if (!user) { return respondWithCode(404, { code: 404, message: 'User not found', @@ -262,11 +275,10 @@ exports.deleteUserProfile = async function (req) { const userObjectId = user._id; const userPrivacySettings = user.privacySettings; - // Delete from database const deleteResult = await db.collection('users').deleteOne({ auth0Id: userData.sub }); if (deleteResult.deletedCount === 0) { - // This case should ideally not happen if findOne succeeded, but good practice + // This case should ideally not happen if findOne succeeded, but good practice return respondWithCode(404, { code: 404, message: 'User not found during deletion', @@ -280,14 +292,13 @@ exports.deleteUserProfile = async function (req) { await invalidateCache(`${CACHE_KEYS.USER_DATA}${userData.sub}`); await invalidateCache(`${CACHE_KEYS.PREFERENCES}${userData.sub}`); - // Clear related store preference caches + // Clear related store preference caches if (userPrivacySettings?.optInStores) { for (const storeId of userPrivacySettings.optInStores) { await invalidateCache(`${CACHE_KEYS.STORE_PREFERENCES}${userObjectId}:${storeId}`); } } - return respondWithCode(204); } catch (error) { console.error('Delete profile failed:', error); @@ -299,13 +310,25 @@ exports.deleteUserProfile = async function (req) { * Get Recent User Data Submissions * Retrieves a list of recent data submissions made about the authenticated user. */ -exports.getRecentUserData = async function (req, limit = 10, page = 1, dataType, storeId, startDate, endDate, searchTerm) { // Add new params +exports.getRecentUserData = async function ( + req, + limit = 10, + page = 1, + dataType, + storeId, + startDate, + endDate, + searchTerm, +) { + // Add new params try { const db = getDB(); const userData = req.user || (await getUserData(req.headers.authorization?.split(' ')[1])); // Find user to get their internal _id - const user = await db.collection('users').findOne({ auth0Id: userData.sub }, { projection: { _id: 1 } }); + const user = await db + .collection('users') + .findOne({ auth0Id: userData.sub }, { projection: { _id: 1 } }); if (!user) { return respondWithCode(404, { code: 404, message: 'User not found' }); } @@ -323,9 +346,9 @@ exports.getRecentUserData = async function (req, limit = 10, page = 1, dataType, try { matchQuery.storeId = new ObjectId(storeId); } catch (e) { - console.warn(`Invalid storeId format provided: ${storeId}`); - // Decide how to handle: return empty, error, or ignore filter - return respondWithCode(400, { code: 400, message: 'Invalid store ID format provided.' }); + console.warn(`Invalid storeId format provided: ${storeId}`); + // Decide how to handle: return empty, error, or ignore filter + return respondWithCode(400, { code: 400, message: 'Invalid store ID format provided.' }); } } @@ -334,14 +357,18 @@ exports.getRecentUserData = async function (req, limit = 10, page = 1, dataType, if (startDate) { try { dateFilter.$gte = new Date(startDate); - } catch (e) { console.warn('Invalid startDate format:', startDate); } + } catch (e) { + console.warn('Invalid startDate format:', startDate); + } } if (endDate) { try { const end = new Date(endDate); end.setDate(end.getDate() + 1); // Include the whole end day dateFilter.$lt = end; - } catch (e) { console.warn('Invalid endDate format:', endDate); } + } catch (e) { + console.warn('Invalid endDate format:', endDate); + } } if (Object.keys(dateFilter).length > 0) { matchQuery.timestamp = dateFilter; @@ -362,12 +389,14 @@ exports.getRecentUserData = async function (req, limit = 10, page = 1, dataType, // --- End Query Building --- // Query userData collection with the built query - const recentData = await db.collection('userData') + const recentData = await db + .collection('userData') .find(matchQuery) // Use the dynamic query .sort({ timestamp: -1 }) // Sort by submission time descending .skip(skip) .limit(limit) - .project({ // Expand projection to include details needed for display + .project({ + // Expand projection to include details needed for display _id: 1, storeId: 1, dataType: 1, @@ -384,17 +413,17 @@ exports.getRecentUserData = async function (req, limit = 10, page = 1, dataType, .toArray(); // Simple transformation (can be enhanced on frontend) - const formattedData = recentData.map(entry => ({ + const formattedData = recentData.map((entry) => ({ _id: entry._id.toString(), // Convert ObjectId to string storeId: entry.storeId.toString(), // Convert ObjectId to string dataType: entry.dataType, timestamp: entry.timestamp, // Process entries for simpler display structure if needed here, // or handle it on the frontend. Example: - details: entry.entries.map(e => ({ - timestamp: e.timestamp, - ...(entry.dataType === 'purchase' && { items: e.items }), - ...(entry.dataType === 'search' && { query: e.query, results: e.results }), + details: entry.entries.map((e) => ({ + timestamp: e.timestamp, + ...(entry.dataType === 'purchase' && { items: e.items }), + ...(entry.dataType === 'search' && { query: e.query, results: e.results }), })), })); @@ -403,7 +432,6 @@ exports.getRecentUserData = async function (req, limit = 10, page = 1, dataType, // Example: const cacheKey = `${CACHE_KEYS.USER_RECENT_DATA}${user._id}:${page}:${limit}:${dataType || 'all'}:${storeId || 'all'}:${startDate || 'all'}:${endDate || 'all'}:${searchTerm || ''}`; return respondWithCode(200, formattedData); - } catch (error) { console.error('Get recent user data failed:', error); return respondWithCode(500, { code: 500, message: 'Internal server error' }); @@ -442,22 +470,24 @@ exports.getSpendingAnalytics = async function (req) { const hasDateFilter = Object.keys(dateMatch).length > 0; // --- End Date Range Handling --- - // Find user to get their internal _id - const user = await db.collection('users').findOne({ auth0Id: userData.sub }, { projection: { _id: 1 } }); + const user = await db + .collection('users') + .findOne({ auth0Id: userData.sub }, { projection: { _id: 1 } }); if (!user) { return respondWithCode(404, { code: 404, message: 'User not found' }); } // Fetch the taxonomy once (remains the same) const taxonomyDoc = await db.collection('taxonomy').findOne({ current: true }); - const categoryMap = (taxonomyDoc && taxonomyDoc.data && taxonomyDoc.data.categories) - ? taxonomyDoc.data.categories.reduce((map, cat) => { - map[cat.id] = cat.name; // Assuming category ID is used in items - map[cat.name] = cat.name; // Allow matching by name too, just in case - return map; - }, {}) - : {}; + const categoryMap = + taxonomyDoc && taxonomyDoc.data && taxonomyDoc.data.categories + ? taxonomyDoc.data.categories.reduce((map, cat) => { + map[cat.id] = cat.name; // Assuming category ID is used in items + map[cat.name] = cat.name; // Allow matching by name too, just in case + return map; + }, {}) + : {}; const pipeline = [ // Match user and data type @@ -473,53 +503,62 @@ exports.getSpendingAnalytics = async function (req) { $group: { _id: { // Group by year-month and category - yearMonth: { $dateToString: { format: "%Y-%m", date: "$entries.timestamp" } }, - category: '$entries.items.category' // Use the category field from item + yearMonth: { $dateToString: { format: '%Y-%m', date: '$entries.timestamp' } }, + category: '$entries.items.category', // Use the category field from item }, // Calculate total spent for this category in this month monthlyTotal: { $sum: { $cond: { - if: { $and: [ - { $isNumber: '$entries.items.price' }, - { $isNumber: '$entries.items.quantity' } - ]}, - then: { $multiply: ['$entries.items.price', '$entries.items.quantity'] }, - // Handle cases where quantity might be missing but price exists - else: { $cond: { if: { $isNumber: '$entries.items.price' }, then: '$entries.items.price', else: 0 } } - } - } - } - } + if: { + $and: [ + { $isNumber: '$entries.items.price' }, + { $isNumber: '$entries.items.quantity' }, + ], + }, + then: { $multiply: ['$entries.items.price', '$entries.items.quantity'] }, + // Handle cases where quantity might be missing but price exists + else: { + $cond: { + if: { $isNumber: '$entries.items.price' }, + then: '$entries.items.price', + else: 0, + }, + }, + }, + }, + }, + }, }, // --- Group by Month to structure categories --- { $group: { _id: '$_id.yearMonth', // Group by month string (e.g., "2025-01") categories: { - $push: { // Create an array of category-spend pairs for the month - k: { $ifNull: [ { $toString: '$_id.category' }, "Unknown" ] }, // Category name (or ID as string) - v: '$monthlyTotal' - } - } - } + $push: { + // Create an array of category-spend pairs for the month + k: { $ifNull: [{ $toString: '$_id.category' }, 'Unknown'] }, // Category name (or ID as string) + v: '$monthlyTotal', + }, + }, + }, }, // --- Convert categories array to object and sort --- { $project: { _id: 0, // Exclude the default _id month: '$_id', // Rename _id to month - spending: { $arrayToObject: '$categories' } // Convert [{k: "Cat1", v: 100}, ...] to { "Cat1": 100, ... } - } + spending: { $arrayToObject: '$categories' }, // Convert [{k: "Cat1", v: 100}, ...] to { "Cat1": 100, ... } + }, }, // Sort by month ascending - { $sort: { month: 1 } } + { $sort: { month: 1 } }, ]; const results = await db.collection('userData').aggregate(pipeline).toArray(); // --- Map category IDs/names to proper names from taxonomy --- - const spendingAnalytics = results.map(monthlyData => { + const spendingAnalytics = results.map((monthlyData) => { const mappedSpending = {}; for (const categoryKey in monthlyData.spending) { const categoryName = categoryMap[categoryKey] || categoryKey; // Use mapped name or original key @@ -527,19 +566,17 @@ exports.getSpendingAnalytics = async function (req) { } return { month: monthlyData.month, - spending: mappedSpending + spending: mappedSpending, }; }); // --- End Mapping --- - // Caching could be added here, considering date range in the key // const cacheKey = `${CACHE_KEYS.USER_SPENDING_ANALYTICS}${user._id}:${startDate || 'all'}:${endDate || 'all'}`; // await setCache(cacheKey, JSON.stringify(spendingAnalytics), { EX: CACHE_TTL.MEDIUM }); // Return the array structure: [{ month: "YYYY-MM", spending: { "Category1": 100, ... } }, ...] return respondWithCode(200, spendingAnalytics); - } catch (error) { console.error('Get spending analytics failed:', error); return respondWithCode(500, { code: 500, message: 'Internal server error' }); diff --git a/api-service/utils/auth0Util.js b/tapiro-api-internal/utils/auth0Util.js similarity index 73% rename from api-service/utils/auth0Util.js rename to tapiro-api-internal/utils/auth0Util.js index 593b90b..2cb7ede 100644 --- a/api-service/utils/auth0Util.js +++ b/tapiro-api-internal/utils/auth0Util.js @@ -62,56 +62,6 @@ async function assignUserRole(userId, role) { } } -/** - * Links two user accounts in Auth0 - * @param {string} primaryUserId - The main user ID (to keep) - * @param {string} secondaryUserId - The user ID to link to primary - * @returns {Promise} - The linked user data - */ -async function linkAccounts(primaryUserId, secondaryUserId) { - try { - const token = await getManagementToken(); - - // Get the secondary user's identity provider data - const secondaryUserResponse = await axios.get( - `${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/users/${secondaryUserId}`, - { - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - }, - ); - - const secondaryUser = secondaryUserResponse.data; - if (!secondaryUser.identities || !secondaryUser.identities.length) { - throw new Error('No identities found on secondary account'); - } - - // Get the provider connection info - const identity = secondaryUser.identities[0]; - const provider = identity.provider; - const userId = identity.user_id; - - // Link the accounts - const response = await axios.post( - `${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/users/${primaryUserId}/identities`, - { provider, user_id: userId }, - { - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - }, - ); - - return response.data; - } catch (error) { - console.error('Account linking failed:', error?.response?.data || error); - throw error; - } -} - /** * Update Auth0 user metadata * @param {string} userId - Auth0 user ID @@ -122,9 +72,9 @@ async function linkAccounts(primaryUserId, secondaryUserId) { async function updateUserMetadata(userId, metadata, invalidateUserCache = false) { try { const token = await getManagementToken(); - + const metadataUpdate = { - user_metadata: metadata + user_metadata: metadata, }; // Update user metadata in Auth0 @@ -136,16 +86,16 @@ async function updateUserMetadata(userId, metadata, invalidateUserCache = false) Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, - } + }, ); - + // Invalidate cache if requested if (invalidateUserCache) { - const { invalidateCache } = require('../utils/redisUtil'); - const { CACHE_KEYS } = require('../utils/cacheConfig'); + const { invalidateCache } = require('./redisUtil'); + const { CACHE_KEYS } = require('./cacheConfig'); await invalidateCache(`${CACHE_KEYS.USER_DATA}${userId}`); } - + return response.data.user_metadata || {}; } catch (error) { console.error('Failed to update user metadata:', error?.response?.data || error); @@ -178,12 +128,15 @@ async function updateUserPhone(userId, phone) { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, - } + }, ); return response.data; } catch (error) { - console.error(`Failed to update Auth0 phone number for ${userId}:`, error?.response?.data || error.message); + console.error( + `Failed to update Auth0 phone number for ${userId}:`, + error?.response?.data || error.message, + ); // Re-throw the error so the calling service can decide how to handle it throw error; } @@ -197,7 +150,7 @@ async function updateUserPhone(userId, phone) { async function getUserMetadata(userId) { try { const token = await getManagementToken(); - + // Get user with metadata from Auth0 Management API const response = await axios.get( `${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/users/${userId}`, @@ -206,9 +159,9 @@ async function getUserMetadata(userId) { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, - } + }, ); - + return response.data.user_metadata || {}; } catch (error) { console.error('Failed to get user metadata:', error?.response?.data || error); @@ -241,14 +194,17 @@ async function updateAuth0Username(userId, newUsername) { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, - } + }, ); console.log(`Successfully updated Auth0 username for ${userId}.`); return response.data; } catch (error) { // Log the specific error (e.g., username already exists) - console.error(`Failed to update Auth0 username for ${userId}:`, error?.response?.data || error.message); + console.error( + `Failed to update Auth0 username for ${userId}:`, + error?.response?.data || error.message, + ); // Re-throw the error so the calling service knows the update failed throw error; } @@ -264,27 +220,26 @@ async function deleteAuth0User(userId) { const token = await getManagementToken(); // Delete user from Auth0 - await axios.delete( - `${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/users/${userId}`, - { - headers: { - Authorization: `Bearer ${token}`, - }, - } - ); + await axios.delete(`${process.env.AUTH0_ISSUER_BASE_URL}/api/v2/users/${userId}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); console.log(`Successfully deleted user ${userId} from Auth0.`); } catch (error) { // Log error but don't throw, allowing the calling service to continue if needed - console.error(`Auth0 deletion failed for user ${userId}:`, error?.response?.data || error.message); + console.error( + `Auth0 deletion failed for user ${userId}:`, + error?.response?.data || error.message, + ); // If you want the deletion failure to stop the process in the service, re-throw the error: // throw error; } } -module.exports = { - getManagementToken, - assignUserRole, - linkAccounts, +module.exports = { + getManagementToken, + assignUserRole, updateUserMetadata, updateUserPhone, getUserMetadata, diff --git a/api-service/utils/authUtil.js b/tapiro-api-internal/utils/authUtil.js similarity index 100% rename from api-service/utils/authUtil.js rename to tapiro-api-internal/utils/authUtil.js diff --git a/api-service/utils/cacheConfig.js b/tapiro-api-internal/utils/cacheConfig.js similarity index 100% rename from api-service/utils/cacheConfig.js rename to tapiro-api-internal/utils/cacheConfig.js diff --git a/api-service/utils/dbSchemas.js b/tapiro-api-internal/utils/dbSchemas.js similarity index 100% rename from api-service/utils/dbSchemas.js rename to tapiro-api-internal/utils/dbSchemas.js diff --git a/api-service/utils/helperUtil.js b/tapiro-api-internal/utils/helperUtil.js similarity index 100% rename from api-service/utils/helperUtil.js rename to tapiro-api-internal/utils/helperUtil.js diff --git a/api-service/utils/mongoUtil.js b/tapiro-api-internal/utils/mongoUtil.js similarity index 100% rename from api-service/utils/mongoUtil.js rename to tapiro-api-internal/utils/mongoUtil.js diff --git a/tapiro-api-internal/utils/redisUtil.js b/tapiro-api-internal/utils/redisUtil.js new file mode 100644 index 0000000..e35c8a9 --- /dev/null +++ b/tapiro-api-internal/utils/redisUtil.js @@ -0,0 +1,51 @@ +require('dotenv').config(); +const redis = require('redis'); +const { CACHE_TTL } = require('./cacheConfig'); + +const client = redis.createClient({ + url: `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`, +}); + +async function connectRedis() { + try { + await client.connect(); + console.log('Connected to Redis'); + } catch (error) { + console.error('Redis connection error:', error); + console.log('Retrying Redis connection in 5 seconds...'); + await new Promise(resolve => setTimeout(resolve, 5000)); + await connectRedis(); // Recursive retry + } +} +async function getCache(key) { + return client.get(key); +} + +async function setCache(key, value, options = {}) { + return client.set(key, value, options); +} + +// Add this function to your existing redisUtil.js +async function invalidateCache(key) { + return setCache(key, '', { EX: CACHE_TTL.INVALIDATION }); +} + +// Modified ping function to handle health checks safely +async function pingRedis() { + try { + return await client.ping(); + } catch (error) { + console.error('Redis ping error:', error); + return null; + } +} + +// Export the new function +module.exports = { + client, + connectRedis, + getCache, + setCache, + invalidateCache, + pingRedis, +}; diff --git a/tapiro-api-internal/utils/writer.js b/tapiro-api-internal/utils/writer.js new file mode 100644 index 0000000..97db628 --- /dev/null +++ b/tapiro-api-internal/utils/writer.js @@ -0,0 +1,40 @@ +const ResponsePayload = function ResponsePayload(code, payload) { + this.code = code; + this.payload = payload; +}; + +exports.respondWithCode = function respondWithCode(code, payload) { + return new ResponsePayload(code, payload); +}; + +const writeJson = function writeJson(response, arg1, arg2) { + let code; + let payload; + + if (arg1 && arg1 instanceof ResponsePayload) { + writeJson(response, arg1.payload, arg1.code); + return; + } + + if (arg2 && Number.isInteger(arg2)) { + code = arg2; + } else if (arg1 && Number.isInteger(arg1)) { + code = arg1; + } + if (code && arg1) { + payload = arg1; + } else if (arg1) { + payload = arg1; + } + + if (!code) { + code = 200; + } + if (typeof payload === 'object') { + payload = JSON.stringify(payload, null, 2); + } + response.writeHead(code, { 'Content-Type': 'application/json' }); + response.end(payload); +}; + +exports.writeJson = writeJson; diff --git a/web/package.json b/web/package.json index dc0fef0..caac3d9 100644 --- a/web/package.json +++ b/web/package.json @@ -11,7 +11,7 @@ "format": "prettier . --write", "format:check": "prettier . --check", "postinstall": "flowbite-react patch", - "generate-api": "swagger-typescript-api generate -p ../api-service/api/openapi.yaml -o ./src/api/types -n apiClient.ts --axios --modular --extract-request-params --extract-request-body --responses" + "generate-api": "swagger-typescript-api generate -p ../tapiro-api-internal/api/openapi.yaml -o ./src/api/types -n apiClient.ts --axios --modular --extract-request-params --extract-request-body --responses" }, "dependencies": { "@auth0/auth0-react": "^2.3.0", diff --git a/web/src/api/types/Users.ts b/web/src/api/types/Users.ts index 5656970..223ba34 100644 --- a/web/src/api/types/Users.ts +++ b/web/src/api/types/Users.ts @@ -19,7 +19,6 @@ import { StoreConsentList, User, UserCreate, - UserData, UserMetadataResponse, UserPreferences, UserPreferencesUpdate, @@ -118,50 +117,6 @@ export class Users< secure: true, ...params, }); - /** - * @description Submit purchase/search history for a user (authenticated via API key) - * - * @tags Data Collection - * @name SubmitUserData - * @summary Submit user data - * @request POST:/users/data - * @secure - * @response `202` `void` Data accepted for processing - * @response `400` `Error` - * @response `401` `Error` - * @response `500` `Error` - */ - submitUserData = (data: UserData, params: RequestParams = {}) => - this.request({ - path: `/users/data`, - method: "POST", - body: data, - secure: true, - type: ContentType.Json, - ...params, - }); - /** - * @description Retrieve preferences for targeted advertising - * - * @tags Data Retrival - * @name GetUserPreferences - * @summary Get user preferences - * @request GET:/users/{userId}/preferences - * @secure - * @response `200` `UserPreferences` UserPreferences retrieved - * @response `401` `Error` - * @response `403` `Error` - * @response `404` `Error` - * @response `500` `Error` - */ - getUserPreferences = (userId: string, params: RequestParams = {}) => - this.request({ - path: `/users/${userId}/preferences`, - method: "GET", - secure: true, - format: "json", - ...params, - }); /** * @description Get the authenticated user's preferences * diff --git a/web/src/api/types/data-contracts.ts b/web/src/api/types/data-contracts.ts index 9c68958..fdf4e8a 100644 --- a/web/src/api/types/data-contracts.ts +++ b/web/src/api/types/data-contracts.ts @@ -45,10 +45,6 @@ export interface PrivacySettings { * @default true */ allowInference?: boolean; - /** List of store IDs the user explicitly allows data sharing with. */ - optInStores?: string[]; - /** List of store IDs the user explicitly blocks data sharing with. */ - optOutStores?: string[]; } export interface Store { @@ -71,6 +67,13 @@ export interface Store { } export interface UserCreate { + /** + * User's unique username, chosen during registration. + * @minLength 3 + * @maxLength 15 + * @pattern ^[a-zA-Z0-9_-]+$ + */ + username: string; preferences?: PreferenceItem[]; /** User's consent for data sharing */ dataSharingConsent: boolean; @@ -390,12 +393,13 @@ export interface PingStatus { export interface UserMetadataResponse { /** Whether metadata was updated successfully */ updated?: boolean; - /** User metadata from Auth0 */ metadata?: { /** The type of registration */ registrationType?: "user" | "store"; /** Whether registration process is complete */ registrationComplete?: boolean; + /** User or store display name */ + nickname?: string; }; } diff --git a/web/src/components/auth/RegistrationCompletionModal.tsx b/web/src/components/auth/RegistrationCompletionModal.tsx index ecad6aa..e05ed63 100644 --- a/web/src/components/auth/RegistrationCompletionModal.tsx +++ b/web/src/components/auth/RegistrationCompletionModal.tsx @@ -17,7 +17,6 @@ export function RegistrationCompletionModal() { "user" | "store" | null >(null); const [error, setError] = useState(null); - const [isNavigating, setIsNavigating] = useState(false); const registerUserMutation = useRegisterUser(); const registerStoreMutation = useRegisterStore(); @@ -32,13 +31,11 @@ export function RegistrationCompletionModal() { const handleUserSubmit = async (userData: UserCreate) => { setError(null); - setIsNavigating(true); // Add this state to track navigation try { await registerUserMutation.mutateAsync(userData); // Navigation will happen via the mutation's onSuccess handler } catch (err) { - setIsNavigating(false); console.error("Failed to complete user registration:", err); const message = err instanceof Error diff --git a/web/src/components/auth/UserRegistrationForm.tsx b/web/src/components/auth/UserRegistrationForm.tsx index 6975b77..22a137a 100644 --- a/web/src/components/auth/UserRegistrationForm.tsx +++ b/web/src/components/auth/UserRegistrationForm.tsx @@ -10,6 +10,7 @@ import { Popover, Select, TextInput, + HelperText, // <-- Import HelperText } from "flowbite-react"; import { UserCreate } from "../../api/types/data-contracts"; import LoadingSpinner from "../common/LoadingSpinner"; @@ -77,6 +78,32 @@ export function UserRegistrationForm({ const [incomeBracket, setIncomeBracket] = useState(null); const [country, setCountry] = useState(null); const [age, setAge] = useState(null); + // --- Add state for username --- + const [username, setUsername] = useState(""); + const [usernameError, setUsernameError] = useState(null); + + const validateUsername = (value: string): boolean => { + if (!value) { + setUsernameError("Username is required."); + return false; + } + if (value.length < 3) { + setUsernameError("Username must be at least 3 characters."); + return false; + } + if (value.length > 15) { + setUsernameError("Username cannot exceed 15 characters."); + return false; + } + if (!/^[a-zA-Z0-9_-]+$/.test(value)) { + setUsernameError( + "Username can only contain letters, numbers, underscores, and hyphens.", + ); + return false; + } + setUsernameError(null); + return true; + }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); @@ -88,7 +115,12 @@ export function UserRegistrationForm({ // return; } + if (!validateUsername(username)) { + return; + } + const userData: UserCreate = { + username: username, // <-- Include username dataSharingConsent: dataSharingConsent, allowInference: allowInference, // <-- Include allowInference preferences: [], @@ -140,6 +172,27 @@ export function UserRegistrationForm({ Complete User Registration + {/* Username Input */} +
+
+ +
+ { + setUsername(e.target.value); + validateUsername(e.target.value); + }} + color={usernameError ? "failure" : "gray"} + /> + {usernameError && ( + {usernameError} + )} +
+ {/* Demographic Information Section */}

diff --git a/web/src/layout/Header.tsx b/web/src/layout/Header.tsx index 8412362..42a6daa 100644 --- a/web/src/layout/Header.tsx +++ b/web/src/layout/Header.tsx @@ -15,11 +15,13 @@ import { import { useAuth } from "../hooks/useAuth"; import { Link, useLocation } from "react-router"; import LoadingSpinner from "../components/common/LoadingSpinner"; +import { useUserMetadata } from "../api/hooks/useAuthHooks"; export function Header() { const { isLoading, isAuthenticated, user, userRoles, login, logout } = - useAuth(); // Use the auth context - const location = useLocation(); // Get current location for active links + useAuth(); + const { data: userMetadata, isLoading: metadataLoading } = useUserMetadata(); + const location = useLocation(); // Determine dashboard link based on role const getDashboardLink = () => { @@ -43,6 +45,15 @@ export function Header() { return "/"; }; + // Get display name from metadata or user object + const getDisplayName = () => { + // First try to get custom nickname from metadata (set during registration) + if (userMetadata?.metadata?.nickname) { + return userMetadata.metadata.nickname; + } + return user?.nickname || user?.name || "User"; + }; + const handleLogin = () => login(); const handleLogout = () => logout(); @@ -50,7 +61,6 @@ export function Header() { {" "} - {/* Use Link for internal navigation */}
- {isLoading ? ( + {isLoading || metadataLoading ? ( } // Use user picture from Auth0 + label={} > - {user?.name || user?.nickname} - {" "} - {/* Use name or nickname */} + {getDisplayName()} {/* Use the new function */} + {user?.email} - {/* Add Profile Link */} Profile @@ -92,7 +100,6 @@ export function Header() { ) : ( <> - {/* Use onClick for Auth0 actions */} @@ -134,6 +141,14 @@ export function Header() { > API Docs + + Demo App + ); diff --git a/web/src/pages/UserDashboard/UserAnalyticsPage.tsx b/web/src/pages/UserDashboard/UserAnalyticsPage.tsx index 5e3234a..d1314fe 100644 --- a/web/src/pages/UserDashboard/UserAnalyticsPage.tsx +++ b/web/src/pages/UserDashboard/UserAnalyticsPage.tsx @@ -420,10 +420,12 @@ const UserAnalyticsPage: React.FC = () => {
- Date - Type - Store - Details + + Date + Type + Store + Details + {activityData.map((entry: RecentUserDataEntry) => { diff --git a/web/src/pages/UserDashboard/UserDashboard.tsx b/web/src/pages/UserDashboard/UserDashboard.tsx index 2ecee40..fe83073 100644 --- a/web/src/pages/UserDashboard/UserDashboard.tsx +++ b/web/src/pages/UserDashboard/UserDashboard.tsx @@ -23,7 +23,6 @@ import { HiOutlineNewspaper, HiOutlineCurrencyDollar, HiOutlineShare, - HiOutlineAdjustments, HiCalendar, HiOutlineGlobeAlt, HiOutlineCake, @@ -206,6 +205,12 @@ export default function UserDashboard() { const [startDate, setStartDate] = useState(null); const [endDate, setEndDate] = useState(null); + // --- Function to clear date filters --- + const clearDates = () => { + setStartDate(null); + setEndDate(null); + }; + // --- Fetch Data (Hooks called unconditionally at the top) --- const { data: profile, @@ -413,272 +418,213 @@ export default function UserDashboard() { return ( <>
- {/* Control Tabs with state */} setActiveTab(tab)} > - {/* Overview Tab (Existing Content) */} + {/* Overview Tab */} - {/* Show loading/error specific to overview if needed, or rely on main checks */} - {profileLoading || - activityLoading || - spendingLoading || - consentLoading || - preferencesLoading || - storesLoading || - taxonomyLoading ? ( -
- -
- ) : profileError || - activityError || - spendingError || - consentError || - preferencesError || - storesError || - taxonomyError ? ( -
- -
- ) : ( -
- {/* --- Recent Activity Card --- */} - -
-

- - Recent Activity -

- {activityLoading ? ( -
- -
- ) : activityError ? ( - - Could not load recent activity. - - ) : !recentActivity || recentActivity.length === 0 ? ( -

- No recent activity recorded. -

- ) : ( -
- - {recentActivity.map((entry) => ( - - - - - {formatDate(entry.timestamp)} - - - {entry.dataType} - {entry.storeId && - ` at ${storeNameMap.get(entry.storeId) || "a store"}`} - - {/* Add more details if needed */} - - - ))} - -
- )} + {activeTab === 0 && ( // Conditionally render Overview tab content + <> + {profileLoading || + activityLoading || + spendingLoading || + consentLoading || + preferencesLoading || + storesLoading || + taxonomyLoading ? ( +
+
- {/* Update Link to Button to change tab */} - - - - {/* --- Spending Overview Card --- */} - -
-

- - Spending Overview -

- {/* Date Filters */} -
- setStartDate(date)} - maxDate={endDate || undefined} - placeholder="Start Date" - /> - setEndDate(date)} - minDate={startDate || undefined} - placeholder="End Date" - /> - {(startDate || endDate) && ( - - )} -
- {/* Chart Area */} - {spendingError ? ( - - Could not load spending data. - - ) : spendingLoading ? ( -
- -
- ) : !lineChartData || lineChartData.length === 0 ? ( -

- No spending data available - {startDate || endDate ? " for this period" : " yet"}. -

- ) : ( -
- - - - - - - formatCurrency(value) - } - labelFormatter={formatMonth} - /> - - {categories.map((category, index) => ( - - ))} - - -
- )} + ) : profileError || + activityError || + spendingError || + consentError || + preferencesError || + storesError || + taxonomyError ? ( +
+
- {/* Update Link to Button to change tab */} - - - - {/* --- Data Sharing Card --- */} - -
-

- - Data Sharing -

- {consentLoading || storesLoading ? ( -
- + ) : ( +
+ {/* --- Recent Activity Card --- */} + +
+

+ + Recent Activity +

+ {activityLoading ? ( +
+ +
+ ) : activityError ? ( + + Could not load recent activity. + + ) : !recentActivity || recentActivity.length === 0 ? ( +

+ No recent activity recorded. +

+ ) : ( +
+ + {recentActivity.map((entry) => ( + + + + + {formatDate(entry.timestamp)} + + + {entry.dataType} + {entry.storeId && + ` at ${storeNameMap.get(entry.storeId) || "Unknown Store"}`} + + {/* Further details can be added here if needed */} + + + ))} + +
+ )}
- ) : consentError || storesError ? ( - - Could not load sharing settings. - - ) : (consentLists?.optInStores?.length ?? 0) === 0 ? ( -

- You are not currently sharing data with any stores. -

- ) : ( - <> -

- You are sharing data with{" "} - - {consentLists?.optInStores?.length} - {" "} - store(s): -

- - {(consentLists?.optInStores || []) - .slice(0, 3) // Show first 3 - .map((storeId) => ( - - {storeNameMap.get(storeId) || - `Store ID: ${storeId}`} - - ))} - {(consentLists?.optInStores?.length ?? 0) > 3 && ( - - ...and more - + +
+ + {/* --- Spending Overview Card --- */} + +
+

+ + Spending Overview +

+ {/* Date Filters */} +
+ setStartDate(date)} + maxDate={endDate || undefined} + placeholder="Start Date" + inputMode="none" // Prevent keyboard on mobile + /> + setEndDate(date)} + minDate={startDate || undefined} + placeholder="End Date" + inputMode="none" // Prevent keyboard on mobile + /> + {(startDate || endDate) && ( + )} - - - )} -
- {/* Update Link to Button to change tab */} - - - - {/* --- Preferences & Demographics Card --- */} - -
-

- - Preference & Profile Overview -

-
- {/* Pie Chart Section */} -
+
+ {/* Chart Area */} + {spendingError ? ( + + Could not load spending data. + + ) : spendingLoading ? ( +
+ +
+ ) : !lineChartData || lineChartData.length === 0 ? ( +

+ No spending data available + {startDate || endDate ? " for this period" : " yet"} + . +

+ ) : ( +
+ + + + + + + formatCurrency(value) + } + labelFormatter={formatMonth} + /> + + {categories.map((category, index) => ( + + ))} + + +
+ )} +
+ + + + {/* --- Top Interests Card --- */} + +

Top Interests

@@ -708,8 +654,8 @@ export default function UserDashboard() { label={renderCustomizedLabel} outerRadius={100} fill="#8884d8" - dataKey="value" // Use 'value' as dataKey - nameKey="name" // Use 'name' as nameKey + dataKey="value" + nameKey="name" > {preferencesPieChartData.map( (_entry, index) => ( @@ -734,19 +680,68 @@ export default function UserDashboard() {
)} - {/* Update Link to Button to change tab */} -
- - {/* Demographics Section */} + +
+ + {/* --- Data Sharing Card --- */} + +
+

+ + Data Sharing +

+ {consentLoading || storesLoading ? ( +
+ +
+ ) : consentError || storesError ? ( + + Could not load sharing settings. + + ) : (consentLists?.optInStores?.length ?? 0) === 0 ? ( +

+ You are not currently sharing data with any stores. +

+ ) : ( + + {(consentLists?.optInStores ?? []).map( + (storeId) => ( + + Sharing with{" "} + + {storeNameMap.get(storeId) || storeId} + + + ), + )} + + )} +
+ +
+ + {/* Demographics Section */} +

About You @@ -770,7 +765,7 @@ export default function UserDashboard() {

)}
-
+
- -
+ )} + )} @@ -801,8 +796,7 @@ export default function UserDashboard() { title="Preferences" icon={HiOutlineSparkles} > - {/* Render UserPreferencesPage directly */} - + {activeTab === 1 && } {/* Sharing Tab */} @@ -811,8 +805,7 @@ export default function UserDashboard() { title="Sharing" icon={HiOutlineShare} > - {/* Render UserDataSharingPage directly */} - + {activeTab === 2 && } {/* Analytics Tab */} @@ -821,8 +814,7 @@ export default function UserDashboard() { title="Analytics" icon={HiOutlineChartPie} > - {/* Render UserAnalyticsPage directly */} - + {activeTab === 3 && }
diff --git a/web/src/pages/UserDashboard/UserPreferencesPage.tsx b/web/src/pages/UserDashboard/UserPreferencesPage.tsx index ea805c3..dc68d41 100644 --- a/web/src/pages/UserDashboard/UserPreferencesPage.tsx +++ b/web/src/pages/UserDashboard/UserPreferencesPage.tsx @@ -448,10 +448,26 @@ const UserPreferencesPage: React.FC = () => { const handleRemovePreference = (indexToRemove: number) => { const currentPreferences = preferencesData?.preferences || []; - const updatedPreferences = currentPreferences.filter( + const filteredPreferences = currentPreferences.filter( (_, index) => index !== indexToRemove, ); - updatePreferences({ preferences: updatedPreferences }); + + const sanitizedPreferences = filteredPreferences.map((pref) => { + const newScore = + pref.score === null || pref.score === undefined ? 0.5 : pref.score; + const newAttributes = + pref.attributes === null || pref.attributes === undefined + ? {} + : pref.attributes; + + return { + ...pref, + score: newScore, + attributes: newAttributes, + }; + }); + + updatePreferences({ preferences: sanitizedPreferences }); }; const openAddModal = () => { diff --git a/web/src/pages/UserDashboard/UserProfilePage.tsx b/web/src/pages/UserDashboard/UserProfilePage.tsx index 1eca662..e2cd742 100644 --- a/web/src/pages/UserDashboard/UserProfilePage.tsx +++ b/web/src/pages/UserDashboard/UserProfilePage.tsx @@ -102,22 +102,28 @@ export default function UserProfilePage() { // Show toasts based on UPDATE mutation state useEffect(() => { if (isUpdateSuccess) { + setShowErrorToast(false); // Ensure error toast is hidden setShowSuccessToast(true); setToastMessage("Profile updated successfully!"); - resetUpdateMutation(); - const timer = setTimeout(() => setShowSuccessToast(false), 5000); + const timer = setTimeout(() => { + setShowSuccessToast(false); + resetUpdateMutation(); // Reset mutation state after toast is hidden + }, 5000); return () => clearTimeout(timer); } }, [isUpdateSuccess, resetUpdateMutation]); useEffect(() => { if (updateError) { + setShowSuccessToast(false); // Ensure success toast is hidden setShowErrorToast(true); setToastMessage( updateError.message || "Failed to update profile. Please try again.", ); - resetUpdateMutation(); - const timer = setTimeout(() => setShowErrorToast(false), 5000); + const timer = setTimeout(() => { + setShowErrorToast(false); + resetUpdateMutation(); // Reset mutation state after toast is hidden + }, 5000); return () => clearTimeout(timer); } }, [updateError, resetUpdateMutation]); @@ -133,8 +139,12 @@ export default function UserProfilePage() { "showPostLogoutToast", "User account deleted successfully.", ); // <-- Set flag - resetDeleteMutation(); - logout(); // <-- Call logout directly + const timer = setTimeout(() => { + // Added a delay for reset similar to update toasts + resetDeleteMutation(); + logout(); + }, 100); // Short delay to ensure state updates propagate if needed before logout + return () => clearTimeout(timer); } }, [isDeleteSuccess, resetDeleteMutation, logout]); // Keep dependencies @@ -147,8 +157,10 @@ export default function UserProfilePage() { deleteError.message || "Failed to delete user account. Please try again.", ); - resetDeleteMutation(); - const timer = setTimeout(() => setShowErrorToast(false), 5000); + const timer = setTimeout(() => { + setShowErrorToast(false); + resetDeleteMutation(); + }, 5000); return () => clearTimeout(timer); } }, [deleteError, resetDeleteMutation]); @@ -156,7 +168,7 @@ export default function UserProfilePage() { const onSubmit: SubmitHandler = (data) => { const updatePayload: UserUpdate = { username: data.username, - phone: data.phone, + phone: data.phone || undefined, // If phone is empty string, send undefined privacySettings: { dataSharingConsent: data.privacySettings_dataSharingConsent ?? false, allowInference: data.privacySettings_allowInference ?? true, // Default to true if undefined