| name | valibot-serialize |
| description | Use valibot-serialize to generate deterministic Valibot modules from Valibot schemas or Drizzle tables, serialize and reconstruct schemas through its versioned AST, migrate stored payloads, convert to or from JSON Schema, or extend generation with explicit source plugins. Use for tasks involving the `valibot-serialize` package, the `vs_tocode` CLI or API, generated-schema check/watch workflows, and serialized Valibot schema compatibility. |
Valibot Serialize
Use the package's narrowest API for the requested job, preserve the host
project's package-manager conventions, and verify generated or reconstructed
schemas with the host project's tests.
Choose the surface
| Goal | Surface |
|---|
| Generate committed Valibot modules | vs_tocode CLI |
| Embed generation, inspect output, or manage watch lifecycle | generate from valibot-serialize/vs_tocode |
| Store or transmit a Valibot schema | fromValibot from valibot-serialize |
| Read current or legacy serialized data | migrateSerializedSchema, then toValibot |
| Emit Valibot builder source | toCode |
| Interoperate with JSON Schema | toJsonSchema or fromJsonSchema |
| Recognize a custom exported source value | defineSourcePlugin with explicit handlers |
Before editing, inspect the installed package version and the project's existing
scripts, generated-file policy, formatter, module system, and package manager.
Do not introduce Node tooling into a Deno-only project.
Install the package
For Node.js 22 or later, add the library and Valibot. Add tsx as a development
dependency only when using the packaged vs_tocode binary:
npm add valibot valibot-serialize
npm add --save-dev tsx
Translate those commands to the project's existing npm-compatible package
manager. For Deno, prefer the JSR package:
deno add jsr:@gadicc/valibot-serialize npm:valibot
Generate static modules
Add separate write and verification scripts. Use an explicit formatter in
reproducible workflows; auto depends on the local environment.
{
"scripts": {
"schema:gen": "vs_tocode --include 'src/schemas/*.ts' --out-dir src/generated --formatter=none",
"schema:check": "vs_tocode --include 'src/schemas/*.ts' --out-dir src/generated --formatter=none --check"
}
}
Follow these rules:
- Use positional files for a fixed list, or
--include plus optional
--exclude for globs. Do not combine positional files with include/exclude.
- Use
--check in CI when generated files are committed. It compares selected
outputs without writing and exits nonzero for missing or changed files.
- Do not promise orphan-output detection; check mode only evaluates currently
selected inputs.
- Use
--watch only for development. A per-file failure leaves the previous
owned output in place and can recover after another edit.
- Treat selected source modules as trusted code because generation imports and
evaluates them.
- Inspect the generated diff and run both the schema check and relevant project
tests.
For Deno CLI use, grant only the permissions needed to import sources and write
outputs, for example:
deno run --allow-read --allow-write jsr:@gadicc/valibot-serialize/vs_tocode --help
Use the generator API
Import the non-exiting API from its subpath. Prefer explicitFiles for known
inputs and set projectRoot when the caller's working directory is ambiguous.
import { generate } from "valibot-serialize/vs_tocode";
const result = await generate({
explicitFiles: ["src/schemas/user.ts"],
outDir: "src/generated",
formatter: "none",
check: true,
});
if (!result.check?.upToDate) {
throw new Error(JSON.stringify(result.check?.mismatches));
}
Use dryRun: true to inspect result.files[].contents without writing. Never
combine check with watch or dryRun. When enabling watch, retain the
handle, close it during shutdown, and await completion when appropriate:
const result = await generate({
explicitFiles: ["src/schemas/user.ts"],
outDir: "src/generated",
formatter: "none",
watch: true,
});
await result.watch?.close();
await result.watch?.done;
The API rejects errors and does not terminate the host process or set its exit
status.
Serialize and reconstruct schemas
Serialize only supported Valibot constructs, cross the wire as JSON, and treat
received data as unknown. Migrate at the boundary to validate legacy or
current payloads and obtain the canonical current format.
import * as v from "valibot";
import {
fromValibot,
migrateSerializedSchema,
toValibot,
} from "valibot-serialize";
const source = v.object({ id: v.number(), name: v.string() });
const wireText = JSON.stringify(fromValibot(source));
const received: unknown = JSON.parse(wireText);
const canonical = migrateSerializedSchema(received);
const reconstructed = toValibot(canonical);
const value = v.parse(reconstructed, { id: 1, name: "Ada" });
Use isSerializedSchema only as a type guard for the current canonical format;
it deliberately rejects supported legacy payloads. Use migrateSerializedSchema
when historical stored data is possible. Do not manually edit kind, vendor,
version, format, node shapes, or reference paths.
Account for these boundaries:
- Arbitrary transforms, callbacks, accessors, and unsupported pipe actions are
not serializable; keep custom runtime behavior outside the serialized schema.
- Defaults must be exact JSON values. Callback defaults, non-finite numbers,
negative zero, sparse arrays, class instances, and richer runtime values fail.
- Recursive lazy schemas can round-trip and produce Valibot code, but recursive
data JSON Schema conversion is unsupported.
- Readers are not forward-compatible with unknown envelope or format versions.
Convert code and JSON Schema
Use toCode(serialized) when the consumer needs a Valibot builder expression;
it returns source without imports, so add imports and formatting at the caller.
Treat toJsonSchema as a best-effort Draft 2020-12 data-schema projection and
fromJsonSchema as a deliberately limited, lossy import. Prefer Valibot plus
fromValibot as the source of truth. Add semantic tests whenever conversion
crosses the JSON Schema boundary, especially for sets, maps, dates, files,
blobs, tuples, formats, and custom constraints.
Extend source generation
Define custom source plugins with defineSourcePlugin. Keep available,
test, and transform deterministic and safe under concurrent processing.
Supplying handlers replaces all built-ins, so compose them explicitly when
they must remain enabled:
import {
builtInHandlers,
defineSourcePlugin,
generate,
} from "valibot-serialize/vs_tocode";
const taggedString = defineSourcePlugin({
name: "tagged-string",
available: () => true,
test: (value) =>
typeof value === "object" && value !== null &&
(value as { kind?: unknown }).kind === "tagged-string",
transform: (symbol) => ({ exports: { [symbol]: "v.string()" } }),
});
await generate({
explicitFiles: ["src/schemas.ts"],
outDir: "src/generated",
formatter: "none",
handlers: [taggedString, ...builtInHandlers],
});
Do not imply registration or discovery: defineSourcePlugin is an identity
helper, plugins are passed per generate call, and their output remains
Valibot-oriented.