2026-04-13/Ben Severn

GoldenCheck Now Runs in TypeScript: Zero-Config Data Validation at the Edge

GoldenCheck's 10 profilers, drift detection, and confidence scoring ship on npm with an edge-safe core. DQBench 88.40 — now in your browser, Workers, and Node.js.

goldenchecktypescriptdata-qualityopen-sourcenpmedge-functions

Most data validation tools start with a blank YAML file and ask you to write rules. How many nulls are acceptable in email? What's the valid range for age? Is status an enum? You don't know yet — you're staring at a CSV you've never seen before.

GoldenCheck flips the model. Hand it a file, and it discovers what's wrong — no rules, no config, no schema. Ten column-level profilers and four cross-column profilers run automatically, each producing findings with severity levels and confidence scores. You review what it found, pin the rules you care about, and export a goldencheck.yml for CI enforcement. Rules come from data, not guesswork.

On Python, GoldenCheck scores 88.40 on DQBench — zero-config, no hand-written rules. For context, Great Expectations scores 21.68 with best-effort rules. Pandera scores 32.51. The gap isn't close.

Today GoldenCheck ships on npm as a full TypeScript port. This post covers what it detects, how the port is structured, and what edge-safe data validation unlocks.

What GoldenCheck Catches

Column-Level Profilers (10 checks)

ProfilerWhat It Flags
Type InferenceString columns that are mostly numeric; numeric columns losing leading zeros
NullabilityRequired columns (0 nulls), optional columns, entirely-null columns
UniquenessPrimary key candidates, near-unique columns with sneaky duplicates
Format DetectionEmails, phones, URLs — flags non-matching values in classified columns
Range & DistributionMin/max violations, outliers beyond 3 standard deviations
CardinalityLow-cardinality enum candidates (<20 unique values)
Pattern ConsistencyMixed structural patterns — XXX-DDDD vs DDDDDDDDDD in the same column
Encoding DetectionMojibake, zero-width Unicode, smart quotes, control characters
Sequence DetectionGaps in auto-increment IDs, broken integer sequences
Drift DetectionTemporal distribution shifts within a single column

Cross-Column Profilers (4 checks)

ProfilerExample
Temporal Ordersignup_date after last_login
Null Correlationaddress, city, and zip always null together
Numeric Cross-Columnclaim_amount exceeds policy_max
Age vs DOBage = 30 but date_of_birth = 1980-01-01

Every finding gets a confidence score from 0.0 to 1.0. If two profilers flag the same column, the confidence gets a corroboration boost. Low-confidence findings are automatically demoted to INFO severity — no noise in your error list.

The TypeScript Port

Edge-safe core, Node extras separate

The package ships with two entry points:

// Edge-safe — works in browsers and Workers
import { scanData, TabularData } from "goldencheck/core";

const data = new TabularData([
  { email: "alice@example.com", age: 30, status: "active" },
  { email: "bad@",              age: -5, status: "active" },
  { email: null,                age: 25, status: "pending" },
  { email: "bob@company.org",   age: 200, status: "unknown" },
]);

const { findings, profile } = scanData(data);

for (const f of findings) {
  console.log(`[${f.severity}] ${f.column}: ${f.message}`);
}
// [ERROR]   email: 1 value does not match email format (row 2: "bad@")
// [WARNING] age: value -5 is below inferred minimum 0
// [WARNING] age: value 200 exceeds 3 standard deviations from the mean
// [WARNING] status: "unknown" not in inferred enum [active, pending]

No rules written. No config file. Four findings from four lines of data.

Node.js file scanning

import { readFile, scanData } from "goldencheck/node";

const data = readFile("customers.csv");
const { findings } = scanData(data);

const errors = findings.filter((f) => f.severity === "ERROR");
if (errors.length > 0) {
  console.error(`${errors.length} errors found`);
  process.exit(1);
}

Baseline and drift detection

GoldenCheck's baseline system captures what "healthy" data looks like using six independent techniques — statistical profiles, constraint mining, semantic types, correlations, pattern grammars, and confidence priors. Once you have a baseline, every subsequent scan detects drift across 13 dimensions.

import { createBaseline, serializeBaseline } from "goldencheck/node";
import { runDriftChecks, deserializeBaseline } from "goldencheck";
import { readFileSync, writeFileSync } from "node:fs";

// Learn from your reference data
const baseline = createBaseline(readFile("reference_data.csv"));
writeFileSync("baseline.json", serializeBaseline(baseline));

// Later: check production data for drift
const saved = deserializeBaseline(readFileSync("baseline.json", "utf-8"));
const driftFindings = runDriftChecks(readFile("production_export.csv"), saved);

for (const f of driftFindings) {
  console.log(`${f.check}: ${f.message}`);
}
// distribution_drift: age distribution has shifted (KS p=0.002)
// new_pattern: phone column has new pattern "DDDDDDDDDD" (was "DDD-DDD-DDDD")
// bound_violation: salary max 450000 exceeds historical max 280000

