convex
Version:
Client for the Convex Cloud
291 lines (265 loc) • 7.22 kB
text/typescript
import { Expand } from "../type_utils.js";
import { GenericId } from "./index.js";
import {
OptionalProperty,
VAny,
VArray,
VBoolean,
VBytes,
VFloat64,
VId,
VInt64,
VLiteral,
VNull,
VObject,
VOptional,
VRecord,
VString,
VUnion,
Validator,
} from "./validators.js";
/**
* The type that all validators must extend.
*
* @public
*/
export type GenericValidator = Validator<any, any, any>;
export function isValidator(v: any): v is GenericValidator {
return !!v.isConvexValidator;
}
/**
* Coerce an object with validators as properties to a validator.
* If a validator is passed, return it.
*
* @public
*/
export function asObjectValidator<
V extends Validator<any, any, any> | PropertyValidators,
>(
obj: V,
): V extends Validator<any, any, any>
? V
: V extends PropertyValidators
? Validator<ObjectType<V>>
: never {
if (isValidator(obj)) {
return obj as any;
} else {
return v.object(obj as PropertyValidators) as any;
}
}
/**
* Coerce an object with validators as properties to a validator.
* If a validator is passed, return it.
*
* @public
*/
export type AsObjectValidator<
V extends Validator<any, any, any> | PropertyValidators,
> =
V extends Validator<any, any, any>
? V
: V extends PropertyValidators
? Validator<ObjectType<V>>
: never;
/**
* The validator builder.
*
* This builder allows you to build validators for Convex values.
*
* Validators can be used in [schema definitions](https://docs.convex.dev/database/schemas)
* and as input validators for Convex functions.
*
* @public
*/
export const v = {
/**
* Validates that the value corresponds to an ID of a document in given table.
* @param tableName The name of the table.
*/
id: <TableName extends string>(tableName: TableName) => {
return new VId<GenericId<TableName>>({
isOptional: "required",
tableName,
});
},
/**
* Validates that the value is of type Null.
*/
null: () => {
return new VNull({ isOptional: "required" });
},
/**
* Validates that the value is of Convex type Float64 (Number in JS).
*
* Alias for `v.float64()`
*/
number: () => {
return new VFloat64({ isOptional: "required" });
},
/**
* Validates that the value is of Convex type Float64 (Number in JS).
*/
float64: () => {
return new VFloat64({ isOptional: "required" });
},
/**
* @deprecated Use `v.int64()` instead
*/
bigint: () => {
return new VInt64({ isOptional: "required" });
},
/**
* Validates that the value is of Convex type Int64 (BigInt in JS).
*/
int64: () => {
return new VInt64({ isOptional: "required" });
},
/**
* Validates that the value is of type Boolean.
*/
boolean: () => {
return new VBoolean({ isOptional: "required" });
},
/**
* Validates that the value is of type String.
*/
string: () => {
return new VString({ isOptional: "required" });
},
/**
* Validates that the value is of Convex type Bytes (constructed in JS via `ArrayBuffer`).
*/
bytes: () => {
return new VBytes({ isOptional: "required" });
},
/**
* Validates that the value is equal to the given literal value.
* @param literal The literal value to compare against.
*/
literal: <T extends string | number | bigint | boolean>(literal: T) => {
return new VLiteral<T>({ isOptional: "required", value: literal });
},
/**
* Validates that the value is an Array of the given element type.
* @param element The validator for the elements of the array.
*/
array: <T extends Validator<any, "required", any>>(element: T) => {
return new VArray<T["type"][], T>({ isOptional: "required", element });
},
/**
* Validates that the value is an Object with the given properties.
* @param fields An object specifying the validator for each property.
*/
object: <T extends PropertyValidators>(fields: T) => {
return new VObject<ObjectType<T>, T>({ isOptional: "required", fields });
},
/** @internal */
record: <
Key extends Validator<any, "required", any>,
Value extends Validator<any, "required", any>,
>(
keys: Key,
values: Value,
) => {
// TODO enforce that Infer<key> extends string
return new VRecord<
Value["isOptional"] extends true
? { [key in Infer<Key>]?: Value["type"] }
: Record<Infer<Key>, Value["type"]>,
Key,
Value
>({
isOptional: "required",
key: keys,
value: values,
});
},
/**
* Validates that the value matches one of the given validators.
* @param members The validators to match against.
*/
union: <T extends Validator<any, "required", any>[]>(...members: T) => {
return new VUnion<T[number]["type"], T>({
isOptional: "required",
members,
});
},
/**
* Does not validate the value.
*/
any: () => {
return new VAny({ isOptional: "required" });
},
/**
* Allows not specifying a value for a property in an Object.
* @param value The property value validator to make optional.
*
* ```typescript
* const objectWithOptionalFields = v.object({
* requiredField: v.string(),
* optionalField: v.optional(v.string()),
* });
* ```
*/
optional: <T extends GenericValidator>(value: T) => {
return value.asOptional() as VOptional<T>;
},
};
/**
* Validators for each property of an object.
*
* This is represented as an object mapping the property name to its
* {@link Validator}.
*
* @public
*/
export type PropertyValidators = Record<
string,
Validator<any, OptionalProperty, any>
>;
/**
* Compute the type of an object from {@link PropertyValidators}.
*
* @public
*/
export type ObjectType<Fields extends PropertyValidators> = Expand<
// Map each key to the corresponding property validator's type making
// the optional ones optional.
{
// This `Exclude<..., undefined>` does nothing unless
// the tsconfig.json option `"exactOptionalPropertyTypes": true,`
// is used. When it is it results in a more accurate type.
// When it is not the `Exclude` removes `undefined` but it is
// added again by the optional property.
[Property in OptionalKeys<Fields>]?: Exclude<
Infer<Fields[Property]>,
undefined
>;
} & {
[Property in RequiredKeys<Fields>]: Infer<Fields[Property]>;
}
>;
type OptionalKeys<PropertyValidators extends Record<string, GenericValidator>> =
{
[Property in keyof PropertyValidators]: PropertyValidators[Property]["isOptional"] extends "optional"
? Property
: never;
}[keyof PropertyValidators];
type RequiredKeys<PropertyValidators extends Record<string, GenericValidator>> =
Exclude<keyof PropertyValidators, OptionalKeys<PropertyValidators>>;
/**
* Extract a TypeScript type from a validator.
*
* Example usage:
* ```ts
* const objectSchema = v.object({
* property: v.string(),
* });
* type MyObject = Infer<typeof objectSchema>; // { property: string }
* ```
* @typeParam V - The type of a {@link Validator} constructed with {@link v}.
*
* @public
*/
export type Infer<T extends Validator<any, OptionalProperty, any>> = T["type"];