GithubHelp home page GithubHelp logo

thoughtspile / banditypes Goto Github PK

View Code? Open in Web Editor NEW
156.0 2.0 4.0 379 KB

๐Ÿค ๐Ÿงจ The mighty 400-byte schema validator for TS / JS

License: MIT License

TypeScript 96.37% JavaScript 3.63%
javascript runtime-validation schema typescript validation

banditypes's Introduction

My name is Vladimir, nice to meet you!

  • I build web things and lead teams at large companies.
  • As an engineer, I specialize in React / svelte frontends, node.js and, generally, JS / TS.
  • Interested in web development? Check out my blog at blog.thoughtspile.tech
  • I study Machine Learining at ITMO University. We could connect on LinkedIn if you want to.

Here are some things I built:

Like my work? Buy me a coffee โ˜•

banditypes's People

Contributors

jordan-boyer avatar pelikhan avatar thoughtspile avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

banditypes's Issues

optional handling in object

given the following snippet

const person = object({
    name: string().or(optional()),
    age: number(),
  });

type Person = Infer<typeof person>;

we have the resulting person type

type Person = {
    name: string | undefined;
    age: number;
}

where we could have

type Person = {
    name?: string | undefined;
    age: number;
}

what do you think ?

[Bug] TS-first schemas allow stricter-than-needed checks

When using TS-first collection schemas, the field validations can be stricter than the type passed:

array<string>(enums(['s']))
array<{ key: string }>(object({ key: string(), extra: string() }))
object<{ key: string }>({
  key: enums(['s'])
})

This never results in incorrect runtime values, but can make authoring and maintaining schemas a bit more daunting and cause false negatives. In the case of nested objects, I'm pretty sure there's no way to disallow extra keys with current TS.

[Feature] Input & output type infer

Given the following snippet :

const person = object({
    name: string().or(() => 'john'),
    age: number(),
  });

type Person = Infer<typeof person>;

Here Person is :

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

This is a correctly inferred type but it's an output type, because it's the type of the object returned by the person function.

Let's say we have a method that post a user to an API that expect a Person object :

function createPerson(input: unknown) {
    const newPerson = person(input);
    axios.post('/api/person', newPerson); // send a Person object
}

It's weird to see unknown as input in a TypeScript project, here we could write code like createPerson(12) and TS would not complain.

Here we cannot type the input parameter via input: Person because the name property is not optional.

But name should be optional because it has a default value.

Here we see that the same schema has a different input and an output type.

What it can take as input is not the same as what it actually return.

We could imagine a InferInput type that would return the input type of a schema.

type PersonInput = InferInput<typeof person>;

function createPerson(input: PersonInput) {
    const newPerson = person(input);
    axios.post('/api/person', newPerson); // send a Person object
}

Now we have a proper type for the input parameter.

This feature has been implemented in Zod :

const stringToNumber = z.string().transform((val) => val.length);

type input = z.input<typeof stringToNumber>; // string
type output = z.output<typeof stringToNumber>; // number

Breaking bug in 0.2.4 introduce by required in object

It's seem this bug is introduce by the way the d.ts file is generated.

Because when you import the ts file directly typescript does not complain.

here the type produce by 0.2.4

export declare const object: <T = Record<string, never>>(schema: Required<T> extends infer T_1 ? { [K in keyof T_1]: Cast<T[K], unknown>; } : never) => Banditype<Simplify<Partial<T> & Pick<T, { [K_1 in keyof T]: T[K_1] extends Exclude<T[K_1], undefined> ? K_1 : never; }[keyof T]>>>;

and here the type produce by 0.2.3

export declare const object: <T = Record<string, never>>(schema: { [K in keyof T]: Cast<T[K], unknown>; }) => Banditype<Simplify<Partial<T> & Pick<T, { [K_1 in keyof T]: T[K_1] extends Exclude<T[K_1], undefined> ? K_1 : never; }[keyof T]>>>;

I'll try to look into it but I think we should remove this release for now until we can fix it

[Discussion] Object processing

The current implementation of object validation (object, objectLoose and record) is simple and works reasonably well, but has a few corner cases, so I decided to open a discussion on how to best approach this.

Current implementation. Every cast defined in the schema is always called, whether the key is present in the input object or not. Optional fields are modelled as type().or(optional()), where optional is a value === undefined check under the hood, marking undefined as an allowed value. To avoid adding explicit undefined keys to the output, undefined field-cast outputs are stripped from the final object:

banditypes/src/index.ts

Lines 88 to 95 in 0087d7d

instance(Object).map((raw: any) => {
const res = {} as T;
for (const key in schema) {
const f = schema[key](raw[key]);
f !== undefined && (res[key] = f);
}
return res as WithOptionalProps<T>;
});

Traversing by schema keys checks that all fields declared in the schema are present, and applies defaults as needed, e.g. object({ miss: string().or(() => 'default') })

This works as expected in most cases except for the following two:

  1. never() fields don't "blacklist" a single field, but always fail the entire validation instead. E.g. object({ hack: never() }) always throws, because hack cast is always called. I think this is not important in practice, because object already removes undeclared fields, and the original value was unsafe to access at any rate.
  2. unknown() fields are, technically, always optional in the output, because they return undefined for missing or undefined input, and are then stripped from the output. However, inferring unknown() fields as optional will probably be inconvenient.

record is simpler, because the validation is only called on keys present in the input object. Two (unplanned, but handy) side-effects of the current implementation for records are:

  • record(never()) correctly allows only empty objects.
  • You can filter the entries by value: record(raw => typeof raw === 'string' && raw ? raw : undefined) will silently remove all non-string or empty values.

We should also keep the implementations of the three validations should as similar as possible to get the best compression.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.