Skip to content

tada5hi/validup

Repository files navigation

Validup 🛡️

Master Workflow CodeQL Known Vulnerabilities Conventional Commits

A composable, path-based validation library for TypeScript.

Mount validators and nested containers onto object paths, run them in groups, collect structured issues, and bridge to your favorite validator (zod, validator.js) or framework (Vue) — all without decorators or schema DSLs.

Core Philosophy

Most validation libraries make you choose between two extremes: a schema DSL that bakes in every rule, or a hand-rolled function that tangles parsing, validation, and transformation. Validup picks a different point — mount any validator function onto any path of any input, compose containers, and let the runtime handle path expansion, error aggregation, and group filtering. You bring the validators (or wrap an existing library via an integration package); validup orchestrates them.

Table of Contents

Packages

This monorepo publishes one core library and four integration packages:

Package Version Description
validup npm Core: Container, Validator, Issue, ValidupError
@validup/standard-schema npm Bridge to any Standard Schema library (zod, valibot, arktype, …)
@validup/zod npm Bridge to zod schemas (vendor-specific issue mapping)
@validup/validator-js npm Pre-baked factories for validator.js (isEmail(), isLength(), …) + a generic wrap
@validup/vue npm Vue 3 composable for client-side forms

Installation

Install the core package:

npm install validup --save

Optionally add an integration:

npm install @validup/standard-schema --save           # Standard Schema (zod 3.24+, valibot, arktype, …)
npm install @validup/zod --save                       # zod-specific (richer issue mapping)
npm install @validup/validator-js validator --save    # validator.js string validators
npm install @validup/vue --save                       # Vue 3 forms

At a Glance

import { Container, ValidupError } from 'validup';
import { createValidator } from '@validup/zod';
import { z } from 'zod';

const user = new Container<{ name: string; email: string; age: number }>();

user.mount('name', createValidator(z.string().min(2)));
user.mount('email', createValidator(z.string().email()));
user.mount('age', { optional: true }, createValidator(z.number().int().positive()));

try {
    const valid = await user.run({
        name: 'Peter',
        email: 'peter@example.com',
    });
    // valid is { name, email } — `age` was optional and omitted
} catch (error) {
    if (error instanceof ValidupError) {
        console.error(error.issues);
    }
}

Why Validup

Feature What it gives you
🧩 Composable Mount validators and nested Containers on any path. Stay flat or nest as deep as you like.
🌐 Universal Pure JS — runs in Node.js, browsers, Deno, Bun, and edge runtimes.
🎭 Integration-ready First-class bridges to Standard Schema, zod, validator.js, and Vue. Trivial to add more.
🛤️ Path-based Mount via dotted paths (a.b.c), brackets (foo[0]), or globs (**.foo).
🚦 Group-aware Run different validations for create / update / custom groups from the same container.
Optional handling Per-mount control over undefined / null / falsy semantics.
📋 Structured errors Discriminated Issue items and groups with code, path, message, expected, received.
🛡️ Type-safe Container<T> propagates the output shape; mount paths are checked against T. The opt-in defineSchema() builder accumulates T from the registered mounts so run()'s static return type matches what was registered.
Cache-aware Opt-in per-mount result cache ({ cache: new ResultCache() }) — defineValidator({ sideEffect: true }) opts the rare cross-field / async validator out so per-keystroke runs reuse fresh results without re-firing slow checks.

Repository Layout

validup/
├── packages/
│   ├── validup/              # Core library
│   ├── standard-schema/      # @validup/standard-schema
│   ├── zod/                  # @validup/zod
│   ├── validator-js/         # @validup/validator-js
│   └── vue/                  # @validup/vue
├── docs/                     # VitePress site (private)
├── playground/
│   └── vite-vue/             # Vite + Vue 3 demo app (private, multi-route)
├── nx.json                   # Nx caching for build / lint / test
└── release-please-config.json

The five packages are managed as an Nx workspace under npm workspaces. Integration packages depend on validup; the core has a single runtime dep on @ebec/core. docs/ and every playground/* workspace are private — excluded from release-please and monoship.

The Vite + Vue playground lives at playground/vite-vue and exercises @validup/vue end-to-end (basic form, groups, nested forms, async + debounce, server errors, severity). Run it with npm run dev --workspace=@validup-playground/vite-vue.

Development

# Install workspace dependencies
npm install

# Build every package (Nx topological build)
npm run build

# Run all test suites
npm run test

# Lint
npm run lint
npm run lint:fix
  • Node.js: >=24.0.0 (CI runs on 24)
  • Test runner: Vitest 4
  • Bundler: tsdown — ESM-only output (dist/index.mjs + dist/index.d.mts)
  • Lint: ESLint v10 flat config
  • Releases: managed by release-please — one component per package; published via tada5hi/monoship

Contributing

Issues and pull requests are welcome. Please follow Conventional Commits — commit messages are linted via commitlint. CI runs install → build → lint → test on every PR.

For security vulnerabilities, please email contact@tada5hi.net rather than opening a public issue (see SECURITY.md).

License

Made with 💚

Published under Apache 2.0 License.

About

TypeScript validation library, compose validators and nested containers onto object paths, with integrations for Zod, Standard Schema, validator.js, and Vue 3.

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages