Skip to content

@pcvl/schema / StringInterpretedSchema

Class: StringInterpretedSchema<Input, Output> ​

Defined in: schemas/stringInterpretedSchema.ts:50

Accepts a JavaScript string, with optional length constraints. Build one with pvl.string().

Constraints are checked in the order they were chained, once the value is a string, and every one that fails is reported — not just the first.

Constraints and the shared modifiers chain in any order — a constraint keeps whatever modifiers were already applied.

Example ​

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

const username = pvl.string().min(3).max(20);

username.validate('ada'); // { value: 'ada' }
username.validate('ad'); // { issues: [{ code: 'TOO_SMALL', ... }] }
username.validate(42); // { issues: [{ code: 'INVALID_TYPE', ... }] }

Extends ​

Type Parameters ​

Input ​

Input = string

Output ​

Output = string

Methods ​

coerce() ​

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

Defined in: schemas/stringInterpretedSchema.ts:174

Converts the raw input with String() before the type check, so any value can satisfy this schema: 42 becomes '42'.

It runs where it is chained among .optional() and .nullable(): pvl.string().coerce().optional() turns undefined into 'undefined', while pvl.string().optional().coerce() accepts undefined as it is. Constraints always run after the type check, so chaining it after them is fine.

Returns ​

StringInterpretedSchema<unknown, Output>

Example ​

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

const pin = pvl.string().length(4).coerce();

pin.validate(1234); // { value: '1234' }
pin.validate(12); // { issues: [{ code: 'INVALID_LENGTH', ... }] }

length() ​

ts
length(exactLength, options?): this;

Defined in: schemas/stringInterpretedSchema.ts:136

Requires exactly exactLength characters.

Parameters ​

exactLength ​

number

options? ​

Partial<Pick<IssueProps, "message">>

Returns ​

this

Example ​

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

const countryCode = pvl.string().length(2);

countryCode.validate('CZ'); // { value: 'CZ' }
countryCode.validate('CZE'); // { issues: [{ code: 'INVALID_LENGTH', ... }] }

max() ​

ts
max(maxLength, options?): this;

Defined in: schemas/stringInterpretedSchema.ts:105

Requires at most maxLength characters.

Parameters ​

maxLength ​

number

options? ​

Partial<Pick<IssueProps, "message">>

Returns ​

this

Example ​

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

const tweet = pvl.string().max(280);

tweet.validate('x'.repeat(281)); // { issues: [{ code: 'TOO_BIG', ... }] }

min() ​

ts
min(minLength, options?): this;

Defined in: schemas/stringInterpretedSchema.ts:75

Requires at least minLength characters. The comparison is on String.prototype.length, i.e. UTF-16 code units.

Parameters ​

minLength ​

number

options? ​

Partial<Pick<IssueProps, "message">>

Returns ​

this

Example ​

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

const password = pvl.string().min(8, { message: 'too short' });

password.validate('hunter2'); // { issues: [{ code: 'TOO_SMALL', message: 'too short' }] }

nullable() ​

ts
nullable(): StringInterpretedSchema<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 ​

StringInterpretedSchema<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(): StringInterpretedSchema<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 ​

StringInterpretedSchema<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?): StringInterpretedSchema<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 ​

StringInterpretedSchema<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(): StringInterpretedSchema<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 ​

StringInterpretedSchema<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