Skip to content

@pcvl/schema / NumberInterpretedSchema

Class: NumberInterpretedSchema<Input, Output> ​

Defined in: schemas/numberInterpretedSchema.ts:53

Accepts a JavaScript number — floats and integers alike, since JavaScript has only one numeric type. Build one with pvl.number().

NaN is rejected even though typeof NaN === 'number': an accepted NaN would silently fail every .min()/.max() comparison instead of producing a clear Issue. Infinity and -Infinity are accepted — there is no built-in finiteness constraint. Arbitrary-precision integers belong to pvl.bigint(), and this schema never accepts or produces one.

Constraints are checked in the order they were chained, and every one that fails is reported. They chain in any order with the shared modifiers — a constraint keeps whatever modifiers were already applied.

Example ​

ts
import { pvl } from '@pcvl/schema';

const age = pvl.number().int().min(0).max(130);

age.validate(36); // { value: 36 }
age.validate(36.5); // { issues: [{ code: 'NOT_INTEGER', ... }] }
age.validate(Number.NaN); // { issues: [{ code: 'INVALID_TYPE', ... }] }

Extends ​

Type Parameters ​

Input ​

Input = number

Output ​

Output = number

Methods ​

coerce() ​

ts
coerce(): NumberInterpretedSchema<unknown, Output>;

Defined in: schemas/numberInterpretedSchema.ts:176

Converts a string or a boolean with Number() before the type check, so '42' can satisfy this schema. Any other value is left as it is, and a string that isn't a number becomes NaN, which the type check rejects. Constraints always run after the type check, so chaining it after them is fine.

Returns ​

NumberInterpretedSchema<unknown, Output>

Example ​

ts
import { pvl } from '@pcvl/schema';

const port = pvl.number().int().min(1).coerce();

port.validate('8080'); // { value: 8080 }
port.validate('nope'); // { issues: [{ code: 'INVALID_TYPE', ... }] }

int() ​

ts
int(options?): this;

Defined in: schemas/numberInterpretedSchema.ts:141

Requires a whole number, as judged by Number.isInteger. Infinity fails it.

Parameters ​

options? ​

Partial<Pick<IssueProps, "message">>

Returns ​

this

Example ​

ts
import { pvl } from '@pcvl/schema';

const pageSize = pvl.number().int();

pageSize.validate(25); // { value: 25 }
pageSize.validate(25.5); // { issues: [{ code: 'NOT_INTEGER', ... }] }

max() ​

ts
max(maxValue, options?): this;

Defined in: schemas/numberInterpretedSchema.ts:109

Requires a value less than or equal to value — the bound is inclusive.

Parameters ​

maxValue ​

number

options? ​

Partial<Pick<IssueProps, "message">>

Returns ​

this

Example ​

ts
import { pvl } from '@pcvl/schema';

const percentage = pvl.number().min(0).max(100);

percentage.validate(101); // { issues: [{ code: 'TOO_BIG', ... }] }

min() ​

ts
min(minValue, options?): this;

Defined in: schemas/numberInterpretedSchema.ts:79

Requires a value greater than or equal to value — the bound is inclusive.

Parameters ​

minValue ​

number

options? ​

Partial<Pick<IssueProps, "message">>

Returns ​

this

Example ​

ts
import { pvl } from '@pcvl/schema';

const quantity = pvl.number().min(1, { message: 'order at least one' });

quantity.validate(1); // { value: 1 }
quantity.validate(0); // { issues: [{ code: 'TOO_SMALL', message: 'order at least one' }] }

nullable() ​

ts
nullable(): NumberInterpretedSchema<Input | null, Output | null>;

Defined in: schemas/chainableInterpretedSchema.ts:78

Accepts null in addition to whatever this schema already accepts. Unlike .optional(), the key stays required — null has to be passed explicitly. Chain both to accept either.

Returns ​

NumberInterpretedSchema<Input | null, Output | null>

Example ​

ts
import { pvl } from '@pcvl/schema';

const deletedAt = pvl.string().nullable();
deletedAt.validate(null); // { value: null }

const eitherWay = pvl.string().nullable().optional();

Inherited from ​

ChainableInterpretedSchema.nullable


optional() ​

ts
optional(): NumberInterpretedSchema<Input | undefined, Output | undefined>;

Defined in: schemas/chainableInterpretedSchema.ts:54

Accepts undefined in addition to whatever this schema already accepts. As an object field, it also makes the key itself optional: an omitted key stays omitted from the output rather than becoming an explicit undefined property.

Returns ​

NumberInterpretedSchema<Input | undefined, Output | undefined>

Example ​

ts
import { pvl } from '@pcvl/schema';

const user = pvl.object({ name: pvl.string(), nickname: pvl.string().optional() });

user.validate({ name: 'Ada' }); // { value: { name: 'Ada' } }
user.validate({ name: 'Ada', nickname: 'Addie' }); // both keys kept

Inherited from ​

ChainableInterpretedSchema.optional


refine() ​

