npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

seam-schema

v0.1.3

Published

One schema. Every language. No drift. Native Node binding.

Downloads

80

Readme

seam-schema

Node bindings for Seam: one schema, every language, no drift.

const { Schema } = require('seam-schema')

const user = Schema.load('contracts/user.seam').validator('User')
const out = user.validate(rawRequestBody)   // a Buffer, a string, or an object

Hand it the bytes

JSON.parse('{"id": 9007199254740993}').id   // 9007199254740992, silently

JSON.parse cannot hold an integer past 2^53, and by the time a validator sees the value the bits are gone. So Seam reads the JSON itself, and a 64-bit field comes back as a bigint:

const out = user.validate(Buffer.from('{"id": 9007199254740993, ...}'))
typeof out.id      // 'bigint'
out.id             // 9007199254740993n

If you pass a plain object instead, a number past 2^53 in a 64-bit field is refused rather than validated as the wrong value:

user.validate({ id: 9007199254740993, ... })   // throws: id, unsafe_integer

There is nothing else Seam could honestly do: the caller's own value is already not the one that was sent.

The three empty states

JavaScript has undefined, null and a missing property, and Seam keeps them apart. undefined reads as absent, which is what JSON.stringify does with it:

'bio' in out          // was the key sent at all?
out.avatar === null   // was it sent as null?

Dates

A Date field is a calendar date and comes back as its ISO string. A DateTime comes back as a JavaScript Date. Pushing a calendar date through an instant is the origin of every off-by-one-day bug at a timezone boundary, so Seam does not do it.

A DateTime without an offset is rejected. Seam never assumes local time.

Errors

const { SeamValidationError } = require('seam-schema')

try {
  user.validate(payload)
} catch (e) {
  if (e instanceof SeamValidationError) {
    e.issues        // every failure, not just the first
    e.path, e.code  // the first one, for the common case
  }
}

path and code are stable API. message is the summary, as JavaScript expects of an Error; an individual issue's own text is issues[0].message. Nothing is built until it is read.

Limits

Untrusted input is bounded whether or not you ask. Tighten them to what a legitimate request looks like:

schema.validator('User', { maxItems: 100, maxStringBytes: 4096 })

Generated TypeScript

npx seam typegen contracts/user.seam     # writes contracts/user.types.ts

The generated file is types only: no imports, no code, nothing to run. Delete it and everything still works; you lose static checking and nothing else. Validation happens in the engine, against the .seam file, at runtime.

import { Schema } from 'seam-schema'
import type { UserTypes } from './contracts/user.types'

const schema = Schema.load<UserTypes>('contracts/user.seam')
const user = schema.validator('User').validate(rawRequestBody)

user.id            // bigint
user.plan          // 'free' | 'pro' | 'enterprise'
user.bio           // string | undefined

Passing the generated map to Schema.load types every validator it hands out, so validator('Usr') is a compile error rather than a runtime one. Without it nothing breaks and validate returns unknown, which is the honest type for a payload nothing has described.

The mapping is the one TypeScript already had words for:

| .seam | TypeScript | |---|---| | String | name: string | | String? | nickname: string \| null | | optional String | bio?: string | | optional String? | avatar?: string \| null | | u8-u32, i8-i32, f64 | number | | u64, i64 | bigint | | Date | string | | DateTime | Date | | enum { free, pro } | 'free' \| 'pro' | | [String?] | (string \| null)[] |

?: is the absence axis and | null is the nullability axis, which is the same distinction NotRequired draws in Python. They are independent, and a field may carry both.

A union becomes a discriminated union, which is what TypeScript already had for exactly this:

union Event @tag("type") {
  created: Created
  deleted: Deleted
}
export type Event =
  | (Created & { type: 'created' })
  | (Deleted & { type: 'deleted' })

The tag is intersected in rather than declared on the variant, because in the .seam file it belongs to the union and no variant may declare it. What you get back is narrowing:

if (event.type === 'created') {
  event.amount        // bigint, and the compiler knows it
}

In CI, --check fails if a generated file has fallen behind its schema:

npx seam typegen --check contracts/*.seam

Status

npm install seam-schema

Early development, published at 0.1.3. Node 20 or newer: the compiled module targets Node-API 6 and would run on 18, but the build tool needs 20, and a version nothing tests is not a version this claims to support.

Binaries ship for Linux x64, macOS ARM and Windows x64. On any other platform the install fails rather than falling back, because there is nothing to fall back to; seam-schema-wasm runs anywhere.

Build from a checkout:

cd seam-js && npm install && npm run build && npm test

The seam command is installed by both this package and the Python one. They take the same subcommand and the same flags on purpose, but if you install both globally, whichever is first on PATH wins; npx seam always reaches this one.