diff --git a/src/schema/accepted/main.ts b/src/schema/accepted/main.ts index fab3cea..550c349 100644 --- a/src/schema/accepted/main.ts +++ b/src/schema/accepted/main.ts @@ -14,7 +14,11 @@ import type { FieldOptions, Validation } from '../../types.js' /** * VineAccepted represents a checkbox input that must be checked */ -export class VineAccepted extends BaseLiteralType { +export class VineAccepted extends BaseLiteralType< + 'on' | '1' | 'yes' | 'true' | true | 1, + true, + true +> { /** * Default collection of accepted rules */ diff --git a/src/schema/any/main.ts b/src/schema/any/main.ts index b052853..b0225ef 100644 --- a/src/schema/any/main.ts +++ b/src/schema/any/main.ts @@ -13,7 +13,7 @@ import type { FieldOptions, Validation } from '../../types.js' /** * VineAny represents a value that can be anything */ -export class VineAny extends BaseLiteralType { +export class VineAny extends BaseLiteralType { constructor(options?: Partial, validations?: Validation[]) { super(options, validations) } diff --git a/src/schema/array/main.ts b/src/schema/array/main.ts index a36ef66..006d876 100644 --- a/src/schema/array/main.ts +++ b/src/schema/array/main.ts @@ -11,7 +11,7 @@ import camelcase from 'camelcase' import { RefsStore, ArrayNode } from '@vinejs/compiler/types' import { BaseType } from '../base/main.js' -import { OTYPE, COTYPE, PARSE, UNIQUE_NAME, IS_OF_TYPE } from '../../symbols.js' +import { ITYPE, OTYPE, COTYPE, PARSE, UNIQUE_NAME, IS_OF_TYPE } from '../../symbols.js' import type { FieldOptions, ParserOptions, SchemaTypes, Validation } from '../../types.js' import { @@ -28,6 +28,7 @@ import { * pipeline */ export class VineArray extends BaseType< + Schema[typeof ITYPE][], Schema[typeof OTYPE][], Schema[typeof COTYPE][] > { diff --git a/src/schema/base/literal.ts b/src/schema/base/literal.ts index 5f3ff4c..83aced8 100644 --- a/src/schema/base/literal.ts +++ b/src/schema/base/literal.ts @@ -11,7 +11,7 @@ import camelcase from 'camelcase' import Macroable from '@poppinss/macroable' import type { LiteralNode, RefsStore } from '@vinejs/compiler/types' -import { OTYPE, COTYPE, PARSE, VALIDATION } from '../../symbols.js' +import { OTYPE, COTYPE, PARSE, VALIDATION, ITYPE } from '../../symbols.js' import type { Parser, Validation, @@ -31,9 +31,9 @@ import { helpers } from '../../vine/helpers.js' /** * Base schema type with only modifiers applicable on all the schema types. */ -abstract class BaseModifiersType +abstract class BaseModifiersType extends Macroable - implements ConstructableSchema + implements ConstructableSchema { /** * Each subtype should implement the compile method that returns @@ -46,6 +46,11 @@ abstract class BaseModifiersType */ abstract clone(): this + /** + * Define the input type of the schema + */ + declare [ITYPE]: Input; + /** * The output value of the field. The property points to a type only * and not the real value. @@ -86,7 +91,8 @@ abstract class BaseModifiersType /** * Modifies the schema type to allow null values */ -class NullableModifier> extends BaseModifiersType< +class NullableModifier> extends BaseModifiersType< + Schema[typeof ITYPE] | null, Schema[typeof OTYPE] | null, Schema[typeof COTYPE] | null > { @@ -118,7 +124,8 @@ class NullableModifier> extends BaseM /** * Modifies the schema type to allow undefined values */ -class OptionalModifier> extends BaseModifiersType< +class OptionalModifier> extends BaseModifiersType< + Schema[typeof ITYPE] | undefined | null, Schema[typeof OTYPE] | undefined, Schema[typeof COTYPE] | undefined > { @@ -328,9 +335,9 @@ class OptionalModifier> extends BaseM * Modifies the schema type to allow custom transformed values */ class TransformModifier< - Schema extends BaseModifiersType, + Schema extends BaseModifiersType, Output, -> extends BaseModifiersType { +> extends BaseModifiersType { /** * The output value of the field. The property points to a type only * and not the real value. @@ -369,7 +376,8 @@ class TransformModifier< * The base type for creating a custom literal type. Literal type * is a schema type that has no children elements. */ -export abstract class BaseLiteralType extends BaseModifiersType< +export abstract class BaseLiteralType extends BaseModifiersType< + Input, Output, CamelCaseOutput > { diff --git a/src/schema/base/main.ts b/src/schema/base/main.ts index 29e2cca..48a21b3 100644 --- a/src/schema/base/main.ts +++ b/src/schema/base/main.ts @@ -9,7 +9,7 @@ import type { CompilerNodes, RefsStore } from '@vinejs/compiler/types' -import { OTYPE, COTYPE, PARSE, VALIDATION } from '../../symbols.js' +import { ITYPE, OTYPE, COTYPE, PARSE, VALIDATION } from '../../symbols.js' import type { Parser, Validation, @@ -23,9 +23,9 @@ import Macroable from '@poppinss/macroable' /** * Base schema type with only modifiers applicable on all the schema types. */ -export abstract class BaseModifiersType +export abstract class BaseModifiersType extends Macroable - implements ConstructableSchema + implements ConstructableSchema { /** * Each subtype should implement the compile method that returns @@ -38,6 +38,11 @@ export abstract class BaseModifiersType */ abstract clone(): this + /** + * Define the input type of the schema + */ + declare [ITYPE]: Input; + /** * The output value of the field. The property points to a type only * and not the real value. @@ -68,7 +73,8 @@ export abstract class BaseModifiersType /** * Modifies the schema type to allow null values */ -class NullableModifier> extends BaseModifiersType< +class NullableModifier> extends BaseModifiersType< + Schema[typeof ITYPE] | null, Schema[typeof OTYPE] | null, Schema[typeof COTYPE] | null > { @@ -102,7 +108,8 @@ class NullableModifier> extends BaseM /** * Modifies the schema type to allow undefined values */ -class OptionalModifier> extends BaseModifiersType< +class OptionalModifier> extends BaseModifiersType< + Schema[typeof ITYPE] | undefined | null, Schema[typeof OTYPE] | undefined, Schema[typeof COTYPE] | undefined > { @@ -137,7 +144,8 @@ class OptionalModifier> extends BaseM * The BaseSchema class abstracts the repetitive parts of creating * a custom schema type. */ -export abstract class BaseType extends BaseModifiersType< +export abstract class BaseType extends BaseModifiersType< + Input, Output, CamelCaseOutput > { diff --git a/src/schema/boolean/main.ts b/src/schema/boolean/main.ts index 54a69ba..7580922 100644 --- a/src/schema/boolean/main.ts +++ b/src/schema/boolean/main.ts @@ -16,7 +16,7 @@ import type { FieldOptions, Validation } from '../../types.js' /** * VineBoolean represents a boolean value in the validation schema. */ -export class VineBoolean extends BaseLiteralType { +export class VineBoolean extends BaseLiteralType { /** * Default collection of boolean rules */ diff --git a/src/schema/builder.ts b/src/schema/builder.ts index 4395866..15f0555 100644 --- a/src/schema/builder.ts +++ b/src/schema/builder.ts @@ -11,6 +11,7 @@ import Macroable from '@poppinss/macroable' import { VineAny } from './any/main.js' import { VineEnum } from './enum/main.js' +import { VineDate } from './date/main.js' import { union } from './union/builder.js' import { VineTuple } from './tuple/main.js' import { VineArray } from './array/main.js' @@ -25,9 +26,8 @@ import { VineAccepted } from './accepted/main.js' import { group } from './object/group_builder.js' import { VineNativeEnum } from './enum/native_enum.js' import { VineUnionOfTypes } from './union_of_types/main.js' -import { OTYPE, COTYPE, IS_OF_TYPE, UNIQUE_NAME } from '../symbols.js' +import { ITYPE, OTYPE, COTYPE, IS_OF_TYPE, UNIQUE_NAME } from '../symbols.js' import type { DateFieldOptions, EnumLike, FieldContext, SchemaTypes } from '../types.js' -import { VineDate } from './date/main.js' /** * Schema builder exposes methods to construct a Vine schema. You may @@ -94,6 +94,9 @@ export class SchemaBuilder extends Macroable { object>(properties: Properties) { return new VineObject< Properties, + { + [K in keyof Properties]: Properties[K][typeof ITYPE] + }, { [K in keyof Properties]: Properties[K][typeof OTYPE] }, @@ -117,6 +120,7 @@ export class SchemaBuilder extends Macroable { tuple(schemas: [...Schema]) { return new VineTuple< Schema, + { [K in keyof Schema]: Schema[K][typeof ITYPE] }, { [K in keyof Schema]: Schema[K][typeof OTYPE] }, { [K in keyof Schema]: Schema[K][typeof COTYPE] } >(schemas) diff --git a/src/schema/date/main.ts b/src/schema/date/main.ts index 5e4b0ea..74e674c 100644 --- a/src/schema/date/main.ts +++ b/src/schema/date/main.ts @@ -39,7 +39,7 @@ import type { * VineDate represents a Date object created by parsing a * string or number value as a date. */ -export class VineDate extends BaseLiteralType { +export class VineDate extends BaseLiteralType { /** * Available VineDate rules */ diff --git a/src/schema/enum/main.ts b/src/schema/enum/main.ts index e1c03c8..268792b 100644 --- a/src/schema/enum/main.ts +++ b/src/schema/enum/main.ts @@ -16,6 +16,7 @@ import type { FieldContext, FieldOptions, Validation } from '../../types.js' * against a pre-defined choices list. */ export class VineEnum extends BaseLiteralType< + Values[number], Values[number], Values[number] > { diff --git a/src/schema/enum/native_enum.ts b/src/schema/enum/native_enum.ts index 7562c8a..6254869 100644 --- a/src/schema/enum/native_enum.ts +++ b/src/schema/enum/native_enum.ts @@ -19,6 +19,7 @@ import type { EnumLike, FieldOptions, Validation } from '../../types.js' * object */ export class VineNativeEnum extends BaseLiteralType< + Values[keyof Values], Values[keyof Values], Values[keyof Values] > { diff --git a/src/schema/literal/main.ts b/src/schema/literal/main.ts index 168203c..48dc634 100644 --- a/src/schema/literal/main.ts +++ b/src/schema/literal/main.ts @@ -14,7 +14,7 @@ import type { FieldOptions, Validation } from '../../types.js' /** * VineLiteral represents a type that matches an exact value */ -export class VineLiteral extends BaseLiteralType { +export class VineLiteral extends BaseLiteralType { /** * Default collection of literal rules */ diff --git a/src/schema/number/main.ts b/src/schema/number/main.ts index 139eb33..850a19d 100644 --- a/src/schema/number/main.ts +++ b/src/schema/number/main.ts @@ -26,7 +26,7 @@ import { /** * VineNumber represents a numeric value in the validation schema. */ -export class VineNumber extends BaseLiteralType { +export class VineNumber extends BaseLiteralType { protected declare options: FieldOptions & { strict?: boolean } /** diff --git a/src/schema/object/conditional.ts b/src/schema/object/conditional.ts index 6d546cc..abf4d83 100644 --- a/src/schema/object/conditional.ts +++ b/src/schema/object/conditional.ts @@ -9,7 +9,7 @@ import type { ConditionalFn, ObjectGroupNode, RefsStore } from '@vinejs/compiler/types' -import { OTYPE, COTYPE, PARSE } from '../../symbols.js' +import { OTYPE, COTYPE, PARSE, ITYPE } from '../../symbols.js' import type { ParserOptions, SchemaTypes } from '../../types.js' /** @@ -18,9 +18,11 @@ import type { ParserOptions, SchemaTypes } from '../../types.js' */ export class GroupConditional< Properties extends Record, + Input, Output, CamelCaseOutput, > { + declare [ITYPE]: Input; declare [OTYPE]: Output; declare [COTYPE]: CamelCaseOutput diff --git a/src/schema/object/group.ts b/src/schema/object/group.ts index fb58d8b..bd69a44 100644 --- a/src/schema/object/group.ts +++ b/src/schema/object/group.ts @@ -11,7 +11,7 @@ import { ObjectGroupNode, RefsStore } from '@vinejs/compiler/types' import { messages } from '../../defaults.js' import { GroupConditional } from './conditional.js' -import { OTYPE, COTYPE, PARSE } from '../../symbols.js' +import { ITYPE, OTYPE, COTYPE, PARSE } from '../../symbols.js' import type { ParserOptions, UnionNoMatchCallback } from '../../types.js' /** @@ -19,7 +19,8 @@ import type { ParserOptions, UnionNoMatchCallback } from '../../types.js' * condition returns a set of object properties to merge into the * existing object. */ -export class ObjectGroup> { +export class ObjectGroup> { + declare [ITYPE]: Conditional[typeof ITYPE]; declare [OTYPE]: Conditional[typeof OTYPE]; declare [COTYPE]: Conditional[typeof COTYPE] diff --git a/src/schema/object/group_builder.ts b/src/schema/object/group_builder.ts index 175b349..68a31e9 100644 --- a/src/schema/object/group_builder.ts +++ b/src/schema/object/group_builder.ts @@ -8,16 +8,16 @@ */ import { ObjectGroup } from './group.js' -import { OTYPE, COTYPE } from '../../symbols.js' import { CamelCase } from '../camelcase_types.js' import { GroupConditional } from './conditional.js' +import { OTYPE, COTYPE, ITYPE } from '../../symbols.js' import type { FieldContext, SchemaTypes } from '../../types.js' /** * Create an object group. Groups are used to conditionally merge properties * to an existing object. */ -export function group>( +export function group>( conditionals: Conditional[] ) { return new ObjectGroup(conditionals) @@ -32,6 +32,9 @@ group.if = function groupIf>( ) { return new GroupConditional< Properties, + { + [K in keyof Properties]: Properties[K][typeof ITYPE] + }, { [K in keyof Properties]: Properties[K][typeof OTYPE] }, @@ -49,6 +52,9 @@ group.else = function groupElse>( ) { return new GroupConditional< Properties, + { + [K in keyof Properties]: Properties[K][typeof ITYPE] + }, { [K in keyof Properties]: Properties[K][typeof OTYPE] }, diff --git a/src/schema/object/main.ts b/src/schema/object/main.ts index 73041fc..32e7b4d 100644 --- a/src/schema/object/main.ts +++ b/src/schema/object/main.ts @@ -12,16 +12,16 @@ import type { ObjectNode, RefsStore } from '@vinejs/compiler/types' import { ObjectGroup } from './group.js' import { GroupConditional } from './conditional.js' -import { BaseModifiersType, BaseType } from '../base/main.js' -import { OTYPE, COTYPE, PARSE, UNIQUE_NAME, IS_OF_TYPE } from '../../symbols.js' +import { BaseType, BaseModifiersType } from '../base/main.js' +import { OTYPE, COTYPE, PARSE, UNIQUE_NAME, IS_OF_TYPE, ITYPE } from '../../symbols.js' import type { Validation, SchemaTypes, FieldOptions, ParserOptions } from '../../types.js' /** * Converts schema properties to camelCase */ export class VineCamelCaseObject< - Schema extends VineObject, -> extends BaseModifiersType { + Schema extends VineObject, +> extends BaseModifiersType { #schema: Schema; /** @@ -64,9 +64,10 @@ export class VineCamelCaseObject< */ export class VineObject< Properties extends Record, + Input, Output, CamelCaseOutput, -> extends BaseType { +> extends BaseType { /** * Object properties */ @@ -75,7 +76,7 @@ export class VineObject< /** * Object groups to merge based on conditionals */ - #groups: ObjectGroup>[] = [] + #groups: ObjectGroup>[] = [] /** * Whether or not to allow unknown properties @@ -119,12 +120,14 @@ export class VineObject< */ allowUnknownProperties(): VineObject< Properties, + Input & { [K: string]: Value }, Output & { [K: string]: Value }, CamelCaseOutput & { [K: string]: Value } > { this.#allowUnknownProperties = true return this as VineObject< Properties, + Input & { [K: string]: Value }, Output & { [K: string]: Value }, CamelCaseOutput & { [K: string]: Value } > @@ -134,12 +137,18 @@ export class VineObject< * Merge a union to the object groups. The union can be a "vine.union" * with objects, or a "vine.object.union" with properties. */ - merge>>( + merge>>( group: Group - ): VineObject { + ): VineObject< + Properties, + Input & Group[typeof ITYPE], + Output & Group[typeof OTYPE], + CamelCaseOutput & Group[typeof COTYPE] + > { this.#groups.push(group) return this as VineObject< Properties, + Input & Group[typeof ITYPE], Output & Group[typeof OTYPE], CamelCaseOutput & Group[typeof COTYPE] > @@ -149,7 +158,7 @@ export class VineObject< * Clone object */ clone(): this { - const cloned = new VineObject( + const cloned = new VineObject( this.getProperties(), this.cloneOptions(), this.cloneValidations() diff --git a/src/schema/record/main.ts b/src/schema/record/main.ts index 3afed80..e21b03b 100644 --- a/src/schema/record/main.ts +++ b/src/schema/record/main.ts @@ -11,7 +11,7 @@ import camelcase from 'camelcase' import { RefsStore, RecordNode } from '@vinejs/compiler/types' import { BaseType } from '../base/main.js' -import { OTYPE, COTYPE, PARSE, UNIQUE_NAME, IS_OF_TYPE } from '../../symbols.js' +import { ITYPE, OTYPE, COTYPE, PARSE, UNIQUE_NAME, IS_OF_TYPE } from '../../symbols.js' import type { FieldOptions, ParserOptions, SchemaTypes, Validation } from '../../types.js' import { fixedLengthRule, maxLengthRule, minLengthRule, validateKeysRule } from './rules.js' @@ -20,6 +20,7 @@ import { fixedLengthRule, maxLengthRule, minLengthRule, validateKeysRule } from * keys are unknown */ export class VineRecord extends BaseType< + { [K: string]: Schema[typeof ITYPE] }, { [K: string]: Schema[typeof OTYPE] }, { [K: string]: Schema[typeof COTYPE] } > { diff --git a/src/schema/string/main.ts b/src/schema/string/main.ts index 99342d1..b0fe14b 100644 --- a/src/schema/string/main.ts +++ b/src/schema/string/main.ts @@ -21,11 +21,14 @@ import type { import { inRule, urlRule, + jwtRule, uuidRule, trimRule, + ibanRule, alphaRule, emailRule, notInRule, + asciiRule, regexRule, sameAsRule, mobileRule, @@ -44,22 +47,19 @@ import { creditCardRule, postalCodeRule, fixedLengthRule, - alphaNumericRule, - normalizeEmailRule, - asciiRule, - ibanRule, - jwtRule, coordinatesRule, toUpperCaseRule, toLowerCaseRule, toCamelCaseRule, normalizeUrlRule, + alphaNumericRule, + normalizeEmailRule, } from './rules.js' /** * VineString represents a string value in the validation schema. */ -export class VineString extends BaseLiteralType { +export class VineString extends BaseLiteralType { static rules = { in: inRule, jwt: jwtRule, diff --git a/src/schema/tuple/main.ts b/src/schema/tuple/main.ts index c9b132f..816616d 100644 --- a/src/schema/tuple/main.ts +++ b/src/schema/tuple/main.ts @@ -20,9 +20,10 @@ import type { FieldOptions, ParserOptions, SchemaTypes, Validation } from '../.. */ export class VineTuple< Schema extends SchemaTypes[], + Input extends any[], Output extends any[], CamelCaseOutput extends any[], -> extends BaseType { +> extends BaseType { #schemas: [...Schema] /** @@ -53,12 +54,14 @@ export class VineTuple< */ allowUnknownProperties(): VineTuple< Schema, + [...Input, ...Value[]], [...Output, ...Value[]], [...CamelCaseOutput, ...Value[]] > { this.#allowUnknownProperties = true return this as unknown as VineTuple< Schema, + [...Input, ...Value[]], [...Output, ...Value[]], [...CamelCaseOutput, ...Value[]] > @@ -68,7 +71,7 @@ export class VineTuple< * Clone object */ clone(): this { - const cloned = new VineTuple( + const cloned = new VineTuple( this.#schemas.map((schema) => schema.clone()) as Schema, this.cloneOptions(), this.cloneValidations() diff --git a/src/schema/union/conditional.ts b/src/schema/union/conditional.ts index 202e115..128614c 100644 --- a/src/schema/union/conditional.ts +++ b/src/schema/union/conditional.ts @@ -9,7 +9,7 @@ import { ConditionalFn, RefsStore, UnionNode } from '@vinejs/compiler/types' -import { OTYPE, COTYPE, PARSE } from '../../symbols.js' +import { ITYPE, OTYPE, COTYPE, PARSE } from '../../symbols.js' import type { ParserOptions, SchemaTypes } from '../../types.js' /** @@ -17,6 +17,7 @@ import type { ParserOptions, SchemaTypes } from '../../types.js' * with a schema */ export class UnionConditional { + declare [ITYPE]: Schema[typeof ITYPE]; declare [OTYPE]: Schema[typeof OTYPE]; declare [COTYPE]: Schema[typeof COTYPE] diff --git a/src/schema/union/main.ts b/src/schema/union/main.ts index 005cd94..df5c86c 100644 --- a/src/schema/union/main.ts +++ b/src/schema/union/main.ts @@ -12,11 +12,11 @@ import { RefsStore, UnionNode } from '@vinejs/compiler/types' import { messages } from '../../defaults.js' import { UnionConditional } from './conditional.js' -import { OTYPE, COTYPE, PARSE } from '../../symbols.js' +import { ITYPE, OTYPE, COTYPE, PARSE } from '../../symbols.js' import type { - ConstructableSchema, - ParserOptions, SchemaTypes, + ParserOptions, + ConstructableSchema, UnionNoMatchCallback, } from '../../types.js' @@ -25,8 +25,14 @@ import type { * of conditionals and each condition has an associated schema */ export class VineUnion> - implements ConstructableSchema + implements + ConstructableSchema< + Conditional[typeof ITYPE], + Conditional[typeof OTYPE], + Conditional[typeof COTYPE] + > { + declare [ITYPE]: Conditional[typeof ITYPE]; declare [OTYPE]: Conditional[typeof OTYPE]; declare [COTYPE]: Conditional[typeof COTYPE] diff --git a/src/schema/union_of_types/main.ts b/src/schema/union_of_types/main.ts index 05e7652..5fb7914 100644 --- a/src/schema/union_of_types/main.ts +++ b/src/schema/union_of_types/main.ts @@ -11,7 +11,7 @@ import camelcase from 'camelcase' import type { RefsStore, UnionNode } from '@vinejs/compiler/types' import { messages } from '../../defaults.js' -import { OTYPE, COTYPE, PARSE, IS_OF_TYPE } from '../../symbols.js' +import { ITYPE, OTYPE, COTYPE, PARSE, IS_OF_TYPE } from '../../symbols.js' import type { SchemaTypes, ParserOptions, @@ -24,8 +24,9 @@ import type { * of conditionals and each condition has an associated schema */ export class VineUnionOfTypes - implements ConstructableSchema + implements ConstructableSchema { + declare [ITYPE]: Schema[typeof ITYPE]; declare [OTYPE]: Schema[typeof OTYPE]; declare [COTYPE]: Schema[typeof COTYPE] diff --git a/src/symbols.ts b/src/symbols.ts index ca31de7..8927d7b 100644 --- a/src/symbols.ts +++ b/src/symbols.ts @@ -23,6 +23,11 @@ export const IS_OF_TYPE = Symbol.for('is_of_type') */ export const PARSE = Symbol.for('parse') +/** + * The symbol for the opaque input type + */ +export const ITYPE = Symbol.for('opaque_input_type') + /** * The symbol for the opaque type */ diff --git a/src/types.ts b/src/types.ts index 4a8f924..c692fb0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -26,7 +26,7 @@ import type { import type { helpers } from './vine/helpers.js' import type { ValidationError } from './errors/validation_error.js' -import type { OTYPE, COTYPE, PARSE, VALIDATION, UNIQUE_NAME, IS_OF_TYPE } from './symbols.js' +import type { OTYPE, COTYPE, PARSE, VALIDATION, UNIQUE_NAME, IS_OF_TYPE, ITYPE } from './symbols.js' /** * Options accepted by the mobile number validation @@ -112,7 +112,8 @@ export type ValidationFields = Record * Constructable schema type refers to any type that can be * constructed for type inference and compiler output */ -export interface ConstructableSchema { +export interface ConstructableSchema { + [ITYPE]: Inputs [OTYPE]: Output [COTYPE]: CamelCaseOutput [PARSE](propertyName: string, refs: RefsStore, options: ParserOptions): CompilerNodes @@ -124,7 +125,7 @@ export interface ConstructableSchema { [UNIQUE_NAME]?: string [IS_OF_TYPE]?: (value: unknown, field: FieldContext) => boolean } -export type SchemaTypes = ConstructableSchema +export type SchemaTypes = ConstructableSchema /** * Representation of a function that performs validation. @@ -270,6 +271,7 @@ export type ValidationOptions | undefined> * Infers the schema type */ export type Infer = Schema[typeof OTYPE] +export type InferInput = Schema[typeof ITYPE] /** * Comparison operators supported by requiredWhen diff --git a/src/vine/main.ts b/src/vine/main.ts index 67d12a8..9691cce 100644 --- a/src/vine/main.ts +++ b/src/vine/main.ts @@ -14,6 +14,7 @@ import { SimpleMessagesProvider } from '../messages_provider/simple_messages_pro import { VineValidator } from './validator.js' import { fields, messages } from '../defaults.js' +import { SimpleErrorReporter } from '../reporters/simple_error_reporter.js' import type { Infer, SchemaTypes, @@ -22,7 +23,6 @@ import type { ErrorReporterContract, MessagesProviderContact, } from '../types.js' -import { SimpleErrorReporter } from '../reporters/simple_error_reporter.js' /** * Validate user input with type-safety using a pre-compiled schema. diff --git a/src/vine/validator.ts b/src/vine/validator.ts index 5d813f8..c41c56c 100644 --- a/src/vine/validator.ts +++ b/src/vine/validator.ts @@ -11,7 +11,7 @@ import { Compiler, refsBuilder } from '@vinejs/compiler' import type { MessagesProviderContact } from '@vinejs/compiler/types' import { messages } from '../defaults.js' -import { OTYPE, PARSE } from '../symbols.js' +import { ITYPE, OTYPE, PARSE } from '../symbols.js' import type { Infer, SchemaTypes, @@ -40,6 +40,7 @@ export class VineValidator< /** * Reference to static types */ + declare [ITYPE]: Schema[typeof ITYPE]; declare [OTYPE]: Schema[typeof OTYPE] /** diff --git a/tests/integration/validator.spec.ts b/tests/integration/validator.spec.ts index fe72c6c..6427641 100644 --- a/tests/integration/validator.spec.ts +++ b/tests/integration/validator.spec.ts @@ -20,6 +20,7 @@ import vine, { VineLiteral, VineBoolean, } from '../../index.js' +import { InferInput } from '../../src/types.js' test.group('Validator | metadata', () => { test('pass metadata to the validation pipeline', async ({ assert }) => { @@ -141,7 +142,7 @@ test.group('Validator | extend schema classes', () => { }) test('extend VineObject class', ({ assert }) => { - VineObject.macro('validatesEmail' as any, function (this: VineObject) { + VineObject.macro('validatesEmail' as any, function (this: VineObject) { return 'email' in this.getProperties() }) @@ -165,7 +166,7 @@ test.group('Validator | extend schema classes', () => { }) test('extend VineTuple class', ({ assert }) => { - VineTuple.macro('atLeastOne' as any, function (this: VineTuple) { + VineTuple.macro('atLeastOne' as any, function (this: VineTuple) { return true }) diff --git a/tests/types/types.spec.ts b/tests/types/types.spec.ts index 6831d3a..65ee667 100644 --- a/tests/types/types.spec.ts +++ b/tests/types/types.spec.ts @@ -9,7 +9,7 @@ import { test } from '@japa/runner' import { Vine } from '../../src/vine/main.js' -import { Infer, ValidationOptions } from '../../src/types.js' +import { Infer, InferInput, ValidationOptions } from '../../src/types.js' const vine = new Vine() @@ -21,6 +21,13 @@ test.group('Types | Flat schema', () => { is_admin: vine.boolean(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + username: string + email: string + is_admin: boolean | string | number + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ username: string @@ -36,6 +43,13 @@ test.group('Types | Flat schema', () => { is_admin: vine.boolean(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + username: string + email: string | null + is_admin: boolean | string | number + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ username: string @@ -51,6 +65,13 @@ test.group('Types | Flat schema', () => { is_admin: vine.boolean().optional(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + username: string + email: string | null | undefined + is_admin: boolean | string | number | undefined | null + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ username: string @@ -75,6 +96,13 @@ test.group('Types | Flat schema', () => { }), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + username: string + email: string | null | undefined + is_admin: boolean | string | number | null + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ username: string @@ -101,6 +129,13 @@ test.group('Types | Flat schema', () => { }) .toCamelCase() + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + username: string + email: string | null | undefined + is_admin: boolean | string | number | null + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ username: string @@ -132,6 +167,13 @@ test.group('Types | Flat schema', () => { .toCamelCase() .clone() + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + username: string + email: string | null | undefined + is_admin: boolean | string | number | null + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ username: string @@ -153,6 +195,17 @@ test.group('Types | Nested schema', () => { }), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + username: string + email: string + is_admin: boolean | string | number + profile: { + twitter_handle: string + github_username: string + } + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ username: string @@ -178,6 +231,17 @@ test.group('Types | Nested schema', () => { .nullable(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + username: string + email: string + is_admin: boolean | string | number + profile: { + twitter_handle: string + github_username: string + } | null + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ username: string @@ -203,6 +267,17 @@ test.group('Types | Nested schema', () => { .nullable(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + username: string + email: string | undefined | null + is_admin: boolean | string | number + profile: { + twitter_handle: string + github_username: string | undefined | null + } | null + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ username: string @@ -231,6 +306,17 @@ test.group('Types | Nested schema', () => { .nullable(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + username: string + email: string | undefined | null + is_admin: boolean | string | number + profile: { + twitter_handle: string + github_username: string | null + } | null + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ username: string @@ -258,6 +344,17 @@ test.group('Types | Nested schema', () => { }) .toCamelCase() + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + username: string + email: string | undefined | null + is_admin: boolean | string | number + profile: { + twitter_handle: string + github_username: string | null + } | null + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ username: string @@ -284,6 +381,17 @@ test.group('Types | Nested schema', () => { .nullable(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + username: string + email: string | undefined | null + is_admin: boolean | string | number + profile: { + twitter_handle: string + github_username: string | null + } | null + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ username: string @@ -313,6 +421,17 @@ test.group('Types | Nested schema', () => { .clone() .toCamelCase() + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + username: string + email: string | undefined | null + is_admin: boolean | string | number + profile: { + twitter_handle: string + github_username: string | null + } | null + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ username: string @@ -345,6 +464,22 @@ test.group('Types | Object groups', () => { }) .merge(guideSchema) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf< + { + visitor_name: string + } & ( + | { + hiring_guide: true + guide_name: string + fees: string + } + | { + hiring_guide: false + } + ) + >() + type Schema = Infer expectTypeOf().toEqualTypeOf< { @@ -381,6 +516,23 @@ test.group('Types | Object groups', () => { .merge(guideSchema) .nullable() + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf< + | ({ + visitor_name: string + } & ( + | { + hiring_guide: true + guide_name: string + fees: string + } + | { + hiring_guide: false + } + )) + | null + >() + type Schema = Infer expectTypeOf().toEqualTypeOf< | ({ @@ -418,6 +570,24 @@ test.group('Types | Object groups', () => { .merge(guideSchema) .optional() + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf< + | ({ + visitor_name: string + } & ( + | { + hiring_guide: true + guide_name: string + fees: string + } + | { + hiring_guide: false + } + )) + | null + | undefined + >() + type Schema = Infer expectTypeOf().toEqualTypeOf< | ({ @@ -468,6 +638,34 @@ test.group('Types | Object groups', () => { .merge(guideSchema) .merge(monumentSchema) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf< + { + visitor_name: string + } & ( + | { + hiring_guide: true + guide_name: string + fees: string + } + | { + hiring_guide: false + } + ) & + ( + | { + monument: 'foo' + available_transport: 'bus' | 'train' + has_free_entry: false + } + | { + monument: 'bar' + available_transport: 'bus' | 'car' + has_free_entry: true + } + ) + >() + type Schema = Infer expectTypeOf().toEqualTypeOf< { @@ -516,6 +714,22 @@ test.group('Types | Object groups', () => { .merge(guideSchema) .toCamelCase() + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf< + { + visitor_name: string + } & ( + | { + hiring_guide: true + guide_name: string + fees: string + } + | { + hiring_guide: false + } + ) + >() + type Schema = Infer expectTypeOf().toEqualTypeOf< { @@ -553,6 +767,26 @@ test.group('Types | Object groups', () => { .allowUnknownProperties() .optional() + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf< + | ({ + visitor_name: string + } & ( + | { + hiring_guide: true + guide_name: string + fees: string + } + | { + hiring_guide: false + } + ) & { + [K: string]: unknown + }) + | null + | undefined + >() + type Schema = Infer expectTypeOf().toEqualTypeOf< | (({ @@ -592,6 +826,26 @@ test.group('Types | Object groups', () => { .optional() .clone() + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf< + | ({ + visitor_name: string + } & ( + | { + hiring_guide: true + guide_name: string + fees: string + } + | { + hiring_guide: false + } + ) & { + [K: string]: unknown + }) + | null + | undefined + >() + type Schema = Infer expectTypeOf().toEqualTypeOf< | (({ @@ -622,6 +876,14 @@ test.group('Types | Arrays', () => { ), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + contacts: { + email: string + is_primary: boolean | number | string + }[] + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ contacts: { @@ -643,6 +905,16 @@ test.group('Types | Arrays', () => { .nullable(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + contacts: + | { + email: string + is_primary: boolean | number | string + }[] + | null + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ contacts: @@ -667,6 +939,17 @@ test.group('Types | Arrays', () => { .optional(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + contacts: + | { + email: string + is_primary: boolean | number | string + }[] + | null + | undefined + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ contacts: @@ -694,6 +977,17 @@ test.group('Types | Arrays', () => { }) .toCamelCase() + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + contacts: + | { + email: string + is_primary: boolean | number | string + }[] + | null + | undefined + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ contacts: @@ -723,6 +1017,17 @@ test.group('Types | Arrays', () => { .toCamelCase() .clone() + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + contacts: + | { + email: string + is_primary: boolean | number | string + }[] + | null + | undefined + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ contacts: @@ -742,6 +1047,11 @@ test.group('Types | Tuples', () => { colors: vine.tuple([vine.string(), vine.string(), vine.string()]), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + colors: [string, string, string] + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ colors: [string, string, string] @@ -753,6 +1063,11 @@ test.group('Types | Tuples', () => { colors: vine.tuple([vine.string(), vine.string(), vine.string()]).nullable(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + colors: [string, string, string] | null + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ colors: [string, string, string] | null @@ -764,6 +1079,11 @@ test.group('Types | Tuples', () => { colors: vine.tuple([vine.string(), vine.string(), vine.string()]).nullable().optional(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + colors: [string, string, string] | null | undefined + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ colors: [string, string, string] | null | undefined @@ -779,6 +1099,11 @@ test.group('Types | Tuples', () => { .optional(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + colors: [string, string, string, ...unknown[]] | null | undefined + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ colors: [string, string, string, ...unknown[]] | null | undefined @@ -802,6 +1127,11 @@ test.group('Types | Tuples', () => { }) .toCamelCase() + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + colors: [string, string, { primary_1: string }, ...unknown[]] | null | undefined + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ colors: [string, string, { primary1: string }, ...unknown[]] | null | undefined @@ -828,6 +1158,11 @@ test.group('Types | Tuples', () => { }) .toCamelCase() + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + colors: [string, string, { primary_1: string }, ...unknown[]] | null | undefined + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ colors: [string, string, { primary1: string }, ...unknown[]] | null | undefined @@ -856,6 +1191,19 @@ test.group('Types | Union', () => { ]), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + contact: + | { + email: string + otp: string + } + | { + username: string + password: string + } + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ contact: @@ -899,6 +1247,21 @@ test.group('Types | Union', () => { ]), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + contact: + | { + email: string + } + | { + otp: string + } + | { + username: string + password: string + } + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ contact: @@ -950,6 +1313,21 @@ test.group('Types | Union', () => { .clone(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + contact: + | { + email: string + } + | { + otp: string + } + | { + username: string + password: string + } + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ contact: @@ -973,6 +1351,13 @@ test.group('Types | Record', () => { colors: vine.record(vine.string()), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + colors: { + [K: string]: string + } + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ colors: { @@ -986,6 +1371,13 @@ test.group('Types | Record', () => { colors: vine.record(vine.string()).nullable(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + colors: { + [K: string]: string + } | null + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ colors: { @@ -999,6 +1391,16 @@ test.group('Types | Record', () => { colors: vine.record(vine.string()).optional().nullable(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + colors: + | { + [K: string]: string + } + | null + | undefined + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ colors: @@ -1051,6 +1453,11 @@ test.group('Types | Enum', () => { }) .clone() + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + role: 'admin' | 'moderator' | 'writer' + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ role: 'admin' | 'moderator' | 'writer' @@ -1062,6 +1469,11 @@ test.group('Types | Enum', () => { role: vine.enum(['admin', 'moderator', 'writer']).nullable(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + role: 'admin' | 'moderator' | 'writer' | null + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ role: 'admin' | 'moderator' | 'writer' | null @@ -1073,6 +1485,11 @@ test.group('Types | Enum', () => { role: vine.enum(['admin', 'moderator', 'writer']).nullable().optional(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + role: 'admin' | 'moderator' | 'writer' | null | undefined + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ role: 'admin' | 'moderator' | 'writer' | null | undefined @@ -1090,6 +1507,11 @@ test.group('Types | Enum', () => { role: vine.enum(Role).nullable().optional(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + role: Role | null | undefined + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ role: Role | null | undefined @@ -1109,6 +1531,11 @@ test.group('Types | Enum', () => { }) .clone() + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + role: Role | null | undefined + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ role: Role | null | undefined @@ -1122,6 +1549,11 @@ test.group('Types | Accepted', () => { terms_and_conditions: vine.accepted(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + terms_and_conditions: 'on' | '1' | 'yes' | 'true' | true | 1 + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ terms_and_conditions: true @@ -1133,6 +1565,11 @@ test.group('Types | Accepted', () => { terms_and_conditions: vine.accepted().nullable(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + terms_and_conditions: 'on' | '1' | 'yes' | 'true' | true | 1 | null + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ terms_and_conditions: true | null @@ -1144,6 +1581,11 @@ test.group('Types | Accepted', () => { terms_and_conditions: vine.accepted().nullable().optional(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + terms_and_conditions: 'on' | '1' | 'yes' | 'true' | true | 1 | null | undefined + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ terms_and_conditions: true | null | undefined @@ -1161,6 +1603,11 @@ test.group('Types | Accepted', () => { }), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + terms_and_conditions: 'on' | '1' | 'yes' | 'true' | true | 1 | null | undefined + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ terms_and_conditions: boolean @@ -1172,6 +1619,11 @@ test.group('Types | Accepted', () => { terms_and_conditions: vine.accepted().clone().nullable().optional(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + terms_and_conditions: 'on' | '1' | 'yes' | 'true' | true | 1 | null | undefined + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ terms_and_conditions: true | null | undefined @@ -1185,6 +1637,11 @@ test.group('Types | Any', () => { secret_message: vine.any(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + secret_message: any + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ secret_message: any @@ -1196,6 +1653,11 @@ test.group('Types | Any', () => { secret_message: vine.any().nullable(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + secret_message: any + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ secret_message: any @@ -1207,6 +1669,11 @@ test.group('Types | Any', () => { secret_message: vine.any().optional().nullable(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + secret_message: any + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ secret_message: any @@ -1220,6 +1687,11 @@ test.group('Types | Any', () => { }), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + secret_message: any + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ secret_message: string @@ -1236,6 +1708,11 @@ test.group('Types | Any', () => { .clone(), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + secret_message: any + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ secret_message: string @@ -1249,6 +1726,11 @@ test.group('Types | UnionOfTypes', () => { health_check: vine.unionOfTypes([vine.boolean(), vine.string()]), }) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + health_check: string | number | boolean + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ health_check: boolean | string @@ -1262,6 +1744,11 @@ test.group('Types | UnionOfTypes', () => { }) .clone() + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + health_check: string | number | boolean + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ health_check: boolean | string @@ -1279,6 +1766,13 @@ test.group('Types | compiled schema', () => { }) ) + type InputsSchema = InferInput + expectTypeOf().toEqualTypeOf<{ + username: string + email: string + is_admin: boolean | number | string + }>() + type Schema = Infer expectTypeOf().toEqualTypeOf<{ username: string