I watched the video Going fully modular with Valibot by Fabian Hiller
and here are my notes.
import { string } from 'zod';
const schema = string().min(5);
schema.parse('foo');
// http-url:https://unpkg.com/[email protected]/lib/index.mjs
var ZodString = class _ZodString extends ZodType {
_parse(input) {
if (this._def.coerce) {
input.data = String(input.data);
}
const parsedType = this._getType(input);
if (parsedType !== ZodParsedType.string) {
const ctx2 = this._getOrReturnCtx(input);
addIssueToContext(ctx2, {
code: ZodIssueCode.invalid_type,
expected: ZodParsedType.string,
received: ctx2.parsedType,
});
return INVALID;
}
const status = new ParseStatus();
let ctx = void 0;
for (const check of this._def.checks) {
if (check.kind === 'min') {
if (input.data.length < check.value) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.too_small,
minimum: check.value,
type: 'string',
inclusive: true,
exact: false,
message: check.message,
});
status.dirty();
}
} else if (check.kind === 'max') {
if (input.data.length > check.value) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.too_big,
maximum: check.value,
type: 'string',
inclusive: true,
exact: false,
message: check.message,
});
status.dirty();
}
} else if (check.kind === 'length') {
const tooBig = input.data.length > check.value;
const tooSmall = input.data.length < check.value;
if (tooBig || tooSmall) {
ctx = this._getOrReturnCtx(input, ctx);
if (tooBig) {
addIssueToContext(ctx, {
code: ZodIssueCode.too_big,
maximum: check.value,
type: 'string',
inclusive: true,
exact: true,
message: check.message,
});
} else if (tooSmall) {
addIssueToContext(ctx, {
code: ZodIssueCode.too_small,
minimum: check.value,
type: 'string',
inclusive: true,
exact: true,
message: check.message,
});
}
status.dirty();
}
} else if (check.kind === 'email') {
if (!emailRegex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: 'email',
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === 'emoji') {
if (!emojiRegex) {
emojiRegex = new RegExp(_emojiRegex, 'u');
}
if (!emojiRegex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: 'emoji',
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === 'uuid') {
if (!uuidRegex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: 'uuid',
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === 'nanoid') {
if (!nanoidRegex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: 'nanoid',
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === 'cuid') {
if (!cuidRegex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: 'cuid',
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === 'cuid2') {
if (!cuid2Regex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: 'cuid2',
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === 'ulid') {
if (!ulidRegex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: 'ulid',
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === 'url') {
try {
new URL(input.data);
} catch (_a) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: 'url',
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === 'regex') {
check.regex.lastIndex = 0;
const testResult = check.regex.test(input.data);
if (!testResult) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: 'regex',
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === 'trim') {
input.data = input.data.trim();
} else if (check.kind === 'includes') {
if (!input.data.includes(check.value, check.position)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.invalid_string,
validation: { includes: check.value, position: check.position },
message: check.message,
});
status.dirty();
}
} else if (check.kind === 'toLowerCase') {
input.data = input.data.toLowerCase();
} else if (check.kind === 'toUpperCase') {
input.data = input.data.toUpperCase();
} else if (check.kind === 'startsWith') {
if (!input.data.startsWith(check.value)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.invalid_string,
validation: { startsWith: check.value },
message: check.message,
});
status.dirty();
}
} else if (check.kind === 'endsWith') {
if (!input.data.endsWith(check.value)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.invalid_string,
validation: { endsWith: check.value },
message: check.message,
});
status.dirty();
}
} else if (check.kind === 'datetime') {
const regex = datetimeRegex(check);
if (!regex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.invalid_string,
validation: 'datetime',
message: check.message,
});
status.dirty();
}
} else if (check.kind === 'date') {
const regex = dateRegex;
if (!regex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.invalid_string,
validation: 'date',
message: check.message,
});
status.dirty();
}
} else if (check.kind === 'time') {
const regex = timeRegex(check);
if (!regex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.invalid_string,
validation: 'time',
message: check.message,
});
status.dirty();
}
} else if (check.kind === 'duration') {
if (!durationRegex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: 'duration',
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === 'ip') {
if (!isValidIP(input.data, check.version)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: 'ip',
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === 'base64') {
if (!base64Regex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: 'base64',
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else {
util.assertNever(check);
}
}
return { status: status.value, value: input.data };
}
_regex(regex, validation, message) {
return this.refinement((data) => regex.test(data), {
validation,
code: ZodIssueCode.invalid_string,
...errorUtil.errToObj(message),
});
}
_addCheck(check) {
return new _ZodString({
...this._def,
checks: [...this._def.checks, check],
});
}
email(message) {
return this._addCheck({ kind: 'email', ...errorUtil.errToObj(message) });
}
url(message) {
return this._addCheck({ kind: 'url', ...errorUtil.errToObj(message) });
}
emoji(message) {
return this._addCheck({ kind: 'emoji', ...errorUtil.errToObj(message) });
}
uuid(message) {
return this._addCheck({ kind: 'uuid', ...errorUtil.errToObj(message) });
}
nanoid(message) {
return this._addCheck({ kind: 'nanoid', ...errorUtil.errToObj(message) });
}
cuid(message) {
return this._addCheck({ kind: 'cuid', ...errorUtil.errToObj(message) });
}
cuid2(message) {
return this._addCheck({ kind: 'cuid2', ...errorUtil.errToObj(message) });
}
ulid(message) {
return this._addCheck({ kind: 'ulid', ...errorUtil.errToObj(message) });
}
base64(message) {
return this._addCheck({ kind: 'base64', ...errorUtil.errToObj(message) });
}
ip(options) {
return this._addCheck({ kind: 'ip', ...errorUtil.errToObj(options) });
}
datetime(options) {
var _a, _b;
if (typeof options === 'string') {
return this._addCheck({
kind: 'datetime',
precision: null,
offset: false,
local: false,
message: options,
});
}
return this._addCheck({
kind: 'datetime',
precision:
typeof (options === null || options === void 0
? void 0
: options.precision) === 'undefined'
? null
: options === null || options === void 0
? void 0
: options.precision,
offset:
(_a =
options === null || options === void 0 ? void 0 : options.offset) !==
null && _a !== void 0
? _a
: false,
local:
(_b =
options === null || options === void 0 ? void 0 : options.local) !==
null && _b !== void 0
? _b
: false,
...errorUtil.errToObj(
options === null || options === void 0 ? void 0 : options.message
),
});
}
date(message) {
return this._addCheck({ kind: 'date', message });
}
time(options) {
if (typeof options === 'string') {
return this._addCheck({
kind: 'time',
precision: null,
message: options,
});
}
return this._addCheck({
kind: 'time',
precision:
typeof (options === null || options === void 0
? void 0
: options.precision) === 'undefined'
? null
: options === null || options === void 0
? void 0
: options.precision,
...errorUtil.errToObj(
options === null || options === void 0 ? void 0 : options.message
),
});
}
duration(message) {
return this._addCheck({ kind: 'duration', ...errorUtil.errToObj(message) });
}
regex(regex, message) {
return this._addCheck({
kind: 'regex',
regex,
...errorUtil.errToObj(message),
});
}
includes(value, options) {
return this._addCheck({
kind: 'includes',
value,
position:
options === null || options === void 0 ? void 0 : options.position,
...errorUtil.errToObj(
options === null || options === void 0 ? void 0 : options.message
),
});
}
startsWith(value, message) {
return this._addCheck({
kind: 'startsWith',
value,
...errorUtil.errToObj(message),
});
}
endsWith(value, message) {
return this._addCheck({
kind: 'endsWith',
value,
...errorUtil.errToObj(message),
});
}
min(minLength, message) {
return this._addCheck({
kind: 'min',
value: minLength,
...errorUtil.errToObj(message),
});
}
max(maxLength, message) {
return this._addCheck({
kind: 'max',
value: maxLength,
...errorUtil.errToObj(message),
});
}
length(len, message) {
return this._addCheck({
kind: 'length',
value: len,
...errorUtil.errToObj(message),
});
}
/**
* @deprecated Use z.string().min(1) instead.
* @see {@link ZodString.min}
*/
nonempty(message) {
return this.min(1, errorUtil.errToObj(message));
}
trim() {
return new _ZodString({
...this._def,
checks: [...this._def.checks, { kind: 'trim' }],
});
}
toLowerCase() {
return new _ZodString({
...this._def,
checks: [...this._def.checks, { kind: 'toLowerCase' }],
});
}
toUpperCase() {
return new _ZodString({
...this._def,
checks: [...this._def.checks, { kind: 'toUpperCase' }],
});
}
get isDatetime() {
return !!this._def.checks.find((ch) => ch.kind === 'datetime');
}
get isDate() {
return !!this._def.checks.find((ch) => ch.kind === 'date');
}
get isTime() {
return !!this._def.checks.find((ch) => ch.kind === 'time');
}
get isDuration() {
return !!this._def.checks.find((ch) => ch.kind === 'duration');
}
get isEmail() {
return !!this._def.checks.find((ch) => ch.kind === 'email');
}
get isURL() {
return !!this._def.checks.find((ch) => ch.kind === 'url');
}
get isEmoji() {
return !!this._def.checks.find((ch) => ch.kind === 'emoji');
}
get isUUID() {
return !!this._def.checks.find((ch) => ch.kind === 'uuid');
}
get isNANOID() {
return !!this._def.checks.find((ch) => ch.kind === 'nanoid');
}
get isCUID() {
return !!this._def.checks.find((ch) => ch.kind === 'cuid');
}
get isCUID2() {
return !!this._def.checks.find((ch) => ch.kind === 'cuid2');
}
get isULID() {
return !!this._def.checks.find((ch) => ch.kind === 'ulid');
}
get isIP() {
return !!this._def.checks.find((ch) => ch.kind === 'ip');
}
get isBase64() {
return !!this._def.checks.find((ch) => ch.kind === 'base64');
}
get minLength() {
let min = null;
for (const ch of this._def.checks) {
if (ch.kind === 'min') {
if (min === null || ch.value > min) min = ch.value;
}
}
return min;
}
get maxLength() {
let max = null;
for (const ch of this._def.checks) {
if (ch.kind === 'max') {
if (max === null || ch.value < max) max = ch.value;
}
}
return max;
}
};
ZodString.create = (params) => {
var _a;
return new ZodString({
checks: [],
typeName: ZodFirstPartyTypeKind.ZodString,
coerce:
(_a = params === null || params === void 0 ? void 0 : params.coerce) !==
null && _a !== void 0
? _a
: false,
...processCreateParams(params),
});
};
// /input.ts
var schema = stringType().min(5);
schema.parse('foo');
The following code is what is actually executed from the bundled code
var schema = stringType().min(5);
schema.parse('foo');
The stringType()
function returns an instance of the ZodString
class, but it seems to include other methods even though I only want to call the min()
method. This prevents tree-shaking, resulting in a larger bundle size.
import * as v from 'valibot';
const schema = v.pipe(v.string(), v.minLength(5));
schema.parse('foo');
// http-url:https://unpkg.com/[email protected]/dist/index.js
var store2;
function getGlobalMessage(lang) {
return store2?.get(lang);
}
var store3;
function getSchemaMessage(lang) {
return store3?.get(lang);
}
var store4;
function getSpecificMessage(reference, lang) {
return store4?.get(reference)?.get(lang);
}
function _stringify(input) {
const type = typeof input;
if (type === 'string') {
return `"${input}"`;
}
if (type === 'number' || type === 'bigint' || type === 'boolean') {
return `${input}`;
}
if (type === 'object' || type === 'function') {
return (input && Object.getPrototypeOf(input)?.constructor?.name) ?? 'null';
}
return type;
}
function _addIssue(context, label, dataset, config2, other) {
const input = other && 'input' in other ? other.input : dataset.value;
const expected = other?.expected ?? context.expects ?? null;
const received = other?.received ?? _stringify(input);
const issue = {
kind: context.kind,
type: context.type,
input,
expected,
received,
message: `Invalid ${label}: ${expected ? `Expected ${expected} but r` : 'R'}eceived ${received}`,
requirement: context.requirement,
path: other?.path,
issues: other?.issues,
lang: config2.lang,
abortEarly: config2.abortEarly,
abortPipeEarly: config2.abortPipeEarly,
};
const isSchema = context.kind === 'schema';
const message =
other?.message ??
context.message ??
getSpecificMessage(context.reference, issue.lang) ??
(isSchema ? getSchemaMessage(issue.lang) : null) ??
config2.message ??
getGlobalMessage(issue.lang);
if (message) {
issue.message =
typeof message === 'function'
? // @ts-expect-error
message(issue)
: message;
}
if (isSchema) {
dataset.typed = false;
}
if (dataset.issues) {
dataset.issues.push(issue);
} else {
dataset.issues = [issue];
}
}
function minLength(requirement, message) {
return {
kind: 'validation',
type: 'min_length',
reference: minLength,
async: false,
expects: `>=${requirement}`,
requirement,
message,
_run(dataset, config2) {
if (dataset.typed && dataset.value.length < this.requirement) {
_addIssue(this, 'length', dataset, config2, {
received: `${dataset.value.length}`,
});
}
return dataset;
},
};
}
function string(message) {
return {
kind: 'schema',
type: 'string',
reference: string,
expects: 'string',
async: false,
message,
_run(dataset, config2) {
if (typeof dataset.value === 'string') {
dataset.typed = true;
} else {
_addIssue(this, 'type', dataset, config2);
}
return dataset;
},
};
}
function pipe(...pipe2) {
return {
...pipe2[0],
pipe: pipe2,
_run(dataset, config2) {
for (const item of pipe2) {
if (item.kind !== 'metadata') {
if (
dataset.issues &&
(item.kind === 'schema' || item.kind === 'transformation')
) {
dataset.typed = false;
break;
}
if (
!dataset.issues ||
(!config2.abortEarly && !config2.abortPipeEarly)
) {
dataset = item._run(dataset, config2);
}
}
}
return dataset;
},
};
}
var schema = pipe(string(), minLength(5));
schema.parse('foo');
When looking at the Valibot bundle, it is clear that only pipe, string, and minLength are actually used, and it does not include any unused validation functions (such as email()
, which are technically referred to as Actions in Valibot).
Additionally, since each Action is separated, they are easier to test in isolation.
https://valibot.dev/guides/pipelines/ https://github.com/fabian-hiller/valibot/discussions/498