question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Typescript Definitions for Schema Typing

See original GitHub issue

Is your feature request related to a problem? Please describe. There is no official way of typing schema definitions. Schema types would be useful in helping to prevent some death by a thousand cuts scenarios like forgetting to define a to property in a reference definition instead of very visually similar of, which is used in an array definition.

It would also be useful in reducing how often one must visit sanity.io/docs and search for usage instructions for just one aspect of a schema definition. Schema docs like sanity.io/docs/conditional-fields, sanity.io/docs/validation, and sanity.io/docs/schema-types are scattered around the site and aren’t grouped in any way for easy access to each other. I have to use search each time in order to find them individually. If the definitions are typed, the need to often visit docs for usage instructions is reduced.

Describe the solution you’d like Please provide official Typescript types for use in schema defintions to improve DX.

Describe alternatives you’ve considered BaseSchemaType from @sanity/types seems close, but its type property does not work with strings, which makes it useless for this purpose.

Some are generating their own types in place of official ones: https://github.com/ricokahler/sanity-codegen/blob/main/src/generate-types.ts. But as new Sanity features are released, these definitions will always be going out of date.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:15
  • Comments:5 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
saiichihashimotocommented, Jan 28, 2022

I’d like to throw my vote in for this. We use sanity-codegen for querying and consuming the docs (amazing! loving this) but typing the configs in the first place would be phenomenal. Having everything returned as any like sanity docs have as a workaround basically means we shouldn’t use typescript here.

1reaction
ryansheehancommented, Sep 1, 2021

This is far from perfect, but I created my own typings because of this issue. Maybe it can serve as a work around, or starting point for others until the @sanity/types is properly working.

import * as React from 'react';
import { JsxElement } from 'typescript';

export type BaseType =
  'array' | 'block' | 'boolean' | 'date' | 'datetime' | 'document' |
  'file' | 'geopoint' | 'image' | 'number' | 'object' | 'reference' |
  'slug' | 'string' | 'text' | 'url' | 'tags';

export type DocumentT = Record<string, any>;

export type InitialValueFn<T> = () => (T | Promise<T>);

export type InitialValue<T> = T | InitialValueFn<T>;

export type IconType = () => unknown;

export interface TitleValue {
  title: string;
  value: string;
}

export interface TitleValueIcon extends TitleValue {
  icon?: IconType;
}

export interface Schema<RuleT, T extends BaseType = BaseType> {
  name: string;
  type: T;
  title?: string;
  description?: string;
  hidden?: boolean;
  readOnly?: boolean;
  fieldset?: string;
  validation?: Validation<RuleT>;
  initialValue?: any;
}

export type SchemaType =
  ArraySchema |
  BlockSchema |
  BooleanSchema |
  DateSchema |
  DatetimeSchema |
  FileSchema |
  GeopointSchema |
  ImageSchema |
  NumberSchema |
  ObjectSchema |
  ReferenceSchema |
  SlugSchema |
  StringSchema |
  TextSchema |
  TagsSchema |
  UrlSchema;

export type SchemaTypeAny = SchemaType | { type: string; title?: string; name?: string };

export interface FieldSetItem {
  name: string;
  title: string;
}

export interface OrderingItem {
  title: string;
  name: string;
  by: { field: string, direction: 'asc' | 'desc' }[]
}

interface ValidationRequired<Rule> {
  required: () => Rule;
};

interface ValidationMin<Rule> {
  min: (min: number) => Rule;
}

interface ValidationMax<Rule> {
  max: (max: number) => Rule;
}

interface ValidationLessThan<Rule> {
  lessThan: (limit: number) => Rule;
}

interface ValidationGreaterThan<Rule> {
  greaterThan: (limit: number) => Rule;
}

interface ValidationInteger<Rule> {
  integer: () => Rule;
}

interface ValidationPrecision<Rule> {
  precision: (limit: number) => Rule;
}

interface ValidationPositive<Rule> {
  positive: () => Rule;
}

interface ValidationNegative<Rule> {
  negative: () => Rule;
}

interface ValidationLength<Rule> {
  length: (exact: number) => Rule;
}

interface ValidationUppercase<Rule> {
  uppercase: () => Rule;
}

interface ValidationLowercase<Rule> {
  lowercase: () => Rule;
}

interface ValidationUnique<Rule> {
  unique: () => Rule;
}

interface ValidationUri<Rule> {
  uri: (opt: {
    scheme?: string | RegExp | (string | RegExp)[];
    allowRelative?: boolean;
    relativeOnly?: boolean;
  }) => Rule;
}

export type CustomValidationResult = true | string;

interface ValidationCustom<Rule> {
  custom: (fn: (input: any, context?: any) => (CustomValidationResult | Promise<CustomValidationResult>)) => Rule;
}

