npm package: @schemashift/zod-valibot
Description: Zod to Valibot transformer for SchemaShift — converts fluent chain API to pipe-based API
Author: Joseph May
Latest version: 0.14.0

# @schemashift/zod-valibot

Zod ↔ Valibot transformer for SchemaShift. Supports both forward (Zod → Valibot) and backward (Valibot → Zod) migrations.

[![npm version](https://img.shields.io/npm/v/@schemashift/zod-valibot.svg)](https://www.npmjs.com/package/@schemashift/zod-valibot)
[![npm downloads](https://img.shields.io/npm/dm/@schemashift/zod-valibot.svg)](https://www.npmjs.com/package/@schemashift/zod-valibot)
[![CI](https://github.com/jdmay2/schemashift/actions/workflows/ci.yml/badge.svg)](https://github.com/jdmay2/schemashift/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

**Tier:** Pro

## Installation

```bash
npm install @schemashift/zod-valibot
```

## Usage

```typescript
import { createZodToValibotHandler, createValibotToZodHandler } from '@schemashift/zod-valibot';
import { TransformEngine } from '@schemashift/core';

const engine = new TransformEngine();
engine.registerHandler('zod', 'valibot', createZodToValibotHandler());
engine.registerHandler('valibot', 'zod', createValibotToZodHandler()); // Backward migration (Pro+)
```

### Backward Migration: Valibot → Zod

Text-based transformer that converts Valibot schemas to Zod equivalents:

| Valibot | Zod |
|---------|-----|
| `v.object({...})` | `z.object({...})` |
| `v.string()` | `z.string()` |
| `v.number()` | `z.number()` |
| `v.boolean()` | `z.boolean()` |
| `v.date()` | `z.date()` |
| `v.array(s)` | `z.array(s)` |
| `v.picklist([...])` | `z.enum([...])` |
| `v.union([...])` | `z.union([...])` |
| `v.literal(val)` | `z.literal(val)` |
| `v.optional(s)` | `s.optional()` |
| `v.nullable(s)` | `s.nullable()` |
| `v.nullish(s)` | `s.nullish()` |
| `v.pipe(s, ...)` | Unwrapped with validations chained |
| `v.check(fn, msg)` | `.refine(fn, msg)` |
| `v.transform(fn)` | `.transform(fn)` |
| `v.brand(name)` | `.brand(name)` |
| `v.partial(s)` | `s.partial()` |
| `v.pick(s, [...])` | `s.pick({...})` |
| `v.omit(s, [...])` | `s.omit({...})` |

**Tier:** Pro+

## API Model Difference

Zod uses a fluent **method chain** API:

```typescript
z.string().email().min(5).max(100)
```

Valibot uses a **pipe-based** API where validations are arguments to `v.pipe()`:

```typescript
v.pipe(v.string(), v.email(), v.minLength(5), v.maxLength(100))
```

Modifiers like `.optional()` and `.nullable()` become wrapping functions:

```typescript
// Zod
z.string().optional()

// Valibot
v.optional(v.string())
```

## Transformation Mappings

### Imports

| Zod | Valibot |
|-----|---------|
| `import { z } from 'zod'` | `import * as v from 'valibot'` |
| `import * as z from 'zod'` | `import * as v from 'valibot'` |

### Factory Methods

| Zod | Valibot |
|-----|---------|
| `z.string()` | `v.string()` |
| `z.number()` | `v.number()` |
| `z.boolean()` | `v.boolean()` |
| `z.date()` | `v.date()` |
| `z.bigint()` | `v.bigint()` |
| `z.symbol()` | `v.symbol()` |
| `z.undefined()` | `v.undefined_()` |
| `z.null()` | `v.null_()` |
| `z.void()` | `v.void_()` |
| `z.any()` | `v.any()` |
| `z.unknown()` | `v.unknown()` |
| `z.never()` | `v.never()` |
| `z.nan()` | `v.nan()` |
| `z.literal(val)` | `v.literal(val)` |
| `z.enum([...])` | `v.picklist([...])` |
| `z.nativeEnum(E)` | `v.enum_(E)` |
| `z.array(s)` | `v.array(s)` |
| `z.object({...})` | `v.object({...})` |
| `z.record(k, v)` | `v.record(k, v)` |
| `z.tuple([...])` | `v.tuple([...])` |
| `z.union([...])` | `v.union([...])` |
| `z.discriminatedUnion(d, [...])` | `v.variant(d, [...])` |
| `z.intersection(a, b)` | `v.intersect(a, b)` |
| `z.lazy(() => s)` | `v.lazy(() => s)` |
| `z.promise(s)` | `v.promise(s)` |
| `z.instanceof(C)` | `v.instance(C)` |

### String Validations (Pipe)

| Zod | Valibot |
|-----|---------|
| `.email()` | `v.email()` |
| `.url()` | `v.url()` |
| `.uuid()` | `v.uuid()` |
| `.regex(r)` | `v.regex(r)` |
| `.ip()` | `v.ip()` |
| `.min(n)` | `v.minLength(n)` |
| `.max(n)` | `v.maxLength(n)` |
| `.length(n)` | `v.length(n)` |
| `.trim()` | `v.trim()` |
| `.toLowerCase()` | `v.toLowerCase()` |
| `.toUpperCase()` | `v.toUpperCase()` |
| `.includes(s)` | `v.includes(s)` |
| `.startsWith(s)` | `v.startsWith(s)` |
| `.endsWith(s)` | `v.endsWith(s)` |
| `.datetime()` | `v.isoDateTime()` |
| `.date()` | `v.isoDate()` |
| `.time()` | `v.isoTime()` |
| `.emoji()` | `v.emoji()` |

### Number Validations (Pipe)

| Zod | Valibot |
|-----|---------|
| `.min(n)` | `v.minValue(n)` |
| `.max(n)` | `v.maxValue(n)` |
| `.gt(n)` | `v.minValue(n + 1)` |
| `.lt(n)` | `v.maxValue(n - 1)` |
| `.gte(n)` | `v.minValue(n)` |
| `.lte(n)` | `v.maxValue(n)` |
| `.int()` | `v.integer()` |
| `.positive()` | `v.minValue(1)` |
| `.negative()` | `v.maxValue(-1)` |
| `.nonnegative()` | `v.minValue(0)` |
| `.nonpositive()` | `v.maxValue(0)` |
| `.multipleOf(n)` | `v.multipleOf(n)` |
| `.finite()` | `v.finite()` |
| `.safe()` | `v.safeInteger()` |

### Array Validations

| Zod | Valibot |
|-----|---------|
| `.min(n)` | `v.minLength(n)` |
| `.max(n)` | `v.maxLength(n)` |
| `.nonempty()` | `v.nonEmpty()` |

### Modifiers (Wrapping)

| Zod | Valibot |
|-----|---------|
| `.optional()` | `v.optional(schema)` |
| `.nullable()` | `v.nullable(schema)` |
| `.nullish()` | `v.nullish(schema)` |
| `.default(val)` | `v.optional(schema, val)` |
| `.catch(val)` | `v.fallback(schema, val)` |
| `.readonly()` | `v.readonly(schema)` |
| `.transform(fn)` | `v.pipe(schema, v.transform(fn))` |
| `.refine(fn, msg)` | `v.pipe(schema, v.check(fn, msg))` |
| `.brand(name)` | `v.pipe(schema, v.brand(name))` |
| `.describe(msg)` | (removed — Valibot has no equivalent) |

### Object Methods (Wrapping)

| Zod | Valibot |
|-----|---------|
| `.partial()` | `v.partial(schema)` |
| `.required()` | `v.required(schema)` |
| `.pick({...})` | `v.pick(schema, {...})` |
| `.omit({...})` | `v.omit(schema, {...})` |
| `.extend(other)` | `v.merge([schema, other])` |
| `.merge(other)` | `v.merge([schema, other])` |
| `.strict()` | `v.strict(schema)` |
| `.passthrough()` | (no-op — Valibot objects are loose by default) |
| `.strip()` | (no-op — Valibot strips unknown by default with v.object) |

### Type Helpers

| Zod | Valibot |
|-----|---------|
| `z.infer<typeof s>` | `v.InferOutput<typeof s>` |
| `z.input<typeof s>` | `v.InferInput<typeof s>` |
| `z.output<typeof s>` | `v.InferOutput<typeof s>` |

## Patterns Requiring Manual Review

### `.superRefine()`

`.superRefine()` is converted with a warning since Valibot's custom validation has a different API:

```typescript
// Zod
z.object({...}).superRefine((data, ctx) => {
  ctx.addIssue({ code: 'custom', message: '...' });
});

// Valibot (needs manual conversion)
v.pipe(v.object({...}), /* TODO: superRefine -> custom validation */ v.check(...))
```

### `.catchall()`

Zod's `.catchall()` for extra properties requires manual conversion to Valibot's `v.record()` or custom validation.

### `.keyof()`

Zod's `.keyof()` requires manual conversion to `v.picklist(Object.keys(...))`.

## Related Packages

- [schemashift-cli](https://www.npmjs.com/package/schemashift-cli) — CLI tool for running migrations
- [@schemashift/core](https://www.npmjs.com/package/@schemashift/core) — Core analysis engine
- [@schemashift/yup-zod](https://www.npmjs.com/package/@schemashift/yup-zod) — Chain: Yup → Zod → Valibot

## License

MIT