bookish-potato-dto
Version:
A TypeScript DTO (Data Transfer Object) parsing and validation library. Define a schema once — get runtime validation and a fully inferred TypeScript type for free.
43 lines (42 loc) • 2.24 kB
TypeScript
import { PrimitiveMap } from '../_types';
import { AnyFieldDescriptor, ArrayDtoFieldDescriptor, ArrayFieldDescriptor, BooleanFieldDescriptor, CustomFieldDescriptor, DateFieldDescriptor, DtoFieldDescriptor, EnumFieldDescriptor, IntegerFieldDescriptor, NumberFieldDescriptor, RegexFieldDescriptor, SchemaMap, StringFieldDescriptor } from './field-descriptor.type';
import { DtoDefinition } from './dto-definition.type';
type FieldOutputType<F extends AnyFieldDescriptor> = F extends StringFieldDescriptor ? string : F extends NumberFieldDescriptor ? number : F extends IntegerFieldDescriptor ? number : F extends BooleanFieldDescriptor ? boolean : F extends DateFieldDescriptor ? Date : F extends RegexFieldDescriptor ? string : F extends EnumFieldDescriptor<infer E> ? E : F extends ArrayFieldDescriptor<infer K> ? PrimitiveMap[K][] : F extends DtoFieldDescriptor<infer S> ? InferDto<DtoDefinition<S>> : F extends ArrayDtoFieldDescriptor<infer S> ? Array<InferDto<DtoDefinition<S>>> : F extends CustomFieldDescriptor<infer T> ? T : unknown;
type WithNullable<F extends AnyFieldDescriptor, T> = F extends {
options: {
isNullable: true;
};
} ? T | null : T;
type InferFieldType<F extends AnyFieldDescriptor> = WithNullable<F, FieldOutputType<F>>;
type RequiredKeys<S extends SchemaMap> = {
[K in keyof S]: S[K] extends {
options: {
isOptional: true;
};
} ? never : K;
}[keyof S];
type OptionalKeys<S extends SchemaMap> = {
[K in keyof S]: S[K] extends {
options: {
isOptional: true;
};
} ? K : never;
}[keyof S];
/**
* Derives the full TypeScript type from a DtoDefinition.
*
* - Required fields become required properties.
* - `isOptional: true` fields become optional (`T | undefined`).
* - `isNullable: true` fields become nullable (`T | null`).
*
* @example
* const PersonDto = defineDto({ name: field.string(), age: field.integer() });
* type PersonDto = InferDto<typeof PersonDto>;
* // → { name: string; age: number }
*/
export type InferDto<D extends DtoDefinition> = D extends DtoDefinition<infer S> ? {
[K in RequiredKeys<S>]: InferFieldType<S[K]>;
} & {
[K in OptionalKeys<S>]?: InferFieldType<S[K]>;
} : never;
export {};