ts
refine<Refined>(predicate, options?): NumberInterpretedSchema<Input, RefinedOutput<Output, Refined>>;

Defined in: schemas/chainableInterpretedSchema.ts:134

Attaches a custom check. The predicate never changes the value; returning false produces an Issue with code CUSTOM. This is where constraints the library has no built-in for — a regex, a cross-field rule — belong.

It runs once the value has passed this schema's type check, in the order it was chained among the constraints, so a failing constraint before it does not stop it and both issues are reported. It is skipped for a value .optional() or .nullable() accepted.

When the predicate is a type predicate, written out ((value): value is T) or inferred by TypeScript ((value) => value === 'ahoj'), the output narrows to its type; a predicate returning a plain boolean leaves it as it was. The input never narrows. A null or undefined that .nullable() or .optional() chained before lets past the predicate stays in the output.

The schema comes back as the same type it went in as, so a refined object or array schema is still one, with its shape or element.

Type Parameters ​

Refined ​

Refined = Output

Parameters ​

predicate ​

((value) => boolean) | ((value) => value is Refined)

options? ​

Partial<Pick<IssueProps, "message">>

Returns ​

NumberInterpretedSchema<Input, RefinedOutput<Output, Refined>>

Example ​

ts
import { pvl } from '@pcvl/schema';

const evenNumber = pvl
  .number()
  .refine((value) => value % 2 === 0, { message: 'must be even' });

evenNumber.validate(3); // { issues: [{ code: 'CUSTOM', message: 'must be even' }] }

// Output 'ahoj', input still string.
const greeting = pvl.string().refine((value) => value === 'ahoj');
// Output string | null: a null never reaches the predicate.
const name = pvl
  .string()
  .nullable()
  .refine((value) => value !== null);

// Still an object schema, so `shape` is still there.
const range = pvl
  .object({ min: pvl.number().standalone(), max: pvl.number() })
  .refine((value) => value.min <= value.max);
range.shape.min.validate(1); // { value: 1 }

Inherited from ​

ChainableInterpretedSchema.refine


standalone() ​

ts
standalone(): NumberInterpretedSchema<Input, Output> & StandaloneMarker;

Defined in: schemas/interpretedSchema.ts:119

Marks this schema as a Standalone Key: as a field of pvl.object(), it is kept in that object's shape, so it can be validated on its own, and once the object is compiled, @pcvl/compiler gives it a compiled validator of its own. Validation is unchanged.

The schema comes back as the same type it went in as, marked, so the chain goes on: modifiers can follow it, and an object keeps its shape and an array its element. It is valid on any schema, a field or not, and calling it again changes nothing.

Returns ​

NumberInterpretedSchema<Input, Output> & StandaloneMarker

Example ​

ts
import { pvl } from '@pcvl/schema';

const user = pvl.object({
  email: pvl.string().standalone().min(3),
  age: pvl.number().int(),
});

user.shape.email.validate('a@b.c'); // { value: 'a@b.c' }
// user.shape.age — a compile error: `age` isn't a Standalone Key

Inherited from ​

ChainableInterpretedSchema.standalone


transform() ​

ts
transform<NewOutput>(fn): Schema<Input, NewOutput>;

Defined in: schemas/chainableInterpretedSchema.ts:176

Converts an accepted value into a different one, changing the Output type to whatever the function returns. It is the last thing to run, and only once nothing has failed, so the function only ever sees a value this schema accepted — unlike .coerce(), which runs before the type check. A value .optional() or .nullable() accepted still reaches it, so the function is typed to receive undefined or null too.

It ends the chain: the result is a plain Schema, which can validate but takes no further modifier, so chain those before transforming. A transformed object has no shape, and a transformed array no element: they describe a value the function replaces.

Type Parameters ​

NewOutput ​

NewOutput

Parameters ​

fn ​

(value) => NewOutput

Returns ​

Schema<Input, NewOutput>

Example ​

ts
import { pvl } from '@pcvl/schema';

const trimmedLength = pvl.string().transform((value) => value.trim().length);

trimmedLength.validate('  hello  '); // { value: 5 }
trimmedLength.validate(42); // { issues: [...] } — never reaches the transform

const label = pvl.string().nullable().transform((value) => value ?? 'none');
label.validate(null); // { value: 'none' }

Inherited from ​

ChainableInterpretedSchema.transform


validate() ​

ts
validate(value): Result<Output>;

Defined in: schemas/interpretedSchema.ts:89

Checks a value against this schema and returns a Result synchronously. It never throws for an invalid value — a rejection comes back as issues, so the two branches are handled the same way every time.

Parameters ​

value ​

unknown

Returns ​

Result<Output>

Example ​

ts
import { pvl } from '@pcvl/schema';

const result = pvl.number().int().validate(1.5);
if (result.issues) {
  console.log(result.issues[0]?.message); // 'Number must be an integer'
} else {
  console.log(result.value);
}

Inherited from ​

ChainableInterpretedSchema.validate