@wilsong/queryguard
v0.4.0
Published
Drop-in observability layer for Supabase apps. Catch silent failures, group issues, detect regressions, and surface impact.
Downloads
320
Maintainers
Readme
QueryGuard
Drop-in observability layer for Supabase apps.
Catch the silent failures Supabase dashboards miss. Instrument queries, group issues, detect regressions, and surface impact — with zero configuration.
Why QueryGuard
Supabase is great. PostgREST is fast. But neither tells you when:
- An RLS policy silently returns 403 to a logged-in user
- A query is taking 4 seconds on the
/dashboardpage - A race condition causes
lesson_progressto return[]for 2% of users - An edge function started failing 3 hours after your last deploy
- A fix you shipped last week re-opened an issue you already resolved
QueryGuard instruments your Supabase client at the fetch layer, groups related failures into tracked issues, scores them by impact, and alerts on regressions — all without requiring changes to your query code.
What It Catches
| Signal | How |
|--------|-----|
| RLS 403s | Intercepts PostgREST responses, detects policy failures |
| Silent query failures | Captures 4xx/5xx before they reach your .then() |
| RPC failures | Monitors /rest/v1/rpc/ endpoint errors separately |
| Edge function errors | Tracks /functions/v1/ failures with function name |
| Auth failures | Monitors /auth/v1/ errors (skips expected 401s) |
| Slow queries | Flags requests exceeding configurable threshold (default 3s) |
| Empty result anomalies | Detects [] responses on tables that should have data |
| Client JS errors | window.onerror + unhandledrejection capture |
| React render errors | QueryGuardErrorBoundary component |
Architecture
QueryGuard is split into two layers:
packages/queryguard ← npm-installable SDK
├── src/
│ ├── types/ ← Canonical QGEvent model
│ ├── fingerprint/ ← Deterministic issue grouping
│ ├── redaction/ ← Secret/PII scrubbing pipeline
│ ├── normalize/ ← URL classification + event shaping
│ ├── transport/ ← Batching, retry, sendBeacon fallback
│ ├── client/ ← Browser guarded fetch + error capture
│ ├── server/ ← Server-side guarded fetch
│ └── react/ ← ErrorBoundary + ErrorLogger components
apps/dashboard (or your app)
├── src/app/api/error-log/ ← Ingestion endpoint
├── src/app/(admin)/errors/ ← Dashboard UI
└── src/lib/supabase/ ← Supabase client wrappersSDK → Ingestion Endpoint → Dashboard
The SDK captures events client/server-side, batches them, and ships to your /api/error-log endpoint. The endpoint groups them into issues, detects regressions, fires webhooks, and updates error budgets. The dashboard visualizes everything.
Installation
Option A: npm package (standalone)
npm install queryguard
# or
pnpm add queryguardOption B: Self-hosted dashboard (this repo)
The dashboard app in src/app/(admin)/admin/errors/ is the full-featured viewer. It runs inside your existing Next.js app — no separate deployment needed.
60-Second Setup
1. Initialize QueryGuard (client)
// src/lib/queryguard.ts
import { initQueryGuard } from "queryguard";
export const qg = initQueryGuard({
endpoint: "/api/error-log",
environment: process.env.NODE_ENV,
deploy_version: process.env.NEXT_PUBLIC_DEPLOY_SHA,
});2. Pass guardedFetch to your Supabase browser client
// src/lib/supabase/client.ts
import { createBrowserClient } from "@supabase/ssr";
import { qg } from "@/lib/queryguard";
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{ global: { fetch: qg.guardedFetch } }
);
}3. Pass serverGuardedFetch to your Supabase server client
// src/lib/supabase/server.ts
import { createServerClient } from "@supabase/ssr";
import { createServerTransport, createServerGuardedFetch } from "queryguard/server";
const transport = createServerTransport({ endpoint: "/api/error-log" });
const serverGuardedFetch = createServerGuardedFetch(transport, {});
export async function createClient() {
return createServerClient(url, key, {
global: { fetch: serverGuardedFetch },
// ...cookies
});
}4. Mount ErrorLogger in your root layout
// app/layout.tsx (client component or child of)
import { ErrorLogger } from "queryguard/react";
import { qg } from "@/lib/queryguard";
export default function Layout({ children }) {
return (
<>
<ErrorLogger transport={qg.transport} />
{children}
</>
);
}That's it. QueryGuard is now capturing all Supabase traffic, grouping issues, and making them visible in your dashboard at /admin/errors.
SDK API Reference
Core
| Export | Description |
|--------|-------------|
| initQueryGuard(config) | Initialize client-side QueryGuard. Returns a handle. |
| withQueryGuardSupabase(factory, config) | Convenience wrapper for Supabase client factories |
| captureException(error, transport, config) | Manually capture an exception |
| captureMessage(message, transport, config) | Manually capture a message |
Transport
| Export | Description |
|--------|-------------|
| BrowserTransport | Batching transport for browser environments |
| createBrowserTransport(config) | Factory for browser transport |
| createServerTransport(config) | Factory for server transport (no batching) |
Instrumentation
| Export | Description |
|--------|-------------|
| createGuardedFetch(transport, config) | Browser fetch wrapper |
| createServerGuardedFetch(transport, config) | Server fetch wrapper |
| setupGlobalErrorCapture(transport, config) | Install window error handlers |
| initBreadcrumbTracker() | Start capturing user event trail |
React
| Export | Description |
|--------|-------------|
| ErrorLogger | Mount-once component: wires error capture + canary |
| QueryGuardErrorBoundary | React error boundary with event capture |
Utilities
| Export | Description |
|--------|-------------|
| createFingerprint(...) | Generate deterministic issue fingerprint |
| normalizeRoute(path) | Collapse dynamic segments to :id/:slug |
| calculateImpactScore(...) | Score impact from page criticality + severity |
| redactString(value) | Redact sensitive strings |
| redactHeaders(headers) | Redact sensitive HTTP headers |
| redactUrl(url) | Remove token query params from URLs |
| redactObject(obj) | Deep-redact object fields |
Configuration
interface QGConfig {
endpoint?: string; // Default: "/api/error-log"
project_id?: string; // For multi-project support
environment?: string; // "production" | "development" | etc.
deploy_version?: string; // Git SHA or build ID
api_key?: string; // For hosted/SaaS mode
slow_query_threshold_ms?: number; // Default: 3000
expected_data_tables?: string[]; // Tables where [] is suspicious
page_criticality?: Record<string, number>; // Path → 1-10 score
batch_size?: number; // Default: 15
flush_delay_ms?: number; // Default: 2000
capture_breadcrumbs?: boolean; // Default: true
capture_global_errors?: boolean; // Default: true
enable_canary?: boolean; // Default: true
quiet?: boolean; // Suppress internal logs
}Fingerprinting & Issue Grouping
QueryGuard generates deterministic fingerprints from:
- Error category (
silent_query_failure,rpc_failure, etc.) - Entity (table name, RPC name, edge function name)
- HTTP status code
- Normalized page path (UUIDs/slugs collapsed to
:id/:slug) - First line of the Postgres error message
100 calls to the same broken RLS policy on the same route → 1 grouped issue, not 100 rows.
Impact Scoring
Every issue gets an impact score based on:
- Page criticality (configurable,
/dashboard/learn= 9,/admin= 3) - HTTP status severity (5xx = 3×, 403 = 2×, 400 = 1.5×)
- Error category (
silent_query_failure= 2×,slow_query= 1.5×)
Issues are sorted by impact in the dashboard — highest business impact first.
Redaction & Safety
QueryGuard never logs dangerous payloads. The redaction pipeline:
- Strips
Authorization,Cookie,apikey, and token headers - Redacts
password,token,api_key,secret, and similar body fields - Scrubs JWT patterns, Bearer tokens, Stripe/Supabase keys via regex
- Truncates body payloads at 1KB
- Truncates metadata values at 500 chars
- Handles circular references safely
- Never logs its own transport failures (loop guard)
Dashboard Features
The admin dashboard at /admin/errors includes:
- Issues — grouped, deduplicated, with regression detection
- Raw Logs — expandable with breadcrumb trail, stack trace, metadata
- Query Health — per-table failure/slow counts
- Analytics — MTTR, SLA compliance, severity distribution, error-by-page
- Error Budget — daily burn rate gauge
- 7-Day Trend — hourly error chart
- Config — webhook alerts (Slack/Discord), SLA settings, canary status
- Bulk actions — resolve, ignore, tag multiple issues
- CSV export — issues export for reporting
Trace Correlation (v0.1 foundations)
QueryGuard includes trace-ready fields in every event:
interface QGTraceContext {
trace_id?: string; // Request-scoped correlation ID
span_id?: string; // Span identifier
parent_span_id?: string;
}These allow linking: user action → fetch → API route → edge function → query event. Full OpenTelemetry compatibility is a future roadmap item.
Local Development
# Install dependencies
cd packages/queryguard
npm install
# Build
npm run build
# Type check
npm run typecheck
# Run tests
npm testRoadmap
- [ ] OpenTelemetry trace export
- [ ] Hosted SaaS ingestion endpoint (multi-project)
- [ ] Slack/Discord/PagerDuty alert integrations
- [ ] Auto-resolve stale issues (configurable TTL)
- [ ] Query performance regression detection across deploys
- [ ] Supabase Storage monitoring
- [ ] Realtime subscription error tracking
- [ ]
queryguard initCLI for zero-config setup
Contributing
- Fork and clone the repo
cd packages/queryguard && npm install- Make changes in
src/ - Run
npm testandnpm run typecheck - Submit a PR with a clear description
Please keep PRs focused. One change per PR.
License
MIT © QueryGuard Contributors
Built to catch what Supabase dashboards miss.
