2026-04-13/Ben Severn

infermap Now Runs in TypeScript: Schema Mapping on the Edge

F1 0.840 on 162 benchmark cases — infermap's seven-scorer schema mapping engine ships on npm with zero runtime dependencies. Runs in Edge Functions, Workers, and the browser.

infermaptypescriptschema-mappingopen-sourcenpmedge-functionsdata-engineeringetl

You get a CSV from a vendor. The columns are fname, lname, tel, addr1. Your database expects first_name, last_name, phone, street_address. You write a mapping dict, ship it, and move on — until the next vendor sends givenName, surname, mobile, address_line_1.

infermap solves this by inferring column mappings automatically. You hand it a source schema and a target schema, and it returns the best 1:1 field assignment with confidence scores and per-scorer reasoning. No hardcoded synonyms. No manual mapping files. It shipped on PyPI in March 2026 and has been running in production Python pipelines since.

Today it's available on npm as a full TypeScript port — feature parity with the Python version, same algorithm, same accuracy. This post covers what changed, what stayed the same, and why a TypeScript infermap opens doors that Python alone couldn't.

What infermap Does in 30 Seconds

infermap runs a multi-scorer pipeline to match source fields to target fields:

  1. Schema extraction — reads columns from CSVs, JSON, database tables, or in-memory records. Infers dtypes, null rates, cardinality, and samples values.
  2. Common-prefix stripping — if every source column starts with prospect_, it strips the prefix before matching so prospect_city matches city.
  3. Score matrix — for each (source, target) pair, seven independent scorers produce a score and a reasoning string. Results are combined via weighted average (minimum 2 contributors).
  4. Optimal assignment — the Hungarian algorithm finds the minimum-cost 1:1 matching across the full M x N matrix.
  5. Confidence filtering — assignments below the threshold (default 0.2) are dropped.

The result: a list of mappings, each with a confidence score and explainable reasoning from every scorer that contributed.

The Seven Scorers

ScorerWeightWhat It Does
ExactScorer1.0Case-insensitive exact name match
AliasScorer0.95Known synonyms — fname to first_name, extensible via config and domain dictionaries
LLMScorer0.8Pluggable LLM-backed scoring (stubbed by default)
InitialismScorer0.75Matches abbreviations via dynamic programming — ASSI to assay_id, CONSC to confidence_score
PatternTypeScorer0.7Semantic type detection from sample values — email, UUID, ISO date, phone, URL, IP, ZIP, currency
ProfileScorer0.5Statistical similarity — dtype, null rate, unique rate, value length, cardinality
FuzzyNameScorer0.4Jaro-Winkler string similarity on normalized names

The combination of name-based, value-based, and statistical scorers means infermap handles cases where any single approach fails. A column named col_7 won't match on name — but if every value is an email address, PatternTypeScorer catches it. A column named patient_identifier won't fuzzy-match mrn — but AliasScorer with the healthcare domain dictionary will.

What the TypeScript Port Looks Like

import { map } from "infermap";

const result = map(
  { records: vendorData, sourceName: "vendor" },
  { records: internalSchema, sourceName: "internal" }
);

for (const m of result.mappings) {
  console.log(`${m.source} -> ${m.target}  (${(m.confidence * 100).toFixed(0)}%)`);
}
// fname -> first_name  (98%)
// lname -> last_name   (98%)
// tel   -> phone       (91%)
// addr1 -> street_address (76%)

The API mirrors Python's. If you've used infermap in a Python pipeline, the TypeScript version feels identical — same function names, same config structure, same output shape.

Database support

import { mapFromDb } from "infermap/node";

// Map columns between two Postgres tables
const result = await mapFromDb(
  { uri: "postgresql://localhost/warehouse", table: "raw_imports" },
  { uri: "postgresql://localhost/warehouse", table: "canonical_customers" }
);

SQLite, PostgreSQL, and DuckDB are supported as optional peer dependencies — install only what you need.

Custom scorers

import { defineScorer, makeScorerResult, MapEngine, defaultScorers } from "infermap";

