Skip to content

lucasavila00/beff

Repository files navigation

🪄 Beff

Efficient validators from Typescript types generated by a blazing fast compiler.

  • Typescript Efficient: Unlike zod, io-ts and similar, beff doesn't generate any extra work for the typescript compiler. Get a fast editor and fast compile times.
  • Fast: Beff is written in Rust and compiled to WASM. It is supported by every OS and should work for you!
  • Really fast: Beff compiles a hello-world project in 5ms. A big project with +200 types is compiled in 200ms.
  • Compatible: Beff uses the Typescript compiler for path resolution. If your editor can find the types, so can beff.
  • Efficient generated code: Beff generates optimal validator code, applying many optimizations to it at compile time.
  • Helpful: Beff generates clear error messages at compile time and at validation time.
  • Powerful: Beff supports recursive types, generic types, mapped types, conditional types, Omit, Exclude, Partial, Required, Record and a lot more. If it makes sense to have a runtime validator for that type, beff will understand it.

Getting Started

1. Install

Install @beff/cli and @beff/client from npm.

npm i @beff/cli @beff/client

2. Configure

Create a json file to configure beff. The file can have any name, but it's standard practice to name if beff.json.

{
  "parser": "./src/parser.ts",
  "outputDir": "./src/generated"
}

3. Create the parser file

Create a typescript file that lists the types for which beff should generate validator for.

It's standard practice to call it parser.ts

import parse from "./generated/parser";

type User = {
  name: string;
  age: number;
};

export const Parsers = parse.buildParsers<{
  User: User;
}>();

4. Generate the parser code

@beff/cli installs a beff binary.

npx beff -p beff.json

5. Use the validators

import { Parsers } from "./parser.ts";

const user1 = Parsers.User.parse({
  name: "John Doe",
  age: 42,
});

const maybeUser = Parser.User.safeParse(null);

CLI Options

The beff binary can run in watch mode, too.

$ npx beff -h
Usage: beff [options]

Generate validators from typescript types

Options:
  -p, --project <string>  Path to the project file
  -v, --verbose           Print verbose output
  -w, --watch             Watch for file changes
  -h, --help              display help for command

Features

Json Schema

Generate flat JSON schema from types.

Recursive types are not supported and become the equivalent of any in the second time they appear.

Configure your beff.json

{
  "parser": "./src/parser.ts",
  "outputDir": "./src/generated"
}

Import the generated code and configure types that will be exported.

import s from "./generated/schema";

type ProfileData = {
  name: string;
};

type User = {
  profile: ProfileData;
  age: number;
};

export const Schemas = s.buildSchemas<{
  User: User;
}>();

console.log(Schemas.User); // JSON Schema without references

Custom String Formats

Configure your beff.json

{
  "parser": "./src/parser.ts",
  "outputDir": "./src/generated",
  "customFormats": [
    {
      "name": "ValidCurrency"
    }
  ]
}

Use the helper StringFormat to create the type. It creates a branded typescript type. Define the runtime validator in the build parsers call.

import parse from "./generated/parser";
import { StringFormat } from "@beff/cli";
export type ValidCurrency = StringFormat<"ValidCurrency">;

export const Parsers = parse.buildParsers<{
  ValidCurrency: ValidCurrency;
}>({
  customFormats: {
    ValidCurrency: (input: string) => {
      if (VALID_CURRENCIES.include(input)) {
        return true;
      }
      return false;
    },
  },
});

Ad-hoc, one-off validator generator

Beff supports a type creation API similar to zod, io-ts and similar.

However, Beff's type creation API is very limited and only supports a few types.

With Beff, complex types should be generated by compiling Typescript types.

Types generated with the ad-hoc API have the same properties and methods as regular compiled types.

import { b } from "@beff/client";

const AdHocItem = b.Object({
  str: b.String(),
  num: b.Number(),
  bool: b.Boolean(),
  undefined: b.Undefined(),
  null: b.Null(),
  any: b.Any(),
  unknown: b.Unknown(),
});

const AdHocList = b.Array(AdHocItem);

const ls = AdHocList.parse([]);

Zod Compatibility

Call .zod() on a parser to create a zod type.

Useful for gradually migrating from zod.

import { Parsers } from "./parser.ts";
import { z } from "zod";

const users = z.array(Parsers.User.zod()).parse({
  name: "John Doe",
  age: 42,
});

Contributing

Please read CONTRIBUTING.md