@sanity/types
Version:
Type definitions for common Sanity data structures
429 lines (398 loc) • 11.7 kB
text/typescript
import {type SanityClient} from '@sanity/client'
import {type SanityDocument} from '../documents'
import {type ValidationMarker} from '../markers'
import {type Path} from '../paths'
import {
type Schema,
type SchemaType,
type SchemaValidationValue,
type SlugSchemaType,
} from '../schema'
import {type SlugParent} from '../slug'
/** @public */
export type RuleTypeConstraint = 'Array' | 'Boolean' | 'Date' | 'Number' | 'Object' | 'String'
/** @public */
export type FieldRules = {[fieldKey: string]: SchemaValidationValue}
/**
* Holds localized validation messages for a given field.
*
* @example Custom message for English (US) and Norwegian (Bokmål):
* ```
* {
* 'en-US': 'Needs to start with a capital letter',
* 'no-NB': 'Må starte med stor bokstav',
* }
* ```
* @public
*/
export interface LocalizedValidationMessages {
[locale: string]: string
}
/**
* Note: `RuleClass` and `Rule` are split to fit the current `@sanity/types`
* setup. Classes are a bit weird in the `@sanity/types` package because classes
* create an actual javascript class while simultaneously creating a type
* definition.
*
* This implicitly creates two types:
* 1. the instance type — `Rule` and
* 2. the static/class type - `RuleClass`
*
* The `RuleClass` type contains the static methods and the `Rule` instance
* contains the instance methods. Downstream in the validation package, the Rule
* implementation asserts the class declaration is of this type.
*
* @internal
*/
export interface RuleClass {
FIELD_REF: symbol
array: (def?: SchemaType) => Rule
object: (def?: SchemaType) => Rule
string: (def?: SchemaType) => Rule
number: (def?: SchemaType) => Rule
boolean: (def?: SchemaType) => Rule
dateTime: (def?: SchemaType) => Rule
valueOfField: Rule['valueOfField']
new (typeDef?: SchemaType): Rule
}
/**
* Holds a reference to a different field
* NOTE: Only use this through {@link Rule.valueOfField}
*
* @public
*/
export interface FieldReference {
type: symbol
path: string | string[]
}
/** @public */
export interface UriValidationOptions {
scheme?: (string | RegExp) | Array<string | RegExp>
allowRelative?: boolean
relativeOnly?: boolean
allowCredentials?: boolean
}
/** @public */
export interface Rule {
// NOTE: these prop is not actually deprecated but there is better TS Doc
// support for `@deprecated` than `@internal`
/**
* @internal
* @deprecated internal use only
*/
_type: RuleTypeConstraint | undefined
/**
* @internal
* @deprecated internal use only
*/
_level: 'error' | 'warning' | 'info' | undefined
/**
* @internal
* @deprecated internal use only
*/
_required: 'required' | 'optional' | undefined
/**
* @internal
* @deprecated internal use only
*/
_typeDef: SchemaType | undefined
/**
* @internal
* @deprecated internal use only
*/
_message: string | LocalizedValidationMessages | undefined
/**
* @internal
* @deprecated internal use only
*/
_rules: RuleSpec[]
/**
* @internal
* @deprecated internal use only
*/
_fieldRules: FieldRules | undefined
/**
* Takes in a path and returns an object with a symbol.
*
* When the validation lib sees this symbol, it will use the provided path to
* get a value from the current field's parent and use that value as the input
* to the Rule.
*
* The path that's given is forwarded to `lodash/get`
*
* ```js
* fields: [
* // ...
* {
* // ...
* name: 'highestTemperature',
* type: 'number',
* validation: (Rule) => Rule.positive().min(Rule.valueOfField('lowestTemperature')),
* // ...
* },
* ]
* ```
*/
valueOfField: (path: string | string[]) => FieldReference
error(message?: string | LocalizedValidationMessages): Rule
warning(message?: string | LocalizedValidationMessages): Rule
info(message?: string | LocalizedValidationMessages): Rule
reset(): this
isRequired(): boolean
clone(): Rule
cloneWithRules(rules: RuleSpec[]): Rule
merge(rule: Rule): Rule
type(targetType: RuleTypeConstraint | Lowercase<RuleTypeConstraint>): Rule
all(children: Rule[]): Rule
either(children: Rule[]): Rule
optional(): Rule
required(): Rule
custom<T = unknown>(fn: CustomValidator<T>, options?: {bypassConcurrencyLimit?: boolean}): Rule
min(len: number | string | FieldReference): Rule
max(len: number | string | FieldReference): Rule
length(len: number | FieldReference): Rule
valid(value: unknown | unknown[]): Rule
integer(): Rule
precision(limit: number | FieldReference): Rule
positive(): Rule
negative(): Rule
greaterThan(num: number | FieldReference): Rule
lessThan(num: number | FieldReference): Rule
uppercase(): Rule
lowercase(): Rule
regex(pattern: RegExp, name: string, options: {name?: string; invert?: boolean}): Rule
regex(pattern: RegExp, options: {name?: string; invert?: boolean}): Rule
regex(pattern: RegExp, name: string): Rule
regex(pattern: RegExp): Rule
email(): Rule
uri(options?: UriValidationOptions): Rule
unique(): Rule
reference(): Rule
fields(rules: FieldRules): Rule
assetRequired(): Rule
validate(
value: unknown,
options: ValidationContext & {
/**
* @deprecated Internal use only
* @internal
*/
__internal?: {
customValidationConcurrencyLimiter?: {
ready: () => Promise<void>
release: () => void
}
}
},
): Promise<ValidationMarker[]>
}
/** @public */
export type RuleSpec =
| {flag: 'integer'}
| {flag: 'email'}
| {flag: 'unique'}
| {flag: 'reference'}
| {flag: 'type'; constraint: RuleTypeConstraint}
| {flag: 'all'; constraint: Rule[]}
| {flag: 'either'; constraint: Rule[]}
| {flag: 'presence'; constraint: 'optional' | 'required'}
| {flag: 'custom'; constraint: CustomValidator}
| {flag: 'min'; constraint: number | string | FieldReference}
| {flag: 'max'; constraint: number | string | FieldReference}
| {flag: 'length'; constraint: number | FieldReference}
| {flag: 'valid'; constraint: unknown[]}
| {flag: 'precision'; constraint: number | FieldReference}
| {flag: 'lessThan'; constraint: number | FieldReference}
| {flag: 'greaterThan'; constraint: number | FieldReference}
| {flag: 'stringCasing'; constraint: 'uppercase' | 'lowercase'}
| {flag: 'assetRequired'; constraint: {assetType: 'asset' | 'image' | 'file'}}
| {
flag: 'regex'
constraint: {
pattern: RegExp
name?: string
invert: boolean
}
}
| {
flag: 'uri'
constraint: {
options: {
scheme: RegExp[]
allowRelative: boolean
relativeOnly: boolean
allowCredentials: boolean
}
}
}
/**
* this is used to get allow index access (e.g. `RuleSpec['constraint']`) to
* constraint when a rule spec might not have a `constraint` prop
*
* @internal
*/
export type ConditionalIndexAccess<T, U> = U extends keyof T ? T[U] : undefined
/** @internal */
export type RuleSpecConstraint<T extends RuleSpec['flag']> = ConditionalIndexAccess<
Extract<RuleSpec, {flag: T}>,
'constraint'
>
/**
* A context object passed around during validation. This includes the
* `Rule.custom` context.
*
* e.g.
*
* ```js
* Rule.custom((_, validationContext) => {
* // ...
* })`
* ```
*
* @public
*/
export interface ValidationContext {
getClient: (options: {apiVersion: string}) => SanityClient
schema: Schema
parent?: unknown
type?: SchemaType
document?: SanityDocument
path?: Path
getDocumentExists?: (options: {id: string}) => Promise<boolean>
environment: 'cli' | 'studio'
}
/**
* The base type for all validators in the validation library. Takes in a
* `RuleSpec`'s constraint, the value to check, an optional override message,
* and the validation context.
*
* @see Rule.validate from `sanity/src/core/validation/Rule`
*
* @internal
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Validator<T = any, Value = any> = (
constraint: T,
value: Value,
message: string | undefined,
context: ValidationContext,
) =>
| ValidationError[]
| ValidationError
| string
| true
| Promise<ValidationError[] | ValidationError | string | true>
/**
* A type helper used to define a group of validators. The type of the
* `RuleSpec` constraint is inferred via the key.
*
* E.g.
*
* ```ts
* const booleanValidators: Validators = {
* ...genericValidator,
*
* presence: (v, value, message) => {
* if (v === 'required' && typeof value !== 'boolean') return message || 'Required'
* return true
* },
* }
* ```
*
* @internal
*/
export type Validators = Partial<{
[P in RuleSpec['flag']]: Validator<
Exclude<ConditionalIndexAccess<Extract<RuleSpec, {flag: P}>, 'constraint'>, FieldReference>
>
}>
/** @internal */
export interface ValidationErrorOptions {
paths?: Path[]
children?: ValidationMarker[]
operation?: 'AND' | 'OR'
}
/**
* This follows the same pattern as `RuleClass` and `Rule` above
* Note: this class does not actually extend `Error` since it's never thrown
* within the validation library
*
* @deprecated It is preferred to a plain object that adheres to `ValidationError`
* @internal
*/
export interface ValidationErrorClass {
new (message: string, options?: ValidationErrorOptions): ValidationError
}
/**
* The shape that can be returned from a custom validator to be converted into
* a validation marker by the validation logic. Inside of a custom validator,
* you can return an array of these in order to specify multiple paths within
* an object or array.
*
* @public
*/
export interface ValidationError {
/**
* The message describing why the value is not valid. This message will be
* included in the validation markers after validation has finished running.
*/
message: string
/**
* If writing a custom validator, you can return validation messages to
* specific path inside of the current value (object or array) by populating
* this `path` prop.
*
* NOTE: This path is relative to the current value and _not_ relative to
* the document.
*/
path?: Path
/**
* Same as `path` but allows more than one value. If provided, the same
* message will create two markers from each path with the same message
* provided.
*
* @deprecated prefer `path`
*/
paths?: Path[]
/**
* @deprecated Unused. Was used to store the results from `.either()` /`.all()`
*/
children?: ValidationMarker[]
/**
* @deprecated Unused. Was used to signal if this error came from an `.either()`/`.all()`.
*/
operation?: 'AND' | 'OR'
/**
* @deprecated Unused. Was relevant when validation error was used as a class.
*/
cloneWithMessage?(message: string): ValidationError
}
/** @public */
export type CustomValidatorResult =
| true
| string
| ValidationError
| ValidationError[]
| LocalizedValidationMessages
/** @public */
export interface CustomValidator<T = unknown> {
(value: T, context: ValidationContext): CustomValidatorResult | Promise<CustomValidatorResult>
bypassConcurrencyLimit?: boolean
}
/** @public */
export interface SlugValidationContext extends ValidationContext {
parent: SlugParent
type: SlugSchemaType
defaultIsUnique: SlugIsUniqueValidator
}
/** @public */
export type SlugIsUniqueValidator = (
slug: string,
context: SlugValidationContext,
) => boolean | Promise<boolean>
/** @public */
export interface FormNodeValidation {
level: 'error' | 'warning' | 'info'
message: string
path: Path
}