const domainScorer = defineScorer(
  "DomainScorer",
  (source, target) => {
    if (source.name.includes("price") && target.name.includes("amount")) {
      return makeScorerResult(0.85, "price/amount semantic match");
    }
    return null; // abstain
  },
  0.7
);

const engine = new MapEngine({
  scorers: [...defaultScorers(), domainScorer],
});

Config persistence

// Save a mapping for reuse — no re-inference needed
result.saveConfig("vendor_to_internal.json");

// Later: load and apply
import { applyConfig } from "infermap";
const renamed = applyConfig(newData, "vendor_to_internal.json");

Same Algorithm, Same Accuracy

The Python and TypeScript versions share a 162-case benchmark corpus — 82 cases from the Valentine schema matching benchmark plus 80 synthetic cases. Both implementations produce results within 0.0005 F1 of each other on the shared corpus.

MetricPythonTypeScript
Overall F10.8400.840
Valentine corpus (82 cases)0.7940.794
ChEMBL subset (25 cases)0.8190.819
Calibrated ECE0.0050.005

The Hungarian algorithm implementation is vendored in TypeScript (O(n^3) — no scipy dependency), and every scorer was ported line-by-line with matching test cases. 186 TypeScript tests verify parity.

Zero Runtime Dependencies in Core

The TypeScript core has zero runtime dependencies. No lodash, no heavy string libraries, no Node.js built-ins. The Jaro-Winkler implementation, Hungarian algorithm, and pattern matchers are all self-contained.

This matters because it means infermap runs anywhere JavaScript runs:

Node.js-specific features (file system access, database providers) are isolated in the infermap/node entrypoint. The core infermap import is edge-safe.

Doors This Opens

A Python-only infermap was useful for batch ETL jobs and data pipelines. A TypeScript infermap changes what's architecturally possible.

Upload-time schema resolution

When a user uploads a CSV to your web app, you can map their columns to your schema before the data ever hits your backend. Run infermap client-side or in an edge function, show the user the proposed mapping with confidence scores, let them confirm or override, then send the already-mapped data to your API.

// In a Next.js API route or edge function
import { map } from "infermap";

export async function POST(req: Request) {
  const { headers, sample } = await req.json();

  const result = map(
    { records: sample, sourceName: "upload" },
    { fields: TARGET_SCHEMA, sourceName: "canonical" }
  );

  // Return proposed mapping for user confirmation
  return Response.json({
    mappings: result.mappings,
    unmapped: result.unmapped_source,
    confidence: result.mappings.map((m) => m.confidence),
  });
}

No round-trip to a Python service. No cold-starting a container. The mapping happens at the edge in milliseconds.

Full-stack type safety

With TypeScript, the mapping result is fully typed. Your IDE autocompletes field names, catches typos at compile time, and your CI pipeline verifies the mapping config matches your schema types. Python's infermap returns dicts — TypeScript's returns typed objects that integrate with your existing type system.

Monorepo workflows

If your backend is Node.js or TypeScript, infermap slots into your existing build pipeline. No Python runtime to install in CI. No virtualenv to manage. One npm install infermap and you're done.

Browser-based mapping UIs

The score matrix that infermap computes — the full M x N grid of (source, target, confidence) tuples — is exposed via the API. You can render it as an interactive mapping UI where users see every candidate match, the confidence score, and the per-scorer reasoning. The zero-dependency core means this runs in the browser without bundling a runtime.

Shared config between Python and TypeScript

infermap configs are serialized as JSON (TypeScript) or YAML (Python). A mapping you infer in a Python notebook can be loaded and applied in a TypeScript API route, and vice versa. Teams that use Python for data science and TypeScript for production APIs can share mapping definitions without translation.

What Stayed the Same

Everything that matters:

Install and Try It

npm install infermap

Or with database support:

npm install infermap better-sqlite3  # SQLite
npm install infermap pg              # PostgreSQL
npm install infermap @duckdb/node-api # DuckDB

The GitHub repo has examples in examples/typescript/ and the full benchmark suite. The Python version is still on PyPI (pip install infermap) and both are maintained in the same monorepo with shared golden tests.

Key Takeaways

Schema mapping shouldn't require a Python service, a container, and a five-second cold start. Now it doesn't. npm install infermap and map your first schema in under a minute.