The 13 drift check types: distribution shift (KS-test), entropy drift, bound violations, Benford's Law deviation, functional dependency breaks, key uniqueness loss, temporal order drift, type drift, correlation breaks, new correlations, pattern drift, and new patterns.

Domain dictionaries

Domain packs add industry-specific semantic type detection:

import { scanData, readFile } from "goldencheck/node";

const { findings } = scanData(readFile("claims.csv"), { domain: "healthcare" });
// Now recognizes NPI, ICD codes, CPT codes, DRG, clinical notes, patient types

Three built-in domains: healthcare, finance, and ecommerce. Each adds 8-15 additional semantic types that the base classifier doesn't cover.

LLM boost

For an optional ~$0.01 per scan, GoldenCheck sends representative data samples to Claude or GPT-4o-mini for semantic analysis. The LLM catches things profilers can't — like 12345 in a name column, or an email field that should be required but has 2% nulls.

import { scanData, callLlm, parseLlmResponse, mergeLlmFindings, buildSampleBlocks } from "goldencheck";

const result = scanData(data, { returnSample: true });
const blocks = buildSampleBlocks(result.sample, result.findings);
const { text } = await callLlm("anthropic", JSON.stringify(blocks));
const llmResponse = parseLlmResponse(text);
if (llmResponse) {
  const enhanced = mergeLlmFindings(result.findings, llmResponse);
}

LLM findings merge into the existing confidence system — they don't replace profiler results, they augment them. Budget control via GOLDENCHECK_LLM_BUDGET keeps costs predictable.

Why This Architecture Matters

Upload-time validation

When a user uploads a CSV to your app, you can validate it before it reaches your backend. Run scanData in an edge function, return findings to the client, and let the user fix issues before the data enters your pipeline.

// Next.js Edge API route
import { scanData, TabularData } from "goldencheck/core";

export const runtime = "edge";

export async function POST(req: Request) {
  const { rows } = await req.json();
  const { findings } = scanData(new TabularData(rows));

  const errors = findings.filter((f) => f.severity === "ERROR");
  return Response.json({
    valid: errors.length === 0,
    findings: findings.map((f) => ({
      column: f.column,
      severity: f.severity,
      message: f.message,
      confidence: f.confidence,
    })),
  });
}

No Python service to cold-start. No container to manage. Validation runs at the edge in milliseconds.

CI/CD data gates

Combine zero-config discovery with config-based enforcement:

# First time: discover issues and save rules
npx goldencheck data.csv --json > findings.json
# Review, edit goldencheck.yml

# CI: enforce rules on every push
npx goldencheck validate data.csv --config goldencheck.yml
# Exit code 1 if any ERROR-level findings

Browser-based data previews

The zero-dependency core means you can run all 14 profilers client-side. Upload a CSV in the browser, scan it locally, and show the user a health score and findings table — no network round-trip, no data leaving the client's machine.

DQBench: How It Compares

DQBench tests three tiers of data quality issues with increasing difficulty. GoldenCheck runs zero-config — no rules written. Competitors use their best-effort hand-written rules.

ToolModeT1 F1T2 F1T3 F1DQBench Score
GoldenCheckzero-config94.1%90.9%83.0%88.40
Panderabest-effort rules36.4%38.1%25.0%32.51
Soda Corebest-effort rules38.1%23.5%13.3%22.36
Great Expectationsbest-effort rules36.4%23.5%12.5%21.68

The gap is largest on Tier 3 — adversarial issues like mixed encodings, broken sequences, and correlated nulls. These are the issues that rule-based tools miss because nobody writes rules for problems they don't know exist.

What's Ported, What's Not

Fully ported: all 14 profilers, semantic classifier with 3 domain packs, baseline system (6 techniques), drift detection (13 checks), LLM boost, confidence scoring, auto-fix, config validation, health scoring, MCP server, JSON/CI reporters.

Not yet ported: the interactive TUI (Textual in Python, would need Ink or a React-based equivalent), database scanning, REST API server, watch mode, scheduled runs. These are Node-only features that will come in future releases.

The core scanning engine — the part that actually finds issues — is at full parity.

Install and Try It

npm install goldencheck

Scan a file:

npx goldencheck data.csv --no-tui

Or in code:

import { scanData, TabularData } from "goldencheck/core";

const { findings } = scanData(new TabularData(yourData));

The GitHub repo has the full benchmark suite, 296 Python tests, and the DQBench comparison. The Python version is still on PyPI (pip install goldencheck) — both are maintained actively.

Key Takeaways

Data validation shouldn't require you to know every possible issue before you scan. GoldenCheck finds the issues first, then lets you decide which ones matter. Now it does that in TypeScript too.