@pcvl/schema / LiteralInterpretedSchema
Class: LiteralInterpretedSchema<LiteralValue, Input, Output>
Defined in: schemas/literalInterpretedSchema.ts:67
Matches exactly one constant value. Build one with pvl.literal(value). A right-shaped value of the wrong type ('42' for pvl.literal(42)) is rejected just like a wrong value is.
The comparison is Object.is, not ===, which matters for the two values they disagree about: pvl.literal(0) rejects -0 rather than accepting it and handing back 0, and pvl.literal(NaN) matches NaN rather than being a schema nothing can satisfy.
Example
import { pvl } from '@pcvl/schema';
const owner = pvl.literal('OWNER');
owner.validate('OWNER'); // { value: 'OWNER' }
owner.validate('MEMBER'); // { issues: [{ code: 'INVALID_VALUE', ... }] }
// Literals are how a union gets a discriminant:
const event = pvl.union([
pvl.object({ type: pvl.literal('created'), id: pvl.string() }),
pvl.object({ type: pvl.literal('deleted'), id: pvl.string() }),
]);Extends
ChainableInterpretedSchema<Input,Output>
Type Parameters
LiteralValue
LiteralValue extends PossibleLiteralValue
Input
Input = LiteralValue
Output
Output = LiteralValue
Methods
coerce()
coerce(): LiteralInterpretedSchema<LiteralValue, unknown, Output>;Defined in: schemas/literalInterpretedSchema.ts:98
Converts the raw input before the check, exactly as the primitive schema of the literal's own type would: pvl.literal(1).coerce() converts as pvl.number().coerce() does. The equality check then decides, so an input that doesn't convert to the literal fails it.
Returns
LiteralInterpretedSchema<LiteralValue, unknown, Output>
Example
import { pvl } from '@pcvl/schema';
const one = pvl.literal(1).coerce();
one.validate('1'); // { value: 1 }
one.validate('2'); // { issues: [{ code: 'INVALID_VALUE', ... }] }nullable()
nullable(): LiteralInterpretedSchema<LiteralValue, 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
LiteralInterpretedSchema<LiteralValue, Input | null, Output | null>
Example
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()
optional(): LiteralInterpretedSchema<LiteralValue, 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
LiteralInterpretedSchema<LiteralValue, Input | undefined, Output | undefined>
Example
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 keptInherited from
ChainableInterpretedSchema.optional
refine()
refine<Refined>(predicate, options?): LiteralInterpretedSchema<LiteralValue, 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
LiteralInterpretedSchema<LiteralValue, Input, RefinedOutput<Output, Refined>>
Example
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()
standalone(): LiteralInterpretedSchema<LiteralValue, 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
LiteralInterpretedSchema<LiteralValue, Input, Output> & StandaloneMarker
Example
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 KeyInherited from
ChainableInterpretedSchema.standalone
transform()
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
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()
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
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);
}