Schema Validation with typescript type inference.
Major Shout-out to zod for the inspiration.
Myzod tries to emulate the typescript type system as much as possible and is even in some ways a little stricter. The goal is that writing a schema feels the same as defining a typescript type, with equivalent & and | operators, and well known Generic types like Record, Pick and Omit. On top of that myzod aims to offer validation within the schemas for such things as number ranges, string patterns and lengths to help enforce business logic.
The resulting package has a similar api to zod with a little bit of inspiration from joi.
The goal is to write schemas from which the type of a successfully parsed value can be inferred. With myzod typescript types and validation logic no longer need to be maintained separately.
When parsing equivalent simple object (with nesting) schemas for myzod, zod and joi, on my machine Linux Ubuntu 18.04 running NodeJS 13.X, the results are as such:
objects parsed per second:
zod: 51861joi: 194325myzod: 1288659myzod vs zod: ~25 X Speedup
myzod vs joi: ~6 X Speedup
npm install --save myzod
Myzod is used by creating a schema, extracting the type by inferring it, and finally by parsing javascript values.
import myzod, { Infer } from 'myzod';
const personSchema = myzod.object({
id: myzod.number(),
name: myzod.string().pattern(/^[A-Z]/),
age: myzod.number().min(0),
birthdate: myzod.number().or(myzod.string()),
employed: myzod.boolean(),
friendIds: myzod.array(myzod.number()).nullable()
});
type Person = Infer<typeof personSchema>;
const person: Person = personSchema.parse({ ... });
Type Root
Primitive Types
Reference Types
Logical Types
Recursive Schemas
All myzod schemas extend the generic myzod.Type class, and as such inherit these methods:
Takes an unknown value, and returns it typed if passed validation. Otherwise throws a myzod.ValidationError
parse(value: unknown): T
Takes an unknown value and returns a result which will either be the parsed value or an instance of ValidationError. This api is useful if you do not want to throw exceptions.
const result = schema.try(data);
if (result instanceof myzod.ValidationError) {
// handle Error
} else {
// result is of type: myzod.Infer<typeof schema>
}
Shorthand for creating intersection types of two schemas.
const nameSchema = myzod.object({ name: myzod.string() });
const ageSchema = myzod.object({ age: myzod.number() });
const personSchema = nameSchema.and(ageSchema); // Same as ageSchema.and(nameSchema);
type Person = Infer<typeof personSchema>; // => { name: string; age: number; }
Shorthand for creating union types of two schemas.
const stringOrBoolSchema = myzod.string().or(myzod.boolean());
type StringOrUndefined = Infer<typeof stringOrBoolSchema>; // => string | boolean
Returns a new schema which is a wrapped OptionalType of the current schema.
const optionalStringSchema = myzod.string().optional(); // => OptionalType<StringType>
type StringOrUndefined = Infer<typeof optionalStringSchema>; // => string | undefined
Returns a new schema which is a wrapped NullableType of the current schema.
const nullableStringSchema = myzod.string().nullable(); // => NullableType<StringType>
type StringOrUndefined = Infer<typeof nullableStringSchema>; // => string | null
Returns a new generic schema to the mapped type. Useful for transforming validated input into a new type on parse.
const ObjectIDSchema = myzod
.string()
.withPredicate(ObjectId.isValid, 'must be an object ID')
.map(value => new ObjectId(value));
// Infer<ObjectIDSchema> === ObjectId
const id = ObjectIDSchema.parse('507c7f79bcf86cd7994f6c0e');
id instanceof ObjectId; // true
const id2 = ObjectIDSchema.parse('some string'); // Throws VaidationError with message "must be an object ID"'
options:
number - min length of stringnumber - max length of stringRegExp - regular expression string must matchstring[] - list of valid stingsPredicate<string> - custom predicates to apply to string valuemethods:
min(value: number, errMsg?: string) => StringTypemax(value: number, errMsg?: string) => StringTypepattern(value: RegExp, errMsg?: string) => StringTypevalid(list: string[], errMsg?: string) => StringTypewithPredicate(fn: (val: string) => boolean), errMsg?: string }default(value: string | (() => string)) => StringTypeoptions can be passed as an option object or chained from schema.
myzod.string({ min: 3, max: 10, pattern: /^hey/ });
// same as
myzod.string().min(3).max(10).pattern(/^hey/);
The valid options lets you validate against a set of strings.
const helloworld = myzod.string().valid(['hello', 'world']);
typeof HelloWorld = myzod.Infer<typeof helloworld>; // => string
if however you want the stings to be typed used the literals helper function:
const helloworld = myzod.literals('hello', 'world');
type HelloWorld = myzod.Infer<typeof helloworld>; // => 'hello' | 'world'
Myzod is not interested in reimplementing all possible string validations, ie isUUID, isEmail, isAlphaNumeric, etc. The myzod string validation can be easily extended via the withPredicate API.
const uuidSchema = myzod.string().withPredicate(validator.isUUID, 'expected string to be uuid');
type UUID = Infer<typeof uuidSchema>; // => string
uuidSchema.parse('hello world'); // Throws ValidationError with message 'expected string to be uuid'
// note that if predicate function throws an error that message will be used instead
Note that you can register multiple predicates, and that each invocation will create a new schema:
const greeting = myzod.string().withPredicate(value => value.startsWith('hello'), 'string must start with hello');
const evenGreeting = greeting.withPredicate(value => value.length % 2 === 0, 'string must have even length');
const oddGreeting = greeting.withPredicate(value => value.length % 2 === 1, 'string must have odd length');
You can use default values or functions to create default values for myzod string schemas.
const uuidSchema = z.string().default(() => uuidv4());
const val = uuidSchema.parse(undefined); // val is a valid uuid constructed by uuidv4()
uuid.parse(null); // throws an error
options:
number - min value for numbernumber - max value for numberboolean - when true will attempt to coerce strings to numbers. default falsemethods:
min(value: number, errMsg?: string) => NumberTypemax(value: number, errMsg?: string) => NumberTypewithPredicate(fn: (value: number) => boolean, errMsg?: string) => NumberTypecoerce(flag?: boolean) => NumberTypedefault(value: number | (() => number)) => NumberTypeoptions can be passed as an option object or chained from schema.
myzod.number({ min: 0, max: 10 });
// Same as:
myzod.number().min(0).max(10);
Coercion example:
const schema = myzod.number().coerce(); // same as myzod.number({ coerce: true });
const value = schema.parse('42');
assert.ok(typeof value === 'number'); // succeeds
assert.equal(value, 42); // succeeds
options:
number - min value for numbernumber - max value for numbermethods:
min(value: number | bigint) => BigIntTypemax(value: number | bigint) => BigIntTypewithPredicate(fn: (value: bigint) => boolean, errMsg?: string) => BigIntTypeoptions can be passed as an option object or chained from schema.
myzod.bigint({ min: 0, max: 10 });
// Same as:
myzod.bigint().min(0).max(10);
const integer = myzod.bigint();
type Integer = myzod.Infer<typeof integer>; // => bigint
The bigint schema automatically coerces bigint interpretable numbers and strings into bigint values.
const schema = myzod.bigint();
const value = schema.parse('42');
assert.ok(typeof value === 'bigint'); // succeeds
assert.equal(value, 42n); // succeeds
methods:
default(value: boolean | (() => boolean)) => BooleanTypemyzod.boolean();
myzod.undefined();
methods:
default() => NullTypemyzod.null();
methods:
default() => LiteralTypeJust as in typescript we can type things using literals
const schema = myzod.literal('Value');
type Val = Infer<typeof schema>; // => 'Value'
Sometimes we do not want to go all out and create an enum to represent a combination of literals. Myzod offers a utility function to avoid have to "or" multiple times over many literalTypes.
const schema = myzod.literals('red', 'green', 'blue');
type Schema = myzod.Infer<typeof schema>; // => 'red' | 'green' | 'blue'
// Other equivalent ways of creating the same schema:
const schema = myzod.literal('red').or(myzod.literal('green')).or(myzod.literal('blue'));
const schema = myzod.union([myzod.literal('red'), myzod.literal('green'), myzod.literal('blue')]);
methods:
default(value: any | (() => any)) => UnknownTypemyzod.unknown();
The unknown schema does nothing when parsing by itself. However it is useful to require a key to be present inside an object schema when we don't know or don't care about the type.
const schema = myzod.object({ unknownYetRequiredField: myzod.unknown() });
type Schema = Infer<typeof schema>; // => { unknownYetRequiredField: unknown }
schema.parse({}); // throws a ValidationError
schema.parse({ unknownYetRequiredField: 'hello' }); // succeeds
options:
boolean - allows for object with keys not specified in expected shape to succeed parsing, default falseboolean - suppress the path to the invalid key in thrown validationErrors. This option should stay false for most cases but is used internally to generate appropriate messages when validating nested objects. default falsemyzod.object is the way to construct arbitrary object schemas.
function object(shape: { [key: string]: Type<T> }, opts?: options);
examples:
const strictEmptyObjSchema = myzod.object({});
const emptyObjSchema = myzod.object({}, { allowUnknown: true });
// Both Schemas infer the same type
type Empty = Infer<typeof emptyObjSchema>; // => {}
type StrictEmpty = Infer<typeof strictEmptyObjSchema>; // => {}
emptyObjSchema.parse({ key: 'value' }); // => succeeds
strictEmptyObjSchema.parse({ key: 'value' }); // => throws ValidationError because not expected key: "key"
const personSchema = myzod.object({
name: myzod.string(),
});
const shape = personSchema.shape(); // => returns { name: myzod.string() }
You can add predicate functions to object schemas. Note that these predicate functions will not be kept around for schemas produces from object.pick/omit/partial as they predicate function signatures need to change for those signatures.
```typescript const registrationSchema = myzod .object({ email: z.string().withPredicate(validator.isEmail, 'expected email'), password: z.string(
$ claude mcp add myzod \
-- python -m otcore.mcp_server <graph>