Careers
diff --git a/apps/landing/app/globals.css b/apps/landing/app/globals.css
index 0a1aece1a6..a8d99722c1 100644
--- a/apps/landing/app/globals.css
+++ b/apps/landing/app/globals.css
@@ -166,14 +166,16 @@
/* Gradient mesh background */
.bg-gradient-mesh {
- background-image: radial-gradient(at 67% 33%, hsla(180, 70%, 40%, 0.15) 0px, transparent 50%),
+ background-image:
+ radial-gradient(at 67% 33%, hsla(180, 70%, 40%, 0.15) 0px, transparent 50%),
radial-gradient(at 33% 67%, hsla(24, 95%, 53%, 0.15) 0px, transparent 50%),
radial-gradient(at 80% 80%, hsla(242, 100%, 70%, 0.15) 0px, transparent 50%),
radial-gradient(at 0% 0%, hsla(343, 100%, 76%, 0.15) 0px, transparent 50%);
}
.dark .bg-gradient-mesh {
- background-image: radial-gradient(at 67% 33%, hsla(180, 70%, 40%, 0.15) 0px, transparent 50%),
+ background-image:
+ radial-gradient(at 67% 33%, hsla(180, 70%, 40%, 0.15) 0px, transparent 50%),
radial-gradient(at 33% 67%, hsla(24, 95%, 53%, 0.15) 0px, transparent 50%),
radial-gradient(at 80% 80%, hsla(242, 100%, 70%, 0.15) 0px, transparent 50%),
radial-gradient(at 0% 0%, hsla(343, 100%, 76%, 0.15) 0px, transparent 50%);
diff --git a/apps/landing/components/app-preview.tsx b/apps/landing/components/app-preview.tsx
index 1fee5685a3..143786430b 100644
--- a/apps/landing/components/app-preview.tsx
+++ b/apps/landing/components/app-preview.tsx
@@ -27,7 +27,7 @@ export default function AppPreview() {
setCurrentScreen((prev) => (prev + 1) % screens.length);
}, 3000);
return () => clearInterval(interval);
- }, []);
+ }, [screens.length]);
assertDefined(screens[currentScreen]);
diff --git a/apps/landing/components/sections/download.tsx b/apps/landing/components/sections/download.tsx
index 3456b353dc..6b766c9cee 100644
--- a/apps/landing/components/sections/download.tsx
+++ b/apps/landing/components/sections/download.tsx
@@ -8,7 +8,6 @@ import Link from 'next/link';
export default function DownloadSection() {
return (
- // biome-ignore lint/nursery/useUniqueElementIds: ignore
{/* Background decoration */}
diff --git a/apps/landing/components/sections/faq.tsx b/apps/landing/components/sections/faq.tsx
index 0b76e449e3..489ee0f756 100644
--- a/apps/landing/components/sections/faq.tsx
+++ b/apps/landing/components/sections/faq.tsx
@@ -29,7 +29,6 @@ export default function FaqSection() {
});
return (
- // biome-ignore lint/nursery/useUniqueElementIds: ignore
{/* Background pattern */}
diff --git a/apps/landing/components/sections/feature-section.tsx b/apps/landing/components/sections/feature-section.tsx
index 560c8d947f..eff874f21b 100644
--- a/apps/landing/components/sections/feature-section.tsx
+++ b/apps/landing/components/sections/feature-section.tsx
@@ -10,7 +10,6 @@ import Link from 'next/link';
export default function FeatureSection() {
return (
- // biome-ignore lint/nursery/useUniqueElementIds: ignore
{/* Background decorations */}
diff --git a/apps/landing/components/sections/how-it-works.tsx b/apps/landing/components/sections/how-it-works.tsx
index 5986aa7f56..3c083ca964 100644
--- a/apps/landing/components/sections/how-it-works.tsx
+++ b/apps/landing/components/sections/how-it-works.tsx
@@ -9,7 +9,6 @@ export default function HowItWorksSection() {
const stepIcons = ['Download', 'Map', 'Backpack'];
return (
- // biome-ignore lint/nursery/useUniqueElementIds: ignore
{/* Background pattern */}
diff --git a/apps/landing/components/sections/testimonials.tsx b/apps/landing/components/sections/testimonials.tsx
index 5710e6f241..44d5a04c04 100644
--- a/apps/landing/components/sections/testimonials.tsx
+++ b/apps/landing/components/sections/testimonials.tsx
@@ -7,7 +7,6 @@ import { Star } from 'lucide-react';
export default function TestimonialsSection() {
return (
- // biome-ignore lint/nursery/useUniqueElementIds: ignore
(
diff --git a/apps/landing/components/ui/carousel.tsx b/apps/landing/components/ui/carousel.tsx
index a181a3a892..3b7978a3e5 100644
--- a/apps/landing/components/ui/carousel.tsx
+++ b/apps/landing/components/ui/carousel.tsx
@@ -152,25 +152,25 @@ const CarouselContent = React.forwardRef>(
- ({ className, ...props }, ref) => {
- const { orientation } = useCarousel();
+const CarouselItem = React.forwardRef<
+ HTMLFieldSetElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { orientation } = useCarousel();
- return (
-
- );
- },
-);
+ return (
+
+ );
+});
CarouselItem.displayName = 'CarouselItem';
const CarouselPrevious = React.forwardRef>(
diff --git a/biome.json b/biome.json
index 4588aa96cd..46acc41c2c 100644
--- a/biome.json
+++ b/biome.json
@@ -1,5 +1,5 @@
{
- "$schema": "https://biomejs.dev/schemas/2.0.4/schema.json",
+ "$schema": "https://biomejs.dev/schemas/2.4.6/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
@@ -32,7 +32,25 @@
"linter": {
"enabled": true,
"rules": {
- "recommended": true
+ "recommended": true,
+ "complexity": {
+ "useMaxParams": {
+ "level": "error",
+ "options": {
+ "max": 3
+ }
+ }
+ },
+ "suspicious": {
+ "noUnknownAtRules": "off"
+ }
+ }
+ },
+ "css": {
+ "parser": {
+ "cssModules": false,
+ "allowWrongLineComments": false,
+ "tailwindDirectives": true
}
},
"javascript": {
diff --git a/bun.lock b/bun.lock
index 3ba08867e2..8d298b4b14 100644
--- a/bun.lock
+++ b/bun.lock
@@ -5,7 +5,7 @@
"": {
"name": "packrat-monorepo",
"devDependencies": {
- "@biomejs/biome": "2.0.4",
+ "@biomejs/biome": "2.4.6",
"@manypkg/cli": "^0.24.0",
"@types/bun": "^1.2.17",
"@types/fs-extra": "^11.0.4",
@@ -110,7 +110,7 @@
},
"devDependencies": {
"@babel/core": "^7.20.0",
- "@biomejs/biome": "2.0.4",
+ "@biomejs/biome": "2.4.6",
"@types/lodash.debounce": "^4.0.9",
"@types/react": "~19.0.10",
"@types/ungap__structured-clone": "^1.2.0",
@@ -272,6 +272,23 @@
"typescript": "^5.8.2",
},
},
+ "packages/analytics": {
+ "name": "@packrat/analytics",
+ "version": "0.1.0",
+ "dependencies": {
+ "@duckdb/node-api": "1.5.0-r.1",
+ "chalk": "^5.4.1",
+ "citty": "^0.2.1",
+ "cli-table3": "^0.6.5",
+ "consola": "^3.4.2",
+ "radash": "^12.1.1",
+ "zod": "^3.24.2",
+ },
+ "devDependencies": {
+ "@types/bun": "latest",
+ "vitest": "~3.1.0",
+ },
+ },
"packages/api": {
"name": "@packrat/api",
"dependencies": {
@@ -640,23 +657,23 @@
"@bcoe/v8-coverage": ["@bcoe/v8-coverage@1.0.2", "", {}, "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA=="],
- "@biomejs/biome": ["@biomejs/biome@2.0.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.0.4", "@biomejs/cli-darwin-x64": "2.0.4", "@biomejs/cli-linux-arm64": "2.0.4", "@biomejs/cli-linux-arm64-musl": "2.0.4", "@biomejs/cli-linux-x64": "2.0.4", "@biomejs/cli-linux-x64-musl": "2.0.4", "@biomejs/cli-win32-arm64": "2.0.4", "@biomejs/cli-win32-x64": "2.0.4" }, "bin": { "biome": "bin/biome" } }, "sha512-DNA++xe+E7UugTvI/HhzSFl6OwrVgU8SIV0Mb2fPtWPk2/oTr4eOSA5xy1JECrvgJeYxurmUBOS49qxv/OUkrQ=="],
+ "@biomejs/biome": ["@biomejs/biome@2.4.6", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.6", "@biomejs/cli-darwin-x64": "2.4.6", "@biomejs/cli-linux-arm64": "2.4.6", "@biomejs/cli-linux-arm64-musl": "2.4.6", "@biomejs/cli-linux-x64": "2.4.6", "@biomejs/cli-linux-x64-musl": "2.4.6", "@biomejs/cli-win32-arm64": "2.4.6", "@biomejs/cli-win32-x64": "2.4.6" }, "bin": { "biome": "bin/biome" } }, "sha512-QnHe81PMslpy3mnpL8DnO2M4S4ZnYPkjlGCLWBZT/3R9M6b5daArWMMtEfP52/n174RKnwRIf3oT8+wc9ihSfQ=="],
- "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-r5McIUMMiedwJ2rltuXhj0+w0W7IJLpkOS+OGCVZQQOOcrGY9gUSUmOo7O6Z7P0vlv5YYZkPbi+qR9MDDWRBSw=="],
+ "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-NW18GSyxr+8sJIqgoGwVp5Zqm4SALH4b4gftIA0n62PTuBs6G2tHlwNAOj0Vq0KKSs7Sf88VjjmHh0O36EnzrQ=="],
- "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-aV5Zc/3E3aXFbrjK1IgCMEQc+6PCkBL+NS+vtjoNM2VPFeM5OL5Q82BI4YZyPnebj+k42BPIoYtz0jJ95PGRRg=="],
+ "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-4uiE/9tuI7cnjtY9b07RgS7gGyYOAfIAGeVJWEfeCnAarOAS7qVmuRyX6d7JTKw28/mt+rUzMasYeZ+0R/U1Mw=="],
- "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-nlJhf7DyuajMj+S7Ygum59cbrHvI/nSRvedfJcEIx4X7SsiZjpRUiC5XtEn77kg7NIKq/KqG5roQIHkmjuFHCw=="],
+ "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-kMLaI7OF5GN1Q8Doymjro1P8rVEoy7BKQALNz6fiR8IC1WKduoNyteBtJlHT7ASIL0Cx2jR6VUOBIbcB1B8pew=="],
- "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-cNukq2PthoOa7quqaKoEFz4Zd1pDPJGfTR5jVyk9Z9iFHEm6TI7+7eeIs3aYcEuuJPNFR9xhJ4Uj3E2iUWkV3A=="],
+ "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-F/JdB7eN22txiTqHM5KhIVt0jVkzZwVYrdTR1O3Y4auBOQcXxHK4dxULf4z43QyZI5tsnQJrRBHZy7wwtL+B3A=="],
- "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-jlzrNZ+OzN9wvp2RL3cl5Y4NiV7xSU+QV5A8bWXke1on3jKy7QbXajybSjVQ6aFw1gdrqkO/W8xV5HODhIMT4g=="],
+ "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.6", "", { "os": "linux", "cpu": "x64" }, "sha512-oHXmUFEoH8Lql1xfc3QkFLiC1hGR7qedv5eKNlC185or+o4/4HiaU7vYODAH3peRCfsuLr1g6v2fK9dFFOYdyw=="],
- "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-oWQALSbp8xF0t/wiHU2zdkZOpIHyaI9QxQv0Ytty9GAKsCGP6pczp8qyKD/P49iGJdDozHp5KiuQPxs33APhyA=="],
+ "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.6", "", { "os": "linux", "cpu": "x64" }, "sha512-C9s98IPDu7DYarjlZNuzJKTjVHN03RUnmHV5htvqsx6vEUXCDSJ59DNwjKVD5XYoSS4N+BYhq3RTBAL8X6svEg=="],
- "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.0.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-/PbNhMJo9ONja7hOxLlifM/qgeHpRD9bF2flTz5KIrXnQqpuegaRuwP/HYdJ9TFkTKFjHkPLoE4onOz3HIT5CQ=="],
+ "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-xzThn87Pf3YrOGTEODFGONmqXpTwUNxovQb72iaUOdcw8sBSY3+3WD8Hm9IhMYLnPi0n32s3L3NWU6+eSjfqFg=="],
- "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-dIM4SgO4/Rmsb4X7fwKtciQ682SZDSC1lm42uSM9gt8zNqBIeTaqsMc6eO1DpxYWMlAb/n2SML9+HUHmCib7NA=="],
+ "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.6", "", { "os": "win32", "cpu": "x64" }, "sha512-7++XhnsPlr1HDbor5amovPjOH6vsrFOCdp93iKXhFn6bcMUI6soodj3WWKfgEO6JosKU1W5n3uky3WW9RlRjTg=="],
"@cloudflare/containers": ["@cloudflare/containers@0.0.30", "", {}, "sha512-i148xBgmyn/pje82ZIyuTr/Ae0BT/YWwa1/GTJcw6DxEjUHAzZLaBCiX446U9OeuJ2rBh/L/9FIzxX5iYNt1AQ=="],
@@ -678,12 +695,30 @@
"@cloudflare/workers-types": ["@cloudflare/workers-types@4.20260304.0", "", {}, "sha512-oQ0QJpWnCWK9tx5q/ZHQeSsf5EcQWa4KqdDMY/R5Ln0ojFzv6UYO0RWsfDPsoXUAwK671VwaXqAW0Mx0uWz7yw=="],
+ "@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="],
+
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
"@dominicstop/ts-event-emitter": ["@dominicstop/ts-event-emitter@1.1.0", "", {}, "sha512-CcxmJIvUb1vsFheuGGVSQf4KdPZC44XolpUT34+vlal+LyQoBUOn31pjFET5M9ctOxEpt8xa0M3/2M7uUiAoJw=="],
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
+ "@duckdb/node-api": ["@duckdb/node-api@1.5.0-r.1", "", { "dependencies": { "@duckdb/node-bindings": "1.5.0-r.1" } }, "sha512-VjvjqlCyqUJ22bydG/+9Jj5l0CEvx9QUn1SATBkKFY0+x6n4uHzt0kp/FKwXK9LNduXX6KCKtdF+6v5BcHScfA=="],
+
+ "@duckdb/node-bindings": ["@duckdb/node-bindings@1.5.0-r.1", "", { "optionalDependencies": { "@duckdb/node-bindings-darwin-arm64": "1.5.0-r.1", "@duckdb/node-bindings-darwin-x64": "1.5.0-r.1", "@duckdb/node-bindings-linux-arm64": "1.5.0-r.1", "@duckdb/node-bindings-linux-x64": "1.5.0-r.1", "@duckdb/node-bindings-win32-arm64": "1.5.0-r.1", "@duckdb/node-bindings-win32-x64": "1.5.0-r.1" } }, "sha512-ViyoHROp0HhQ0ATT8z6h7SV6vEOxNjN8mJcdkst+3rJIU3Rd/gKs8exJO23LxFsjBuAiDpndpEwFihTAdfwJZg=="],
+
+ "@duckdb/node-bindings-darwin-arm64": ["@duckdb/node-bindings-darwin-arm64@1.5.0-r.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-s7CB9Y10f3dg2W4jdp6zipogxamq1pPQVt/r+YigZrHqk7HvytUeRH1VOI/3wuGTTlR8mAnIXj2MJIrmMujX4w=="],
+
+ "@duckdb/node-bindings-darwin-x64": ["@duckdb/node-bindings-darwin-x64@1.5.0-r.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-2KxnGvg9o/Y915hwr1Mpb6Cbfrvt/w+/Fv0RZ2VXeQuxqNCMOoJd/7yQ8ffWXJRuOFU/vRmsR88koIjW+yJibA=="],
+
+ "@duckdb/node-bindings-linux-arm64": ["@duckdb/node-bindings-linux-arm64@1.5.0-r.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-FpmcfJqhKoInjEa1o5dsWPQAFWYgu1eZgJP94nz6d+cKfdXEZKT6vGIwokZRzDqKLG1ZAOBZO2IhxzmW8IbbfA=="],
+
+ "@duckdb/node-bindings-linux-x64": ["@duckdb/node-bindings-linux-x64@1.5.0-r.1", "", { "os": "linux", "cpu": "x64" }, "sha512-A4Zgsaq6szEKiMyF1eWkoaHw0OMA0a8Zl8Ev0GcZSRjI4iGHgBQ1f/Zi/w3QKyz7OtjIiZ+pQ1XYyG4QoF6OKA=="],
+
+ "@duckdb/node-bindings-win32-arm64": ["@duckdb/node-bindings-win32-arm64@1.5.0-r.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-ctCJ2HWpyiN3FNiCQ40RWALZLwKtJ8Aq4Anen2ush1I0a2E52T//jitLzl7DcX0quzew5eci1MQ+H5T1nVhzFA=="],
+
+ "@duckdb/node-bindings-win32-x64": ["@duckdb/node-bindings-win32-x64@1.5.0-r.1", "", { "os": "win32", "cpu": "x64" }, "sha512-725LoefejBlyG/bUcMfqVJkZ8KnZRLa0xOkszw9Q+V5G26iagJY3dgtGJAvwTeVJvXigSEVc5yD9Cj6kWR67vA=="],
+
"@egjs/hammerjs": ["@egjs/hammerjs@2.0.17", "", { "dependencies": { "@types/hammerjs": "^2.0.36" } }, "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A=="],
"@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
@@ -962,6 +997,8 @@
"@packrat-ai/nativewindui": ["@packrat-ai/nativewindui@2.0.1", "https://npm.pkg.github.com/download/@packrat-ai/nativewindui/2.0.1/c8f3e4e6113c8d464803f637dbfb6fe5fa9a5e36", { "peerDependencies": { "@expo/vector-icons": ">=15.0.0", "@gorhom/bottom-sheet": "^5.1.2", "@react-native-community/datetimepicker": "^8.4.0", "@react-native-community/slider": "^5.0.0", "@react-native-picker/picker": "^2.11.0", "@react-native-segmented-control/segmented-control": "^2.5.0", "@react-navigation/drawer": "^7.1.1", "@react-navigation/elements": "^2.3.1", "@react-navigation/native": "^7.0.14", "@rn-primitives/alert-dialog": "^1.1.0", "@rn-primitives/avatar": "^1.0.4", "@rn-primitives/checkbox": "^1.1.0", "@rn-primitives/context-menu": "^1.1.0", "@rn-primitives/dropdown-menu": "^1.1.0", "@rn-primitives/hooks": "^1.1.0", "@rn-primitives/portal": "^1.1.0", "@rn-primitives/slot": "^1.1.0", "@shopify/flash-list": "^2.0.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "expo-blur": "~15.0.8", "expo-device": "~8.0.0", "expo-glass-effect": "*", "expo-haptics": "~15.0.8", "expo-image": "~3.0.11", "expo-linear-gradient": "~15.0.8", "expo-navigation-bar": "~5.0.10", "expo-router": "~6.0.23", "expo-symbols": "~1.0.8", "nativewind": "^4.1.21", "react": ">=19.0.0", "react-native": ">=0.79.0", "react-native-keyboard-controller": "^1.16.7", "react-native-reanimated": ">=3.17.0", "react-native-safe-area-context": ">=5.4.0", "react-native-screens": ">=4.11.0", "react-native-uitextview": "^1.1.4", "rn-icon-mapper": "^0.0.1", "tailwind-merge": "^2.2.1" } }, "sha512-zMzFalxu6MKuBMIIDzeiMj/7wM9qn8kFkAbWm3IxBWTHHSsl/Zic053DCJZS1GQcY0Ke2Om3TmUTuBujERuwvA=="],
+ "@packrat/analytics": ["@packrat/analytics@workspace:packages/analytics"],
+
"@packrat/api": ["@packrat/api@workspace:packages/api"],
"@packrat/guards": ["@packrat/guards@workspace:packages/guards"],
@@ -1746,6 +1783,8 @@
"ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
+ "citty": ["citty@0.2.2", "", {}, "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w=="],
+
"cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="],
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
@@ -1754,6 +1793,8 @@
"cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
+ "cli-table3": ["cli-table3@0.6.5", "", { "dependencies": { "string-width": "^4.2.0" }, "optionalDependencies": { "@colors/colors": "1.5.0" } }, "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ=="],
+
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
@@ -1792,6 +1833,8 @@
"connect": ["connect@3.7.0", "", { "dependencies": { "debug": "2.6.9", "finalhandler": "1.1.2", "parseurl": "~1.3.3", "utils-merge": "1.0.1" } }, "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ=="],
+ "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
+
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
@@ -3656,6 +3699,8 @@
"@manypkg/tools/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
+ "@packrat/analytics/@types/bun": ["@types/bun@1.3.12", "", { "dependencies": { "bun-types": "1.3.12" } }, "sha512-DBv81elK+/VSwXHDlnH3Qduw+KxkTIWi7TXkAeh24zpi5l0B2kUg9Ga3tb4nJaPcOFswflgi/yAvMVBPrxMB+A=="],
+
"@packrat/api/@types/bun": ["@types/bun@1.3.12", "", { "dependencies": { "bun-types": "1.3.12" } }, "sha512-DBv81elK+/VSwXHDlnH3Qduw+KxkTIWi7TXkAeh24zpi5l0B2kUg9Ga3tb4nJaPcOFswflgi/yAvMVBPrxMB+A=="],
"@pnpm/network.ca-file/graceful-fs": ["graceful-fs@4.2.10", "", {}, "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="],
@@ -4174,6 +4219,8 @@
"@manypkg/tools/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
+ "@packrat/analytics/@types/bun/bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="],
+
"@packrat/api/@types/bun/bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="],
"@react-native/codegen/glob/minimatch": ["minimatch@3.1.4", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw=="],
diff --git a/package.json b/package.json
index 5615456b30..f32535e627 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,7 @@
"bump": "bun .github/scripts/bump.ts"
},
"devDependencies": {
- "@biomejs/biome": "2.0.4",
+ "@biomejs/biome": "2.4.6",
"@manypkg/cli": "^0.24.0",
"@types/bun": "^1.2.17",
"@types/fs-extra": "^11.0.4",
diff --git a/packages/analytics/src/core/query-builder.ts b/packages/analytics/src/core/query-builder.ts
index 5eef7b612f..24607cadac 100644
--- a/packages/analytics/src/core/query-builder.ts
+++ b/packages/analytics/src/core/query-builder.ts
@@ -77,7 +77,11 @@ export class SQLFragments {
}
/** Safe float extraction from FIELD_MAPPINGS with optional range check. */
- static safeFloat(field: string, alias?: string, minVal?: number, maxVal?: number): string {
+ static safeFloat(
+ field: string,
+ opts: { alias?: string; minVal?: number; maxVal?: number } = {},
+ ): string {
+ const { alias, minVal, maxVal } = opts;
const a = alias ?? field;
const variations = FIELD_MAPPINGS[field] ?? [field];
const digits = DBConfig.PRICE_ROUND_DIGITS;
@@ -169,10 +173,10 @@ export class SQLFragments {
SQLFragments.safeCoalesce('product_url'),
SQLFragments.safeCoalesce('image_url'),
// V2 fields
- SQLFragments.safeFloat('compare_at_price', undefined, 0, DBConfig.MAX_VALID_PRICE),
- SQLFragments.safeFloat('rating_value', undefined, 0, 5),
+ SQLFragments.safeFloat('compare_at_price', { minVal: 0, maxVal: DBConfig.MAX_VALID_PRICE }),
+ SQLFragments.safeFloat('rating_value', { minVal: 0, maxVal: 5 }),
SQLFragments.safeInt('review_count'),
- SQLFragments.safeFloat('weight', undefined, -1),
+ SQLFragments.safeFloat('weight', { minVal: -1 }),
SQLFragments.safeCoalesce('weight_unit', "''"),
SQLFragments.safeCoalesce('color'),
SQLFragments.safeCoalesce('size'),
@@ -254,11 +258,9 @@ export class QueryBuilder {
searchQuery(
keyword: string,
- sites?: string[],
- minPrice?: number,
- maxPrice?: number,
- limit: number = DBConfig.DEFAULT_LIMIT,
+ opts: { sites?: string[]; minPrice?: number; maxPrice?: number; limit?: number } = {},
): string {
+ const { sites, minPrice, maxPrice, limit = DBConfig.DEFAULT_LIMIT } = opts;
const select = SQLFragments.selectFields().join(',\n ');
const source = SQLFragments.readCsvSource(this.bucketPath);
@@ -373,10 +375,9 @@ export class QueryBuilder {
dealsQuery(
maxPrice: number,
- category?: string,
- sites?: string[],
- limit: number = DBConfig.DEFAULT_LIMIT,
+ opts: { category?: string; sites?: string[]; limit?: number } = {},
): string {
+ const { category, sites, limit = DBConfig.DEFAULT_LIMIT } = opts;
const select = SQLFragments.selectFields().join(',\n ');
const source = SQLFragments.readCsvSource(this.bucketPath);
diff --git a/packages/analytics/test/core/query-builder.test.ts b/packages/analytics/test/core/query-builder.test.ts
index b7989aa0e6..e1b30122d7 100644
--- a/packages/analytics/test/core/query-builder.test.ts
+++ b/packages/analytics/test/core/query-builder.test.ts
@@ -176,18 +176,18 @@ describe('QueryBuilder', () => {
});
it('applies site filter', () => {
- const sql = qb.searchQuery('tent', ['rei']);
+ const sql = qb.searchQuery('tent', { sites: ['rei'] });
expect(sql).toContain("'rei'");
});
it('applies price range', () => {
- const sql = qb.searchQuery('tent', undefined, 50, 200);
+ const sql = qb.searchQuery('tent', { minPrice: 50, maxPrice: 200 });
expect(sql).toContain('>= 50');
expect(sql).toContain('<= 200');
});
it('uses custom limit', () => {
- const sql = qb.searchQuery('tent', undefined, undefined, undefined, 50);
+ const sql = qb.searchQuery('tent', { limit: 50 });
expect(sql).toContain('LIMIT 50');
});
});
@@ -232,7 +232,7 @@ describe('QueryBuilder', () => {
});
it('filters by category', () => {
- const sql = qb.dealsQuery(100, 'tents');
+ const sql = qb.dealsQuery(100, { category: 'tents' });
expect(sql).toContain("'%tents%'");
});
});
diff --git a/packages/api/src/global.d.ts b/packages/api/src/global.d.ts
index c470ee06cd..5041e76011 100644
--- a/packages/api/src/global.d.ts
+++ b/packages/api/src/global.d.ts
@@ -1,3 +1,4 @@
+///
import 'typed-htmx';
// A demo of how to augment foreign types with htmx attributes.
diff --git a/packages/api/src/routes/chat.ts b/packages/api/src/routes/chat.ts
index d70848939f..7ac376aaa7 100644
--- a/packages/api/src/routes/chat.ts
+++ b/packages/api/src/routes/chat.ts
@@ -358,7 +358,7 @@ chatRoutes.openapi(updateReportRoute, async (c) => {
return c.json({ error: 'Unauthorized' }, 403);
}
- const id = Number.parseInt(c.req.param('id'));
+ const id = Number.parseInt(c.req.param('id'), 10);
const { status } = await c.req.json();
await db
diff --git a/packages/api/src/routes/packTemplates/generateFromOnlineContent.ts b/packages/api/src/routes/packTemplates/generateFromOnlineContent.ts
index bd1721f765..d967a154c0 100644
--- a/packages/api/src/routes/packTemplates/generateFromOnlineContent.ts
+++ b/packages/api/src/routes/packTemplates/generateFromOnlineContent.ts
@@ -60,7 +60,7 @@ async function fetchTikTokPostData(
const { APP_CONTAINER } = getEnv(c);
// Get the container instance using the binding
- const container = getContainer(APP_CONTAINER as any);
+ const container = getContainer(APP_CONTAINER);
// Make request to the container's /import endpoint
const response = await container.fetch(
@@ -446,9 +446,7 @@ generateFromOnlineContentRoutes.openapi(generateFromOnlineContentRoute, async (c
return c.json({ ...newTemplate, items: insertedItems }, 201);
} catch (error) {
console.error('Error generating pack template:', error);
- c.get('sentry').captureException(error, {
- extra: { contentUrl, errorType: 'template_generation_error' },
- } as any);
+ c.get('sentry').captureException(error);
// Determine specific error type based on error context
let errorCode = 'UNKNOWN_ERROR';
diff --git a/packages/api/src/services/aiService.ts b/packages/api/src/services/aiService.ts
index 5c3d7a4701..8ac301b64b 100644
--- a/packages/api/src/services/aiService.ts
+++ b/packages/api/src/services/aiService.ts
@@ -1,5 +1,4 @@
import { createPerplexity } from '@ai-sdk/perplexity';
-import type { AutoRAG } from '@cloudflare/workers-types';
import type { Env } from '@packrat/api/types/env';
import { DEFAULT_MODELS } from '@packrat/api/utils/ai/models';
import { getEnv } from '@packrat/api/utils/env-validation';
diff --git a/packages/api/src/services/imageDetectionService.ts b/packages/api/src/services/imageDetectionService.ts
index a250a0203c..e18191b0dc 100644
--- a/packages/api/src/services/imageDetectionService.ts
+++ b/packages/api/src/services/imageDetectionService.ts
@@ -89,6 +89,7 @@ export class ImageDetectionService {
/**
* Detect items in an image and find matching catalog items
*/
+
async detectAndMatchItems(
imageUrl: string,
matchLimit: number = 3,
diff --git a/packages/api/src/services/r2-bucket.ts b/packages/api/src/services/r2-bucket.ts
index 4c0500ed86..4186706ba8 100644
--- a/packages/api/src/services/r2-bucket.ts
+++ b/packages/api/src/services/r2-bucket.ts
@@ -13,7 +13,7 @@ import {
UploadPartCommand,
} from '@aws-sdk/client-s3';
import type { Env } from '@packrat/api/types/env';
-import { isString } from '@packrat/guards';
+import { isDate, isNumber, isObject, isString } from '@packrat/guards';
// Define our own types to avoid conflicts with Cloudflare Workers types
interface R2HTTPMetadata {
@@ -583,39 +583,53 @@ export class R2BucketService {
}
private createR2Object(key: string, response: Record): R2Object {
- const r2Object = {
+ const toUploaded = (v: unknown): Date => {
+ if (isDate(v)) return v;
+ if (isString(v) || isNumber(v)) return new Date(v);
+ return new Date();
+ };
+ const toExpiry = (v: unknown): Date | undefined => {
+ if (isDate(v)) return v;
+ if (isString(v) || isNumber(v)) return new Date(v);
+ return undefined;
+ };
+ const toStringRecord = (v: unknown): Record => {
+ if (!isObject(v)) return {};
+ const out: Record = {};
+ for (const [k, val] of Object.entries(v as Record)) {
+ if (isString(val)) out[k] = val;
+ }
+ return out;
+ };
+
+ const { ContentType, ContentLanguage, ContentDisposition, ContentEncoding, CacheControl } =
+ response;
+
+ const r2Object: R2Object = {
key,
- version: response.VersionId || '',
- size: response.ContentLength || 0,
+ version: isString(response.VersionId) ? response.VersionId : '',
+ size: isNumber(response.ContentLength) ? response.ContentLength : 0,
etag: isString(response.ETag) ? response.ETag.replace(/"/g, '') : '',
- httpEtag: response.ETag || '',
+ httpEtag: isString(response.ETag) ? response.ETag : '',
checksums: this.createChecksums(response),
- uploaded: new Date(String(response.LastModified) || new Date()),
+ uploaded: toUploaded(response.LastModified),
httpMetadata: {
- contentType: response.ContentType,
- contentLanguage: response.ContentLanguage,
- contentDisposition: response.ContentDisposition,
- contentEncoding: response.ContentEncoding,
- cacheControl: response.CacheControl,
- cacheExpiry: response.Expires,
+ contentType: isString(ContentType) ? ContentType : undefined,
+ contentLanguage: isString(ContentLanguage) ? ContentLanguage : undefined,
+ contentDisposition: isString(ContentDisposition) ? ContentDisposition : undefined,
+ contentEncoding: isString(ContentEncoding) ? ContentEncoding : undefined,
+ cacheControl: isString(CacheControl) ? CacheControl : undefined,
+ cacheExpiry: toExpiry(response.Expires),
},
- customMetadata: response.Metadata || {},
- storageClass: response.StorageClass || 'STANDARD',
+ customMetadata: toStringRecord(response.Metadata),
+ storageClass: isString(response.StorageClass) ? response.StorageClass : 'STANDARD',
+ range: undefined,
writeHttpMetadata: (_headers: Record) => {
// This is a no-op in our implementation
},
};
- // Add range if present
- if (response.ContentRange) {
- // @ts-ignore - ignore
- r2Object.range = response.ContentRange; // Assign the value if it exists
- } else {
- // @ts-ignore - ignore
- r2Object.range = undefined; // Explicitly set to undefined if not present
- }
-
- return r2Object as R2Object;
+ return r2Object;
}
private createChecksums(response: Record): R2Checksums {
diff --git a/packages/api/src/utils/__tests__/compute-pack.test.ts b/packages/api/src/utils/__tests__/compute-pack.test.ts
index 37368b4b21..ae7a8b2c0f 100644
--- a/packages/api/src/utils/__tests__/compute-pack.test.ts
+++ b/packages/api/src/utils/__tests__/compute-pack.test.ts
@@ -165,7 +165,7 @@ describe('computePacksWeights', () => {
}),
];
const results = computePacksWeights(packs);
- expect(results[0]!.totalWeight).toBe(500);
- expect(results[1]!.totalWeight).toBe(1000);
+ expect(results[0]?.totalWeight).toBe(500);
+ expect(results[1]?.totalWeight).toBe(1000);
});
});
diff --git a/packages/api/src/utils/csv-utils.ts b/packages/api/src/utils/csv-utils.ts
index 282e5da8e5..375998af0b 100644
--- a/packages/api/src/utils/csv-utils.ts
+++ b/packages/api/src/utils/csv-utils.ts
@@ -26,7 +26,7 @@ export function mapCsvRowToItem({
const reviewCountStr =
fieldMap.reviewCount !== undefined ? values[fieldMap.reviewCount] : undefined;
- item.reviewCount = reviewCountStr ? parseInt(reviewCountStr) || 0 : 0;
+ item.reviewCount = reviewCountStr ? parseInt(reviewCountStr, 10) || 0 : 0;
if (fieldMap.categories !== undefined && values[fieldMap.categories]) {
const val = values[fieldMap.categories]?.trim();
diff --git a/packages/api/src/utils/env-validation.ts b/packages/api/src/utils/env-validation.ts
index 364fcfa700..4cb847beb6 100644
--- a/packages/api/src/utils/env-validation.ts
+++ b/packages/api/src/utils/env-validation.ts
@@ -1,4 +1,4 @@
-import type { Ai, DurableObjectNamespace, Queue, R2Bucket } from '@cloudflare/workers-types';
+import type { Container } from '@cloudflare/containers';
import type { Context } from 'hono';
import { env } from 'hono/adapter';
import { z } from 'zod';
@@ -110,7 +110,7 @@ export type ValidatedEnv = Omit<
LOGS_QUEUE: Queue;
EMBEDDINGS_QUEUE: Queue;
// AppContainer Durable Object binding (APP_CONTAINER)
- APP_CONTAINER: DurableObjectNamespace;
+ APP_CONTAINER: DurableObjectNamespace>;
};
// Cache for validated environments per request
diff --git a/packages/api/test/fixtures/pack-fixtures.ts b/packages/api/test/fixtures/pack-fixtures.ts
index c21e765d18..0b9e28bfa4 100644
--- a/packages/api/test/fixtures/pack-fixtures.ts
+++ b/packages/api/test/fixtures/pack-fixtures.ts
@@ -29,6 +29,7 @@ export const createTestPack = (
/**
* Test fixture for creating a pack item
*/
+
export const createTestPackItem = (
packId: string,
overrides?: Partial>,
diff --git a/packages/api/test/fixtures/pack-template-fixtures.ts b/packages/api/test/fixtures/pack-template-fixtures.ts
index aca56fd8bb..787150b2f2 100644
--- a/packages/api/test/fixtures/pack-template-fixtures.ts
+++ b/packages/api/test/fixtures/pack-template-fixtures.ts
@@ -27,6 +27,7 @@ export const createTestPackTemplate = (
/**
* Test fixture for creating a pack template item
*/
+
export const createTestPackTemplateItem = (
packTemplateId: string,
overrides?: Partial>,
diff --git a/packages/api/test/utils/db-helpers.ts b/packages/api/test/utils/db-helpers.ts
index 955c3987e1..a9b43cf79d 100644
--- a/packages/api/test/utils/db-helpers.ts
+++ b/packages/api/test/utils/db-helpers.ts
@@ -91,6 +91,7 @@ export async function seedCatalogItem(overrides?: Partial>,
@@ -139,6 +140,7 @@ export async function seedPackTemplate(
* Seeds multiple pack templates into the test database
* @returns Array of created pack templates with ids
*/
+
export async function seedPackTemplates(
count: number,
overrides?: Partial>,
@@ -161,6 +163,7 @@ export async function seedPackTemplates(
* Seeds a pack template item into the test database
* @returns The created pack template item with id
*/
+
export async function seedPackTemplateItem(
packTemplateId: string,
overrides?: Partial>,
@@ -180,6 +183,7 @@ export async function seedPackTemplateItem(
* Seeds multiple pack template items into the test database
* @returns Array of created pack template items with ids
*/
+
export async function seedPackTemplateItems(
packTemplateId: string,
count: number,
@@ -219,6 +223,7 @@ export async function seedPack(overrides?: Partial>,
@@ -241,6 +246,7 @@ export async function seedPacks(
* Seeds a pack item into the test database
* @returns The created pack item with id
*/
+
export async function seedPackItem(
packId: string,
overrides?: Partial>,
@@ -260,6 +266,7 @@ export async function seedPackItem(
* Seeds multiple pack items into the test database
* @returns Array of created pack items with ids
*/
+
export async function seedPackItems(
packId: string,
count: number,