interface ValidationRegex<Rule> {
  regex: (exp: RegExp, options?: { name: string, invert: boolean }) => Rule;
}

interface ValidationWarning<Rule> {
  warning: (msg: string) => Rule;
}

interface ValidationError<Rule> {
  error: (msg: string) => Rule;
}

export type ValidationRuleFn<Rule> = (rule: Rule) => (Rule | Rule[]);

export type Validation<Rule> = ValidationRuleFn<Rule> | false;

interface RuleBase<Rule> extends ValidationRequired<Rule>,
  ValidationWarning<Rule>,
  ValidationError<Rule>,
  ValidationCustom<NumberValidationRule> { }

export interface PreviewResult {
  title: string;
  subtitle?: string;
  media?: JsxElement
}

export interface Preview {
  select: Record<string, string>;
  prepare?: (selection: DocumentT) => PreviewResult;
  component?: React.ComponentType;
}

export interface OfReference {
  type: string;
  title?: string;
  to?: { type: string }[];
}

export interface DocumentValidationRule extends ValidationCustom<DocumentValidationRule> { }
export interface DocumentSchema extends Schema<DocumentValidationRule, 'document'> {
  fields: SchemaType[];
  liveEdit?: boolean;
  orderings?: OrderingItem[];
  fieldsets?: FieldSetItem[];
  preview?: { select: PreviewResult } | Preview;
  validation?: Validation<DocumentValidationRule>;
}

export interface StringSchemaOptionsBase {
  list: TitleValue[];
}

export interface StringSchemaOptionsDropdown extends StringSchemaOptionsBase {
  layout: 'dropdown';
}

export interface StringSchemaOptionsRadio extends StringSchemaOptionsBase {
  layout: 'radio';
  direction?: 'horizontal' | 'vertical';
}

export interface StringValidationRule extends RuleBase<StringValidationRule>,
  ValidationMin<StringValidationRule>,
  ValidationMax<StringValidationRule>,
  ValidationLength<StringValidationRule>,
  ValidationUppercase<StringValidationRule>,
  ValidationLowercase<StringValidationRule>,
  ValidationRegex<StringValidationRule> { }

export interface StringSchema extends Schema<StringValidationRule, 'string'> {
  options?: StringSchemaOptionsDropdown | StringSchemaOptionsRadio;
  initialValue?: InitialValue<string>;
}


export interface NumberValidationRule extends RuleBase<NumberValidationRule>,
  ValidationMin<NumberValidationRule>,
  ValidationMax<NumberValidationRule>,
  ValidationLessThan<NumberValidationRule>,
  ValidationGreaterThan<NumberValidationRule>,
  ValidationInteger<NumberValidationRule>,
  ValidationPrecision<NumberValidationRule>,
  ValidationPositive<NumberValidationRule>,
  ValidationNegative<NumberValidationRule> { }

export interface NumberSchema extends Schema<NumberValidationRule, 'number'> {
  options?: {
    list: number[] | { value: number, title: string }[];
  }
  initialValue?: InitialValue<number>;
}

export interface ObjectValidationRule extends RuleBase<ObjectValidationRule> { }
export interface ObjectSchema extends Schema<ObjectValidationRule, 'object'> {
  fields: SchemaType[];
  fieldsets?: FieldSetItem[];
  preview?: PreviewResult | Preview;
  inputComponent?: JsxElement;
  options?: {
    collapsible?: boolean;
    collapsed?: boolean;
    columns?: number;
  }
  initialValue?: InitialValue<DocumentT>;
}

export interface ArrayValidationRule extends RuleBase<ArrayValidationRule>,
  ValidationUnique<ArrayValidationRule>,
  ValidationMin<ArrayValidationRule>,
  ValidationMax<ArrayValidationRule>,
  ValidationLength<ArrayValidationRule> { }

export interface ArraySchema extends Schema<ArrayValidationRule, 'array'> {
  of: (OfReference | SchemaType)[];
  options?: {
    sortable?: boolean;
    layout?: 'tags' | 'grid';
    list?: TitleValue[];
    editModal?: 'dialog' | 'fullscreen' | 'popover';
  };
  initialValue?: InitialValue<DocumentT[]>;
}

export interface BooleanValidationRule extends RuleBase<BooleanValidationRule> { }
export interface BooleanSchema extends Schema<BooleanValidationRule, 'boolean'> {
  options?: {
    layout: 'switch' | 'checkbox';
  };
  initialValue?: InitialValue<boolean>;
}

export interface BlockValidationRule extends RuleBase<BlockValidationRule> { }
export interface BlockSchema extends Schema<BlockValidationRule, 'block'> {
  styles?: TitleValueIcon[];
  lists?: TitleValueIcon[];
  marks?: {
    decorators?: TitleValueIcon[];
    annotations?: SchemaTypeAny[];
  };
  of?: SchemaTypeAny[];
  icon?: IconType;
}

