ProvenPin Install

Superstruct structs with coercion, defaults and refinements: working versions (tested 2026-10-15)

Validate and coerce untrusted input with Superstruct 2: string to number coercion, defaulted fields, custom refinements, Infer types and StructError failure paths.

passed4 passed · 0 failed2026-10-15 17:11 UTC
Status
passed
Last passed
2026-10-15 17:11 UTC
Tests
4 / 4
Language
TypeScript
Runtime
node v22.23.3
pinned versions3
superstruct@2.0.2@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false
npm test
test environment
node v22.23.3
linux 6.17.0-1022-azure
x64
runner: github-actions 38775053802

The code

Exactly the files that passed. Your agent can pull them with get_recipe("superstruct-coercion-validation") (install), or see them on GitHub.

package.json
{
  "name": "superstruct-coercion-validation",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "superstruct": "2.0.2"
  },
  "devDependencies": {
    "@types/node": "26.6.5",
    "tsx": "4.23.15"
  }
}
src/order.ts
import {
  array, assert, coerce, create, defaulted, enums, integer, is, min, object, optional,
  refine, size, string, number, trimmed, StructError, type Infer,
} from "superstruct";

const Email = refine(trimmed(string()), "email", (v) => /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(v) || `"${v}" is not an email`);
const IntFromString = coerce(integer(), string(), (v) => Number.parseInt(v, 10));
const NumFromString = coerce(number(), string(), (v) => Number(v));

export const LineItem = object({
  sku: size(string(), 3, 20),
  qty: min(IntFromString, 1),
  price: min(NumFromString, 0),
});

export const Order = object({
  email: Email,
  shipping: defaulted(enums(["standard", "express"]), "standard"),
  coupon: optional(string()),
  items: size(array(LineItem), 1, 50),
});

export type Order = Infer<typeof Order>;

export function parseOrder(input: unknown): Order {
  return create(input, Order);
}

export function orderTotal(order: Order): number {
  return Math.round(order.items.reduce((s, i) => s + i.qty * i.price, 0) * 100) / 100;
}

export function explain(input: unknown): string[] {
  try {
    create(input, Order);
    return [];
  } catch (e) {
    if (!(e instanceof StructError)) throw e;
    return e.failures().map((f) => `${f.path.join(".")}: ${f.refinement ?? f.type}`);
  }
}

export { assert, is };
test/order.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { StructError } from "superstruct";
import { Order, parseOrder, orderTotal, explain, is } from "../src/order.ts";

test("coerces form-style strings and applies defaults", () => {
  const order = parseOrder({ email: " [email protected] ", items: [{ sku: "BOLT-1", qty: "3", price: "2.50" }] });
  assert.equal(order.email, "[email protected]");
  assert.equal(order.shipping, "standard");
  assert.equal(order.items[0].qty, 3);
  assert.equal(orderTotal(order), 7.5);
});

test("is() checks without coercion", () => {
  assert.equal(is({ email: "[email protected]", shipping: "express", items: [{ sku: "ABC", qty: 1, price: 1 }] }, Order), true);
  assert.equal(is({ email: "[email protected]", shipping: "express", items: [{ sku: "ABC", qty: "1", price: 1 }] }, Order), false);
});

test("failures report path and refinement", () => {
  const msgs = explain({ email: "nope", shipping: "drone", items: [{ sku: "X", qty: "0", price: "-1" }] });
  assert.ok(msgs.includes("email: email"), msgs.join(", "));
  assert.ok(msgs.includes("shipping: enums"), msgs.join(", "));
  assert.ok(msgs.includes("items.0.sku: size"), msgs.join(", "));
  assert.ok(msgs.includes("items.0.qty: min"), msgs.join(", "));
});

test("throws StructError for empty items", () => {
  assert.throws(() => parseOrder({ email: "[email protected]", items: [] }), StructError);
});

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.