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

@slowclap/vkit

v0.3.1

Published

Lightweight, strict runtime validation toolkit for TypeScript projects

Readme

@slowclap/vkit: Low Footprint Validator Kit

vkit is a tiny, no-runtime-dependency library for validating unknown objects, forms, or external API responses in TypeScript.

It provides:

  • isObjectOfShape(obj, shape) - returns true or false
  • assertShape(obj, shape) - throws AggregateValidationError if invalid
  • validateFields(obj, shape) - full validation results
  • v - built-in validators (like v.isString, v.isArray, etc.)
  • v.opt - optionalized versions (accepts undefined/null)
  • arrayOf(shape) - validates arrays of items
  • optionalize(shape) - manually make any validator optional

Installation

npm install @slowclap/vkit

Usage Example

import { v, createKit, defineShape, VKit } from '@slowclap/vkit'

interface User {
  id: string
  name: string
  age?: number
  tags: string[]
}

// Define a validation shape
const userShape = defineShape<User>({
  id: v.isString,
  name: v.isString,
  age: v.opt.isNumber,
  tags: arrayOf(v.isString)
})

// Create a validation kit for the User type
const userKit: VKit<User> = createKit<User>(userShape)

// Validate safely
const unknownObj: unknown = fetchUser()

if (userKit.isObjectOfShape(unknownObj)) {
  console.log(unknownObj.name) // fully typed User
}

// Or throw rich validation errors
try {
  userKit.assertShape(unknownObj)
  console.log('valid!')
} catch (e) {
  if (e instanceof AggregateValidationError) {
    console.error(e.errors)
  }
}

Built-in Validators

| Name | Description | |------|-------------| | v.isString | Value must be a string | | v.isNumber | Value must be a number | | v.isInteger | Value must be an integer | | v.isBoolean | Value must be a boolean | | v.isNumericString | Value must be a numeric string | | v.isIntegerString | Value must be an integer string | | v.isBooleanString | Value must be a boolean string | | v.isArray | Value must be an array | | v.isEnum(EnumType) | Value must be a valid value for EnumType | | v.literally(string \| number \| boolean) | Value must match exactly the provided value | | v.dt.isISODateString | Value must be a valid ISO date string | | v.ignore | Accepts any value (no validation) |

Optional Validators

All built-in validators have corresponding optional versions in the v.opt chain.

Example:

v.opt.isString(undefined) // valid
v.opt.isNumber(null) // valid
v.opt.isBoolean(undefined) // valid

Advanced: Custom Validators

You can define your own custom validators:

import { Validator } from '@slowclap/vkit'

const isUUID: Validator = (v, field) => {
  const valid = typeof v === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(v)
  return [{ field: field || '', valid: valid, value: v, message: valid ? undefined : 'Invalid UUID' }]
}

// Usage
const userShape = defineShape<User>({
  id: isUUID,
  // ... other fields
})

Ignoring Object Properties

You can use v.ignore to accept any value for specific object properties without validation:

interface Config {
  apiKey: string
  settings: any  // We want to accept any settings object
}

const configShape = defineShape<Config>({
  apiKey: v.isString,
  settings: v.ignore  // Accepts any value, no validation
})

// This will pass validation
const config = {
  apiKey: 'secret-key',
  settings: { theme: 'dark', notifications: true, unknownField: 'anything' }
}

⚠️ Concerns and Known Limitations

Record-like Objects

There are limitations on the ability to tightly constrain record types (e.g. { [id: string]: valueType }) while allowing deep object validation. Because of this, validation occurs loosely in that if keys are not specified in the object validation shape, they will pass as if valid. For the highest degree of safety, only trust fields that are specified in the validator shape itself.

Example:

interface Library {
  [name: string]: string
}
interface MyLibrary extendLibrary {
  myName: string
}

const vkit: createKit<MyLibrary>({
  myName: v.isString,
})

// E.g. returns { myName: 'Bob', favoriteBook: 'The Giving Tree' }
const obj = getObjFromSource()
if (vkit.isObjectOfShape(obj)) {
  // ...we can trust obj.myName, but not obj.favoriteBook
}

If we have a record type that we do want to validate, we can create a validator shape for the value type, and iterate/validate manually using Object.values(obj).

Date Validation

The v.dt.isISODateString validator only validates that a string is in ISO date format (e.g., "2024-03-20T15:30:00Z"). It does not convert the string to a JavaScript Date object. If your TypeScript interface expects a Date type, you'll need to manually convert the validated string to a Date object after validation. This is typically a concern when a strongly typed Date is serialized to JSON and back. The serialization from Date to string happens automatically, but JSON won't convert it on deserialization.

Example:

interface Event {
  startTime: Date  // Note: Type is Date, not string
}

const eventShape = defineShape<Event>({
  startTime: v.dt.isISODateString  // Validates string format
})

// After validation, you need to convert:
if (eventKit.isObjectOfShape(data)) {
  const event: Event = {
    ...data,
    startTime: new Date(data.startTime)  // Convert string to Date
  }
}

When working with TypeScript interfaces that use index signatures (e.g., [key: string]: string), these cannot be directly used in object validation paths. Instead, you must use either v.isRecord or v.recordOf for validation. If you need to work with a type that has both specific properties and an index signature, you can use the RemoveIndexSignature<T> utility type to strip the index signature.

Example:

interface RecordLike {
  [key: string]: string
}

interface SpecificRecordLike extends RecordLike {
  name: string
}

interface MyObj {
  hi: string
  person: RemoveIndexSignature<SpecificRecordLike>
}

const shape = createKit<MyObj>({
  hi: v.isString,
  person: {
    name: v.isString
  }
})

Warnings

There are some warnings logged when in development mode to give the developer guidance on usage in certain circumstances. These will only be logged when NODE_ENV=development. If the developer wants to supress them, they can set VKIT_SHOW_DEVELOPMENT_WARNINGS=false.

Error Handling

vkit provides detailed validation errors via AggregateValidationError.

Each error includes:

  • field - the name of the field
  • message - human-readable message describing what went wrong
  • value - the actual value that caused the error

Example error handling:

try {
  assertShape<User>(data, userShape)
} catch (e) {
  if (e instanceof AggregateValidationError) {
    e.errors.forEach(error => {
      console.error(`Field "${error.field}": ${error.message}`)
      console.error(`Invalid value: ${error.value}`)
    })
  }
}

Philosophy

  • Tiny: No runtime dependencies. Pure TypeScript.
  • Type-Safe: Full TypeScript support with proper type inference
  • Explicit: Validation shapes match your TypeScript interfaces
  • Flexible: Easy to extend with custom validators
  • Detailed Errors: Rich error reporting for debugging
  • Zero Dependencies: No external dependencies required

License

MIT License - see LICENSE file for details