export interface DateValidationRule extends RuleBase<DateValidationRule> { }
export interface DateSchema extends Schema<DateValidationRule, 'date'> {
  options?: {
    dateFormat: string;
  }
  initialValue?: InitialValue<string>;
}

export interface DatetimeValidationRule extends RuleBase<DatetimeValidationRule>,
  ValidationMin<DatetimeValidationRule>,
  ValidationMax<DatetimeValidationRule> { }

export interface DatetimeSchema extends Schema<DatetimeValidationRule, 'datetime'> {
  options?: {
    dateFormat?: string;
    timeFormat?: string;
    timeStep?: number;
  }
  initialValue?: InitialValue<string>;
}

export interface FileValidationRule extends RuleBase<FileValidationRule> { }
export interface FileSchema extends Schema<FileValidationRule, 'file'> {
  options?: {
    storeOriginalFileName?: boolean;
    accept?: string;
  };
  initialValue?: InitialValue<File>;
}

export interface GeopointValidationRule extends RuleBase<GeopointValidationRule> { }
export interface GeopointSchema extends Schema<GeopointValidationRule, 'geopoint'> {
  initialValue?: InitialValue<{ lat: number; lng: number; alt?: number; }>;
}

export type ImageMetaData = 'exif' | 'location' | 'lqip' | 'palette';
export interface ImageValidationRule extends RuleBase<ImageValidationRule> { }
export interface ImageSchema extends Schema<ImageValidationRule, 'image'> {
  fields?: SchemaType[];
  options?: {
    metadata?: ImageMetaData[];
    hotspot?: boolean;
    storeOriginalFilename?: boolean;
    accept?: string;
    sources?: unknown[];
  };
}

export interface ReferenceValidationRule extends RuleBase<ReferenceValidationRule> { }
export type ReferenceFilterFn = (data: { document: any, parent: any, parentPath: any; }) => ({ filter: any; params: any; } | Promise<{ filter: any; params: any; }>);
export interface ReferenceSchema extends Schema<ReferenceValidationRule, 'reference'> {
  to?: { type: string }[];
  weak?: boolean;
  options?: {
    filter?: string | ReferenceFilterFn;
    filterParams?: any;
  };
}

export interface SlugValidationRule extends RuleBase<SlugValidationRule> { }
export type SlugOptionsSourceFn = (doc: any, opt: { parent: any, parentPath: any }) => string;
export interface SlugSchema extends Schema<SlugValidationRule, 'slug'> {
  options?: {
    source: string | SlugOptionsSourceFn;
    maxLength?: number;
    slugify?: (input: string, type: any) => any;
    isUnique?: (slug: string, opt: any) => boolean;
  }
  initialValue?: InitialValue<string>;
}


export interface TextValidationRule extends RuleBase<TextValidationRule>,
  ValidationMin<StringValidationRule>,
  ValidationMax<StringValidationRule>,
  ValidationLength<StringValidationRule>,
  ValidationUppercase<StringValidationRule>,
  ValidationLowercase<StringValidationRule>,
  ValidationRegex<StringValidationRule> { }
export interface TextSchema extends Schema<TextValidationRule, 'text'> {
  rows?: number;
  initialValue?: InitialValue<string>;
}


export interface UrlValidationRule extends RuleBase<UrlValidationRule>,
  ValidationUri<UrlValidationRule> { }
export interface UrlSchema extends Schema<UrlValidationRule, 'url'> {
  initialValue?: InitialValue<string>;
}

export interface TagsValidationRule extends RuleBase<TagsValidationRule> { }
export interface TagsSchema extends Schema<TagsValidationRule, 'tags'> {

}
Read more comments on GitHub >

github_iconTop Results From Across the Web

Schemas and Elements in TypeScript - iTwin.js
A Schema represents an ECSchema in TypeScript. It is a collection of Entity-based classes. See the BIS overview for how ECSchemas are used...
Read more >
Typescript Type of JSON schema object - Stack Overflow
Is there a special type associated with JSON-schema objects in typescript? My class has a method that checks whether its members satisfy the ......
Read more >
json-schema-to-typescript - npm
Generates code for definitions that aren't referenced by the schema. strictIndexSignatures, boolean, false, Append all index signatures with | ...
Read more >
Using with TypeScript - Ajv JSON schema validator
The fastest JSON schema Validator. Supports JSON Schema draft-04/06/07/2019-09/2020-12 and JSON Type Definition (RFC8927)
Read more >
Schemas in TypeScript - Mongoose
3.1. Mongoose supports auto typed schemas so you don't need to define additional typescript interface anymore but you are still able to do...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found