diff --git a/apps/dashboard/src/components/CommandPalette.tsx b/apps/dashboard/src/components/CommandPalette.tsx index d97cedc..0f90fd2 100644 --- a/apps/dashboard/src/components/CommandPalette.tsx +++ b/apps/dashboard/src/components/CommandPalette.tsx @@ -3,15 +3,25 @@ import { QK } from "@/lib/query-keys"; import { useQuery } from "@tanstack/react-query"; import { Command } from "cmdk"; import { + Activity, + BarChart2, + Bell, + Clock, + Code, + Database, FolderOpen, + Globe, HardDrive, + Key, LayoutDashboard, ScrollText, Settings, Shield, Users, + Webhook, + Zap, } from "lucide-react"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { useNavigate } from "react-router"; interface CommandPaletteProps { @@ -19,16 +29,44 @@ interface CommandPaletteProps { onClose: () => void; } -const staticCommands = [ +interface CommandItem { + label: string; + href: string; + icon: React.ElementType; + keywords?: string; +} + +const staticCommands: CommandItem[] = [ { label: "Overview", href: "/", icon: LayoutDashboard }, { label: "Projects", href: "/projects", icon: FolderOpen }, { label: "Storage", href: "/storage", icon: HardDrive }, { label: "Logs", href: "/logs", icon: ScrollText }, + { label: "Observability", href: "/observability", icon: Activity }, + { label: "Metrics", href: "/metrics", icon: BarChart2 }, { label: "Audit Log", href: "/audit", icon: Shield }, { label: "Team", href: "/team", icon: Users }, - { label: "Settings", href: "/settings", icon: Settings }, + { label: "Settings", href: "/settings", icon: Settings, keywords: "general" }, { label: "SMTP Settings", href: "/settings/smtp", icon: Settings }, - { label: "API Keys", href: "/settings/api-keys", icon: Settings }, + { label: "Notifications", href: "/settings/notifications", icon: Bell }, + { label: "API Keys", href: "/settings/api-keys", icon: Key }, + { label: "Inngest", href: "/settings/inngest", icon: Zap }, +]; + +const projectTabCommands = (projectId: string): CommandItem[] => [ + { label: "Overview", href: `/projects/${projectId}`, icon: FolderOpen }, + { label: "Observability", href: `/projects/${projectId}/observability`, icon: Activity }, + { label: "Users", href: `/projects/${projectId}/users`, icon: Users }, + { label: "Auth", href: `/projects/${projectId}/auth`, icon: Key }, + { label: "Database", href: `/projects/${projectId}/database`, icon: Database }, + { label: "Environment", href: `/projects/${projectId}/env`, icon: Globe }, + { label: "Webhooks", href: `/projects/${projectId}/webhooks`, icon: Webhook }, + { label: "Functions", href: `/projects/${projectId}/functions`, icon: Zap }, + { label: "IaC Schema", href: `/projects/${projectId}/iac/schema`, icon: Code }, + { label: "IaC Functions", href: `/projects/${projectId}/iac/functions`, icon: Code }, + { label: "IaC Jobs", href: `/projects/${projectId}/iac/jobs`, icon: Code }, + { label: "IaC Realtime", href: `/projects/${projectId}/iac/realtime`, icon: Code }, + { label: "IaC Query", href: `/projects/${projectId}/iac/query`, icon: Code }, + { label: "Realtime", href: `/projects/${projectId}/realtime`, icon: Clock }, ]; export function CommandPalette({ open, onClose }: CommandPaletteProps) { @@ -45,6 +83,29 @@ export function CommandPalette({ open, onClose }: CommandPaletteProps) { if (!open) setQuery(""); }, [open]); + const allCommands = useMemo(() => { + const commands = [...staticCommands]; + if (projectsData?.projects) { + for (const project of projectsData.projects) { + commands.push({ + label: project.name, + href: `/projects/${project.id}`, + icon: FolderOpen, + keywords: `project ${project.name}`, + }); + for (const tab of projectTabCommands(project.id)) { + commands.push({ + label: `${project.name} > ${tab.label}`, + href: tab.href, + icon: tab.icon, + keywords: `project ${project.name} ${tab.label}`, + }); + } + } + } + return commands; + }, [projectsData]); + if (!open) return null; function go(href: string) { @@ -68,13 +129,13 @@ export function CommandPalette({ open, onClose }: CommandPaletteProps) { - + 0 && ( - {projectsData!.projects.map((p) => ( - go(`/projects/${p.id}`)} - className="flex items-center gap-2.5 px-3 py-2 rounded-lg cursor-pointer text-sm" - style={{ color: "var(--color-text-secondary)" }} - > - - {p.name} - - ))} + {allCommands + .filter((c) => c.keywords?.includes("project")) + .map((cmd) => ( + go(cmd.href)} + className="flex items-center gap-2.5 px-3 py-2 rounded-lg cursor-pointer text-sm" + style={{ color: "var(--color-text-secondary)" }} + > + + {cmd.label} + + ))} )} diff --git a/apps/dashboard/src/pages/MetricsPage.tsx b/apps/dashboard/src/pages/MetricsPage.tsx index 28fbc6c..5ad4ea7 100644 --- a/apps/dashboard/src/pages/MetricsPage.tsx +++ b/apps/dashboard/src/pages/MetricsPage.tsx @@ -5,9 +5,27 @@ import { Skeleton } from "@/components/ui/skeleton"; import { api } from "@/lib/api"; import { QK } from "@/lib/query-keys"; import { useQuery } from "@tanstack/react-query"; -import { BarChart2, Clock, FolderOpen, TrendingUp, Users, Zap } from "lucide-react"; +import { + AlertTriangle, + BarChart2, + Clock, + FolderOpen, + TrendingUp, + Users, + Zap, +} from "lucide-react"; import { useSearchParams } from "react-router"; -import { Area, AreaChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts"; +import { + Area, + AreaChart, + Bar, + BarChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, + Cell, +} from "recharts"; type Period = "24h" | "7d" | "30d"; @@ -62,17 +80,30 @@ export default function MetricsPage() { const ts = timeseries?.timeseries ?? []; const endpoints = topEndpoints?.endpoints ?? []; + const totalRequests = ts.reduce((sum: number, point: any) => sum + (point.total ?? 0), 0); + const totalErrors = ts.reduce((sum: number, point: any) => sum + (point.errors ?? 0), 0); + const errorRate = totalRequests > 0 ? ((totalErrors / totalRequests) * 100).toFixed(2) : "0.00"; + const setPeriod = (p: Period) => { const newParams = new URLSearchParams(searchParams); newParams.set("period", p); setSearchParams(newParams); }; + const latencyBars = l + ? [ + { label: "P50", value: l.p50, color: "var(--color-success)" }, + { label: "P95", value: l.p95, color: "var(--color-warning)" }, + { label: "P99", value: l.p99, color: "var(--color-danger)" }, + { label: "Avg", value: l.avg, color: "var(--color-brand)" }, + ] + : []; + return (
@@ -124,117 +155,198 @@ export default function MetricsPage() { />
- {/* Latency Distribution */} - {latencyLoading ? ( -
- {[1, 2, 3, 4].map((i) => ( - - ))} -
- ) : latencyError ? ( + {/* Secondary stats */} +
-
- Failed to load latency data +
+ + + Requests ({period}) + +
+
+ {totalRequests.toLocaleString()}
- ) : (
- {[ - { label: "P50 Latency", value: l?.p50 ?? 0, icon: TrendingUp }, - { label: "P95 Latency", value: l?.p95 ?? 0, icon: TrendingUp }, - { label: "P99 Latency", value: l?.p99 ?? 0, icon: TrendingUp }, - { label: "Avg Latency", value: l?.avg ?? 0, icon: BarChart2 }, - ].map(({ label, value, icon: Icon }) => ( -
-
- - - {label} - -
-
- {value}ms -
-
- ))} +
+ + + Errors ({period}) + +
+
0 ? "var(--color-danger)" : "var(--color-text-primary)", + }} + > + {totalErrors.toLocaleString()} +
- )} - - {/* Request Trends */} - {timeseriesLoading ? ( - - ) : timeseriesError ? (
-
Failed to load request trends
+
+ + + Error Rate + +
+
1 ? "var(--color-danger)" : "var(--color-text-primary)", + }} + > + {errorRate}% +
- ) : (
-

- Request Trends — {period} -

-
- - - new Date(v).toLocaleTimeString([], { hour: "2-digit" })} - stroke="var(--color-text-muted)" - fontSize={11} - /> - - - - - - +
+ + + P95 Latency + +
+
+ {l?.p95 ?? 0}ms
- )} +
+ + {/* Charts row */} +
+ {/* Request Trends */} + {timeseriesLoading ? ( + + ) : timeseriesError ? ( +
+
Failed to load request trends
+
+ ) : ( +
+

+ Request Trends — {period} +

+
+ + + + new Date(v).toLocaleTimeString([], { hour: "2-digit" }) + } + stroke="var(--color-text-muted)" + fontSize={11} + /> + + + + + + +
+
+ )} + + {/* Latency Distribution */} + {latencyLoading ? ( + + ) : latencyError ? ( +
+
Failed to load latency data
+
+ ) : ( +
+

+ Latency Distribution — {period} +

+
+ + + + + [`${value}ms`, "Latency"]} + /> + + {latencyBars.map((entry, index) => ( + + ))} + + + +
+
+ )} +
{/* Top Endpoints Performance */} {topEndpointsLoading ? ( @@ -292,7 +404,10 @@ export default function MetricsPage() { className="border-t" style={{ borderColor: "var(--color-border)" }} > - + #{i + 1} diff --git a/apps/dashboard/src/pages/OverviewPage.tsx b/apps/dashboard/src/pages/OverviewPage.tsx index 08e1540..d7c4f90 100644 --- a/apps/dashboard/src/pages/OverviewPage.tsx +++ b/apps/dashboard/src/pages/OverviewPage.tsx @@ -183,8 +183,13 @@ export default function OverviewPage() {

Recent Activity

-
- {(auditData?.logs ?? []).map((log: any) => ( +
+ {(auditData?.logs ?? []).length === 0 ? ( +
+ No recent activity yet. +
+ ) : ( + (auditData?.logs ?? []).map((log: any) => (
- ))} -
+ )) + )} +
diff --git a/apps/dashboard/src/pages/StorageBucketPage.tsx b/apps/dashboard/src/pages/StorageBucketPage.tsx index 0330f4e..dfbadab 100644 --- a/apps/dashboard/src/pages/StorageBucketPage.tsx +++ b/apps/dashboard/src/pages/StorageBucketPage.tsx @@ -24,13 +24,20 @@ export default function StorageBucketPage() { const { data, isLoading } = useQuery({ queryKey: ["storageObjects", bucketName], - queryFn: () => api.get(`/admin/storage/${bucketName}`), + queryFn: () => api.get<{ objects: { Key: string; Size: number; LastModified?: string; ETag?: string }[] }>(`/admin/storage/buckets/${bucketName}/objects`), }); if (isLoading) return ; const objects = data?.objects ?? []; + const formatSize = (bytes?: number) => { + if (bytes == null) return "-"; + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; + return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; + }; + return (
Name Size - Type - Created + Last Modified Actions {objects - .filter((o: any) => o.name.includes(search)) - .map((obj: any) => ( - - {obj.name} - - {obj.size} - + .filter((o) => o.Key.includes(search)) + .map((obj) => ( + + {obj.Key} - {obj.content_type} + {formatSize(obj.Size)} - {obj.created_at ? new Date(obj.created_at).toLocaleDateString() : "-"} + {obj.LastModified + ? new Date(obj.LastModified).toLocaleDateString() + : "-"}
@@ -106,10 +111,10 @@ export default function StorageBucketPage() { ))} - - -
- )} +
+ +
+ )}
); diff --git a/apps/dashboard/src/pages/StoragePage.tsx b/apps/dashboard/src/pages/StoragePage.tsx index 0096e06..ab2b2f6 100644 --- a/apps/dashboard/src/pages/StoragePage.tsx +++ b/apps/dashboard/src/pages/StoragePage.tsx @@ -1,18 +1,46 @@ import { EmptyState } from "@/components/ui/EmptyState"; import { PageHeader } from "@/components/ui/PageHeader"; import { PageSkeleton } from "@/components/ui/PageSkeleton"; +import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; import { api } from "@/lib/api"; import { QK } from "@/lib/query-keys"; -import { formatDate } from "@/lib/utils"; -import { useQuery } from "@tanstack/react-query"; -import { FolderOpen, HardDrive } from "lucide-react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { FolderOpen, HardDrive, Loader2, Plus } from "lucide-react"; +import { useState } from "react"; import { Link } from "react-router"; +import { toast } from "sonner"; export default function StoragePage() { + const queryClient = useQueryClient(); + const [open, setOpen] = useState(false); + const [bucketName, setBucketName] = useState(""); + const { data, isLoading } = useQuery({ queryKey: QK.storageBuckets(), - queryFn: () => api.get("/admin/storage"), + queryFn: () => api.get<{ buckets: { Name: string; CreationDate?: string }[] }>("/admin/storage/buckets"), + }); + + const createMutation = useMutation({ + mutationFn: (name: string) => api.post("/admin/storage/buckets", { name }), + onSuccess: () => { + toast.success("Bucket created"); + setOpen(false); + setBucketName(""); + queryClient.invalidateQueries({ queryKey: QK.storageBuckets() }); + }, + onError: (err: any) => toast.error(err.message), }); if (isLoading) return ; @@ -21,19 +49,70 @@ export default function StoragePage() { return (
- + + + + + + + + Create Bucket + + + Create a new storage bucket (pocket) for your projects. + + +
+
+ + setBucketName(e.target.value)} + required + /> +
+
+ + + + +
+ + } + />
{buckets.length === 0 ? ( ) : (
- {buckets.map((bucket: any) => ( - + {buckets.map((bucket) => ( +
@@ -44,11 +123,16 @@ export default function StoragePage() {
-

- {bucket.name} +

+ {bucket.Name}

- {bucket.public ? "Public" : "Private"} + {bucket.CreationDate + ? new Date(bucket.CreationDate).toLocaleDateString() + : "S3 Bucket"}

diff --git a/apps/dashboard/src/pages/TeamPage.tsx b/apps/dashboard/src/pages/TeamPage.tsx index f4a7758..756efa3 100644 --- a/apps/dashboard/src/pages/TeamPage.tsx +++ b/apps/dashboard/src/pages/TeamPage.tsx @@ -478,7 +478,10 @@ export default function TeamPage() { assignRoleMutation.mutate({ admin_id: formData.get("admin_id") as string, role_id: formData.get("role_id") as string, - project_id: (formData.get("project_id") as string) || undefined, + project_id: + (formData.get("project_id") as string) === "__global__" + ? undefined + : (formData.get("project_id") as string) || undefined, }); }} > @@ -520,7 +523,7 @@ export default function TeamPage() { - Global (no scope) + Global (no scope) {projectsData?.projects?.map((p) => ( {p.name} diff --git a/apps/dashboard/src/pages/projects/ProjectLayout.tsx b/apps/dashboard/src/pages/projects/ProjectLayout.tsx index 66c5969..1b98cb2 100644 --- a/apps/dashboard/src/pages/projects/ProjectLayout.tsx +++ b/apps/dashboard/src/pages/projects/ProjectLayout.tsx @@ -60,7 +60,9 @@ export default function ProjectLayout() { ]; const currentPath = location.pathname; - const activeTab = tabs.find((tab) => currentPath.startsWith(tab.href))?.value ?? "overview"; + const activeTab = + [...tabs].sort((a, b) => b.href.length - a.href.length).find((tab) => currentPath.startsWith(tab.href)) + ?.value ?? "overview"; return (
diff --git a/apps/dashboard/src/pages/settings/InngestDashboardPage.tsx b/apps/dashboard/src/pages/settings/InngestDashboardPage.tsx index c72250b..fc76343 100644 --- a/apps/dashboard/src/pages/settings/InngestDashboardPage.tsx +++ b/apps/dashboard/src/pages/settings/InngestDashboardPage.tsx @@ -2,6 +2,8 @@ import { PageHeader } from "@/components/ui/PageHeader"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; import { Table, TableBody, @@ -10,6 +12,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; +import { api } from "@/lib/api"; import { type InngestFunction, type InngestRun, @@ -24,6 +27,8 @@ import { Clock, Loader2, PlayCircle, + Save, + Settings, XCircle, } from "lucide-react"; import { useState } from "react"; @@ -62,6 +67,12 @@ export default function InngestDashboardPage() { const queryClient = useQueryClient(); const [selectedFunction, setSelectedFunction] = useState(null); const [runStatusFilter, setRunStatusFilter] = useState(""); + const [showConfig, setShowConfig] = useState(false); + const [configForm, setConfigForm] = useState({ + inngest_api_key: "", + inngest_env_id: "", + inngest_base_url: "", + }); // Connection status const { @@ -108,6 +119,23 @@ export default function InngestDashboardPage() { onError: (err: any) => toast.error(err.message ?? "Failed to cancel run"), }); + // Save config mutation + const saveConfigMutation = useMutation({ + mutationFn: (data: { + inngest_api_key?: string; + inngest_env_id?: string; + inngest_base_url?: string; + }) => api.patch("/admin/instance", data), + onSuccess: () => { + toast.success("Inngest configuration saved"); + setShowConfig(false); + refetchStatus(); + }, + onError: (err: any) => toast.error(err.message ?? "Failed to save configuration"), + }); + + const isConnected = status?.status === "connected"; + return (
@@ -126,7 +154,7 @@ export default function InngestDashboardPage() { {statusLoading ? ( - ) : status?.status === "connected" ? ( + ) : isConnected ? (
@@ -134,14 +162,93 @@ export default function InngestDashboardPage() {
) : ( -
- - - {status?.error ?? "Unable to connect to Inngest"} - - +
+
+ + + {status?.error ?? "Unable to connect to Inngest"} + + +
+ {!showConfig && ( + + )} + {showConfig && ( +
+
+ + + setConfigForm((f) => ({ ...f, inngest_api_key: e.target.value })) + } + /> +
+
+ + + setConfigForm((f) => ({ ...f, inngest_env_id: e.target.value })) + } + /> +
+
+ + + setConfigForm((f) => ({ ...f, inngest_base_url: e.target.value })) + } + /> +

+ Leave empty for Inngest Cloud. Use http://localhost:8288 for local dev + server. +

+
+
+ + +
+
+ )}
)} diff --git a/bun.lock b/bun.lock index d0a30e5..f7b0899 100644 --- a/bun.lock +++ b/bun.lock @@ -59,6 +59,15 @@ "vite": "^6.0.6", }, }, + "apps/test-project": { + "name": "test-project", + "version": "0.1.0", + "dependencies": { + "@betterbase/client": "workspace:*", + "@betterbase/core": "workspace:*", + "hono": "^4.0.0", + }, + }, "packages/cli": { "name": "@betterbase/cli", "version": "0.1.0", @@ -574,7 +583,7 @@ "@neon-rs/load": ["@neon-rs/load@0.0.4", "", {}, "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw=="], - "@neondatabase/serverless": ["@neondatabase/serverless@1.0.2", "", { "dependencies": { "@types/node": "^22.15.30", "@types/pg": "^8.8.0" } }, "sha512-I5sbpSIAHiB+b6UttofhrN/UJXII+4tZPAq1qugzwCwLIL8EZLV7F/JyHUrEIiGgQpEXzpnjlJ+zwcEhheGvCw=="], + "@neondatabase/serverless": ["@neondatabase/serverless@1.1.0", "", {}, "sha512-r3ZZhRjEcfEdKIZnoB1RusNgvHuaBRqfCzV4Gi+5A9yUX0S4HTws/ASWqt13wL4y4I+0rqsWGdA2w7EQXHi3+Q=="], "@next/env": ["@next/env@15.5.12", "", {}, "sha512-pUvdJN1on574wQHjaBfNGDt9Mz5utDSZFsIIQkMzPgNS8ZvT4H2mwOrOIClwsQOb6EGx5M76/CZr6G8i6pSpLg=="], @@ -1114,7 +1123,7 @@ "@types/mysql": ["@types/mysql@2.15.27", "", { "dependencies": { "@types/node": "*" } }, "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA=="], - "@types/node": ["@types/node@22.19.11", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w=="], + "@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="], "@types/nodemailer": ["@types/nodemailer@6.4.23", "", { "dependencies": { "@types/node": "*" } }, "sha512-aFV3/NsYFLSx9mbb5gtirBSXJnAlrusoKNuPbxsASWc7vrKLmIrTQRpdcxNcSFL3VW2A2XpeLEavwb2qMi6nlQ=="], @@ -1670,6 +1679,8 @@ "temporal-spec": ["temporal-spec@0.2.4", "", {}, "sha512-lDMFv4nKQrSjlkHKAlHVqKrBG4DyFfa9F74cmBZ3Iy3ed8yvWnlWSIdi4IKfSqwmazAohBNwiN64qGx4y5Q3IQ=="], + "test-project": ["test-project@workspace:apps/test-project"], + "thread-stream": ["thread-stream@2.7.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw=="], "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], @@ -1702,7 +1713,7 @@ "ulid": ["ulid@2.4.0", "", { "bin": { "ulid": "bin/cli.js" } }, "sha512-fIRiVTJNcSRmXKPZtGzFQv9WRrZ3M9eoptl/teFJvjOzmpU+/K/JH6HZ8deBfb5vMEpicJcLn7JmvdknlMq7Zg=="], - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], @@ -1768,7 +1779,13 @@ "@better-auth/core/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - "@betterbase/core/@libsql/client": ["@libsql/client@0.17.2", "", { "dependencies": { "@libsql/core": "^0.17.2", "@libsql/hrana-client": "^0.9.0", "js-base64": "^3.7.5", "libsql": "^0.5.28", "promise-limit": "^2.7.0" } }, "sha512-0aw0S3iQMHvOxfRt5j1atoCCPMT3gjsB2PS8/uxSM1DcDn39xqz6RlgSMxtP8I3JsxIXAFuw7S41baLEw0Zi+Q=="], + "@betterbase/core/@libsql/client": ["@libsql/client@0.17.3", "", { "dependencies": { "@libsql/core": "^0.17.3", "@libsql/hrana-client": "^0.10.0", "js-base64": "^3.7.5", "libsql": "^0.5.28", "promise-limit": "^2.7.0" } }, "sha512-HXk9wiAoJbKFbyBH4O+aEhN6ir5ERXuXvwE5OD2eR4/5RUa3Pw/8L9zrnVdU+iNJitRvisPWaIwmhkO3bH7giA=="], + + "@betterbase/core/@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], + + "@betterbase/core/postgres": ["postgres@3.4.9", "", {}, "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw=="], + + "@betterbase/shared/@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], @@ -1778,8 +1795,12 @@ "@graphql-tools/schema/@graphql-tools/utils": ["@graphql-tools/utils@11.0.0", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@whatwg-node/promise-helpers": "^1.0.0", "cross-inspect": "1.0.1", "tslib": "^2.4.0" }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-bM1HeZdXA2C3LSIeLOnH/bcqSgbQgKEDrjxODjqi3y58xai2TkNrtYcQSoWzGbt9VMN1dORGjR7Vem8SPnUFQA=="], + "@inngest/ai/@types/node": ["@types/node@22.19.11", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w=="], + "@inquirer/core/@inquirer/type": ["@inquirer/type@2.0.0", "", { "dependencies": { "mute-stream": "^1.0.0" } }, "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag=="], + "@inquirer/core/@types/node": ["@types/node@22.19.11", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w=="], + "@inquirer/core/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "@opentelemetry/instrumentation-pg/@types/pg": ["@types/pg@8.15.6", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ=="], @@ -1824,26 +1845,6 @@ "@traceloop/instrumentation-anthropic/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.203.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.203.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-ke1qyM+3AK2zPuBPb6Hk/GCsc5ewbLvPNkEuELx/JmANeEp6ZjnZ+wypPAJSucTw0wvCGrUaibDSdcrGFoWxKQ=="], - "@types/bunyan/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="], - - "@types/connect/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="], - - "@types/memcached/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="], - - "@types/mute-stream/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="], - - "@types/mysql/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="], - - "@types/nodemailer/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="], - - "@types/oracledb/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="], - - "@types/pg/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="], - - "@types/tedious/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="], - - "@types/ws/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="], - "better-auth/jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], "better-auth/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], @@ -1852,8 +1853,6 @@ "betterbase-base-template/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - "bun-types/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="], - "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], @@ -1882,8 +1881,6 @@ "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - "protobufjs/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="], - "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -1894,10 +1891,16 @@ "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], - "@betterbase/core/@libsql/client/@libsql/core": ["@libsql/core@0.17.2", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-L8qv12HZ/jRBcETVR3rscP0uHNxh+K3EABSde6scCw7zfOdiLqO3MAkJaeE1WovPsjXzsN/JBoZED4+7EZVT3g=="], + "@betterbase/core/@libsql/client/@libsql/core": ["@libsql/core@0.17.3", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-2UjK1i7JBkMduJo4WdvvBxMMvVJ31pArBZNONyz/GCJJAH+1UHat2X6vn10S/WpY5fKzIT98WqYFl2vzWRLOfg=="], + + "@betterbase/core/@libsql/client/@libsql/hrana-client": ["@libsql/hrana-client@0.10.0", "", { "dependencies": { "@libsql/isomorphic-ws": "^0.1.5", "js-base64": "^3.7.5" } }, "sha512-OoA4EMqRAC7kn7V2P6EQqRcpZf2W+AjsNIyCizBg339Tq/aMC7sRnzs3SklderhmQWAqEzvv8A2vhxVmWpkVvw=="], "@betterbase/core/@libsql/client/libsql": ["libsql@0.5.28", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.5.28", "@libsql/darwin-x64": "0.5.28", "@libsql/linux-arm-gnueabihf": "0.5.28", "@libsql/linux-arm-musleabihf": "0.5.28", "@libsql/linux-arm64-gnu": "0.5.28", "@libsql/linux-arm64-musl": "0.5.28", "@libsql/linux-x64-gnu": "0.5.28", "@libsql/linux-x64-musl": "0.5.28", "@libsql/win32-x64-msvc": "0.5.28" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "arm", "x64", "arm64", ] }, "sha512-wKqx9FgtPcKHdPfR/Kfm0gejsnbuf8zV+ESPmltFvsq5uXwdeN9fsWn611DmqrdXj1e94NkARcMA2f1syiAqOg=="], + "@betterbase/core/@types/bun/bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], + + "@betterbase/shared/@types/bun/bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], @@ -1942,9 +1945,11 @@ "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], - "@inquirer/core/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "@inngest/ai/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - "@opentelemetry/instrumentation-pg/@types/pg/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="], + "@inquirer/core/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "@inquirer/core/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "@opentelemetry/instrumentation-pg/@types/pg/pg-protocol": ["pg-protocol@1.13.0", "", {}, "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w=="], @@ -1954,30 +1959,8 @@ "@traceloop/instrumentation-anthropic/@opentelemetry/instrumentation/require-in-the-middle": ["require-in-the-middle@7.5.2", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3", "resolve": "^1.22.8" } }, "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ=="], - "@types/bunyan/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - - "@types/connect/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - - "@types/memcached/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - - "@types/mute-stream/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - - "@types/mysql/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - - "@types/nodemailer/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - - "@types/oracledb/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - - "@types/pg/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - - "@types/tedious/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - - "@types/ws/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - "betterbase-base-template/@types/bun/bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="], - "bun-types/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "inngest/strip-ansi/ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="], @@ -2024,8 +2007,6 @@ "ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], - "protobufjs/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - "string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -2056,12 +2037,6 @@ "@betterbase/core/@libsql/client/libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="], - "@opentelemetry/instrumentation-pg/@types/pg/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - "@traceloop/instrumentation-anthropic/@opentelemetry/instrumentation/import-in-the-middle/cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="], - - "betterbase-base-template/@types/bun/bun-types/@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="], - - "betterbase-base-template/@types/bun/bun-types/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], } } diff --git a/check-admin.ts b/check-admin.ts new file mode 100644 index 0000000..fd069cd --- /dev/null +++ b/check-admin.ts @@ -0,0 +1,10 @@ +import { Client } from 'pg' + +const client = new Client({ + connectionString: 'postgresql://neondb_owner:npg_NDrg3StRE4jY@ep-still-thunder-an4tpncc-pooler.c-6.us-east-1.aws.neon.tech/neondb?sslmode=require&channel_binding=require' +}) + +await client.connect() +const res = await client.query("SELECT id, email FROM betterbase_meta.admin_users") +console.log(JSON.stringify(res.rows, null, 2)) +await client.end() diff --git a/check-hash.ts b/check-hash.ts new file mode 100644 index 0000000..4e4b269 --- /dev/null +++ b/check-hash.ts @@ -0,0 +1,10 @@ +import { Client } from 'pg' + +const client = new Client({ + connectionString: 'postgresql://neondb_owner:npg_NDrg3StRE4jY@ep-still-thunder-an4tpncc-pooler.c-6.us-east-1.aws.neon.tech/neondb?sslmode=require&channel_binding=require' +}) + +await client.connect() +const res = await client.query("SELECT password_hash FROM betterbase_meta.admin_users WHERE email = 'admin@example.com'") +console.log('Stored hash:', res.rows[0]?.password_hash) +await client.end() diff --git a/packages/cli/src/commands/login.ts b/packages/cli/src/commands/login.ts index b1b845c..33f832a 100644 --- a/packages/cli/src/commands/login.ts +++ b/packages/cli/src/commands/login.ts @@ -68,12 +68,27 @@ export async function runLoginCommand(opts: { serverUrl?: string } = {}) { process.exit(1); } + const fullVerificationUri = `${verificationUri}?code=${userCode}`; + keyValue("Instance", serverUrl); keyValue("Your code", chalk.bold(chalk.yellow(userCode))); blank(); - console.log(` ${chalk.dim("Open:")} ${chalk.cyan(`${verificationUri}?code=${userCode}`)}`); + console.log(` ${chalk.dim("Open:")} ${chalk.cyan(fullVerificationUri)}`); blank(); - console.log(chalk.dim(" Waiting for browser authorization") + chalk.dim(" (5 min timeout)...")); + + // Try to open the browser automatically + try { + if (process.platform === "darwin") { + await Bun.spawn(["open", fullVerificationUri]); + } else if (process.platform === "win32") { + await Bun.spawn(["cmd", "/c", "start", fullVerificationUri]); + } else { + await Bun.spawn(["xdg-open", fullVerificationUri]); + } + console.log(chalk.dim(" Browser opened. Waiting for authorization...")); + } catch { + console.log(chalk.dim(" Waiting for browser authorization") + chalk.dim(" (5 min timeout)...")); + } // Step 2: Poll for token const deadline = Date.now() + POLL_TIMEOUT_MS; diff --git a/packages/server/src/routes/admin/inngest.ts b/packages/server/src/routes/admin/inngest.ts index aaf4e4e..56be886 100644 --- a/packages/server/src/routes/admin/inngest.ts +++ b/packages/server/src/routes/admin/inngest.ts @@ -3,8 +3,17 @@ import { getPool } from "../../lib/db"; export const inngestAdminRoutes = new Hono(); -const getInngestBaseUrl = (): string => { - return process.env.INNGEST_BASE_URL ?? "https://api.inngest.com"; +const getInngestBaseUrl = async (): Promise => { + const envUrl = process.env.INNGEST_BASE_URL; + if (envUrl) return envUrl; + + const pool = getPool(); + const { rows } = await pool.query( + "SELECT value FROM betterbase_meta.instance_settings WHERE key = 'inngest_base_url'", + ); + const storedValue = rows[0]?.value; + const url = typeof storedValue === "string" ? storedValue : (storedValue?.value ?? null); + return url || "https://api.inngest.com"; }; const getInngestHeaders = async (): Promise => { @@ -33,8 +42,8 @@ const getInngestEnv = async (): Promise => { return typeof storedValue === "string" ? storedValue : (storedValue?.value ?? null); }; -const isSelfHosted = (): boolean => { - const baseUrl = getInngestBaseUrl(); +const isSelfHosted = async (): Promise => { + const baseUrl = await getInngestBaseUrl(); return baseUrl !== "https://api.inngest.com"; }; @@ -51,9 +60,9 @@ const fetchWithErrorCheck = async (url: string, options?: RequestInit) => { // GET /admin/inngest/status — Check Inngest connection status inngestAdminRoutes.get("/status", async (c) => { try { - const baseUrl = getInngestBaseUrl(); + const baseUrl = await getInngestBaseUrl(); - if (isSelfHosted()) { + if (await isSelfHosted()) { const res = await fetch(`${baseUrl}/health`); const healthy = res.ok; @@ -84,11 +93,11 @@ inngestAdminRoutes.get("/status", async (c) => { // GET /admin/inngest/functions — List all registered functions inngestAdminRoutes.get("/functions", async (c) => { try { - const baseUrl = getInngestBaseUrl(); + const baseUrl = await getInngestBaseUrl(); const headers = await getInngestHeaders(); const envId = await getInngestEnv(); - if (isSelfHosted()) { + if (await isSelfHosted()) { // Self-hosted Inngest has different API structure // Return local functions from inngest.ts const { allInngestFunctions } = await import("../../lib/inngest"); @@ -121,7 +130,7 @@ inngestAdminRoutes.get("/functions", async (c) => { inngestAdminRoutes.get("/functions/:id/runs", async (c) => { try { const functionId = c.req.param("id"); - const baseUrl = getInngestBaseUrl(); + const baseUrl = await getInngestBaseUrl(); const headers = await getInngestHeaders(); const envId = await getInngestEnv(); @@ -131,7 +140,7 @@ inngestAdminRoutes.get("/functions/:id/runs", async (c) => { const params = new URLSearchParams({ limit: String(limit) }); if (status) params.append("status", status); - if (isSelfHosted()) { + if (await isSelfHosted()) { // Self-hosted: query from database webhook_deliveries by webhook_id // Note: functionId in routes refers to webhook ID for webhook deliveries const pool = getPool(); @@ -174,11 +183,11 @@ inngestAdminRoutes.get("/functions/:id/runs", async (c) => { inngestAdminRoutes.get("/runs/:runId", async (c) => { try { const runId = c.req.param("runId"); - const baseUrl = getInngestBaseUrl(); + const baseUrl = await getInngestBaseUrl(); const headers = await getInngestHeaders(); const envId = await getInngestEnv(); - if (isSelfHosted()) { + if (await isSelfHosted()) { // Self-hosted: get from database const pool = getPool(); const { rows } = await pool.query( @@ -306,11 +315,11 @@ inngestAdminRoutes.post("/functions/:id/test", async (c) => { inngestAdminRoutes.post("/runs/:runId/cancel", async (c) => { try { const runId = c.req.param("runId"); - const baseUrl = getInngestBaseUrl(); + const baseUrl = await getInngestBaseUrl(); const headers = await getInngestHeaders(); const envId = await getInngestEnv(); - if (isSelfHosted()) { + if (await isSelfHosted()) { // Self-hosted: cannot cancel (webhooks are synchronous from DB perspective) return c.json( { diff --git a/packages/server/src/routes/admin/instance.ts b/packages/server/src/routes/admin/instance.ts index 9468cf4..58b891c 100644 --- a/packages/server/src/routes/admin/instance.ts +++ b/packages/server/src/routes/admin/instance.ts @@ -31,6 +31,9 @@ instanceRoutes.patch( require_email_verification: z.boolean().optional(), ip_allowlist: z.array(z.string()).optional(), cors_origins: z.array(z.string().url()).optional(), + inngest_api_key: z.string().optional(), + inngest_env_id: z.string().optional(), + inngest_base_url: z.string().url().optional().or(z.literal("")), }), ), async (c) => { diff --git a/reset-password.ts b/reset-password.ts new file mode 100644 index 0000000..54e9ce7 --- /dev/null +++ b/reset-password.ts @@ -0,0 +1,22 @@ +import { Client } from 'pg' +import bcrypt from 'bcryptjs' + +const client = new Client({ + connectionString: 'postgresql://neondb_owner:npg_NDrg3StRE4jY@ep-still-thunder-an4tpncc-pooler.c-6.us-east-1.aws.neon.tech/neondb?sslmode=require&channel_binding=require' +}) + +await client.connect() + +const newPassword = 'AdminPass123!' +const hash = await bcrypt.hash(newPassword, 12) + +await client.query(` + UPDATE betterbase_meta.admin_users + SET password_hash = $1 + WHERE email = 'admin@example.com' +`, [hash]) + +console.log('Password updated to:', newPassword) +console.log('Hash:', hash) + +await client.end()