Validup ๐Ÿ›ก๏ธ

June 3, 2026 ยท View on GitHub

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:

PackageVersionDescription
validupnpmCore: Container, Validator, Issue, ValidupError
@validup/standard-schemanpmBridge to any Standard Schema library (zod, valibot, arktype, โ€ฆ)
@validup/zodnpmBridge to zod schemas (vendor-specific issue mapping)
@validup/validator-jsnpmPre-baked factories for validator.js (isEmail(), isLength(), โ€ฆ) + a generic wrap
@validup/vuenpmVue 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

FeatureWhat it gives you
๐Ÿงฉ ComposableMount validators and nested Containers on any path. Stay flat or nest as deep as you like.
๐ŸŒ UniversalPure JS โ€” runs in Node.js, browsers, Deno, Bun, and edge runtimes.
๐ŸŽญ Integration-readyFirst-class bridges to Standard Schema, zod, validator.js, and Vue. Trivial to add more.
๐Ÿ›ค๏ธ Path-basedMount via dotted paths (a.b.c), brackets (foo[0]), or globs (**.foo).
๐Ÿšฆ Group-awareRun different validations for create / update / custom groups from the same container.
โ“ Optional handlingPer-mount control over undefined / null / falsy semantics.
๐Ÿ“‹ Structured errorsDiscriminated Issue items and groups with code, path, message, expected, received.
๐Ÿ›ก๏ธ Type-safeContainer<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-awareOpt-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.