UNPKG

@sanity/types

Version:

Type definitions for common Sanity data structures

313 lines (305 loc) • 10.3 kB
import { type DefineArrayMemberBase, type DefineSchemaBase, type DefineSchemaOptions, type MaybeAllowUnknownProps, type NarrowPreview, type StrictDefinition, type WidenInitialValue, type WidenValidation, } from './defineTypes' import {type FieldDefinitionBase, type IntrinsicTypeName} from './definition' import {type AutocompleteString} from './types' /** * Helper function for defining a Sanity type definition. This function does not do anything on its own; * it exists to check that your schema definition is correct, and help autocompletion in your IDE. * * This function will narrow the schema type down to fields and options based on the provided type-string. * * Schema types defined using `defineType` should typically be added to the Studio config under `schema.types`. * Defined types can be referenced by their `name`. This is referred to as a type-alias. * * When using type-aliases as `type`, `defineType` cannot know the base-type, so type-safety will be reduced. * If you know the base type of the type-alias, provide `defineOptions.aliasFor: <base type name>`. * This will enforce that the schema definition conforms with the provided type. * * By default, `defineType` only allows known properties and options. * Use `defineOptions.strict: false` to allow unknown properties and options. * * ### Basic usage * * ```ts * defineType({ * type: 'object', * name: 'custom-object', * fields: [ {type: 'string', name: 'title', title: 'Title'}], * }) * ``` * * ### Usage with aliasFor narrowing * * ```ts * defineType({ * type: 'custom-object', * name: 'redefined-custom-object', * options: { * columns: 2 * } * }, {aliasFor: 'object' }) * ``` * * ### Allow unknown properties * * ```ts * defineType({ * type: 'custom-object', * name: 'redefined-custom-object', * allowsUnknownProperties: true * options: { * columns: 2, * allowsUnknownOptions: true * } * }, {strict: false}) * ``` * ### Maximum safety and best autocompletion * * Use {@link defineType}, {@link defineField} and {@link defineArrayMember}: * * ```ts * defineType({ * type: 'object', * name: 'custom-object', * fields: [ * defineField({ * type: 'array', * name: 'arrayField', * title: 'Things', * of: [ * defineArrayMember({ * type: 'object', * name: 'type-name-in-array', * fields: [defineField({type: 'string', name: 'title', title: 'Title'})], * }), * ], * }), * ], * }) * ``` * * ## Note on type-safety in the current implementation * * Type-safety inside array-like properties (schema properties like `fields` and `of`) can only be guaranteed when * {@link defineField} and {@link defineArrayMember} are used to wrap each value in the array. * * For array-values without a function-wrapper, TypeScript will resolve to a union type of all possible properties across * all schema types. This result in less precise typing. * * ### Extending the Sanity Schema types * * If you want to extend the Sanity Schema types with your own properties or options to make them typesafe, * you can use [TypeScript declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html). * * With declaration merging, properties and options will be available in a type-safe manner, and * `strict: false` will not be necessary. * * #### Example: Add option to StringOptions * * ```ts * // string.ts * * //redeclare the sanity module * declare module 'sanity' { * // redeclare StringOptions; it will be merged with StringOptions in the sanity module * export interface StringOptions { * myCustomOption?: boolean * } * } * * // the option is now part of the StringOptions type, just as if it was declared in the sanity codebase: * defineType({ * type: 'string', * name: 'my-string', * options: { * myCustomOption: true // this does not give an error anymore * } * }) * * ``` * * #### Example: Add a schema definition to "intrinsic-types" * * ```ts * //my-custom-type-definition.ts * * // create a new schema definition based on object (we remove the ability to assign field, change the type add some options) * export type MagicallyAddedDefinition = Omit<Schema.ObjectDefinition, 'type' | 'fields'> & { * type: 'magically-added-type' * options?: { * sparkles?: boolean * } * } * * // redeclares sanity module so we can add interfaces props to it * declare module 'sanity' { * // redeclares IntrinsicDefinitions and adds a named definition to it * // it is important that the key is the same as the type in the definition ('magically-added-type') * export interface IntrinsicDefinitions { * 'magically-added-type': MagicallyAddedDefinition * } * } * * // defineType will now narrow `type: 'magically-added-type'` to `MagicallyAddedDefinition` * defineType({ * type: 'magically-added-type' * name: 'magic', * options: { * sparkles: true // this is allowed, * //@ts-expect-error this is not allowed in MagicallyAddedDefinition.options * sparks: true * } * }) * ``` * * @param schemaDefinition - should be a valid schema type definition. * @param defineOptions - optional param to provide type hints for `schemaDefinition`. * * @see defineField * @see defineArrayMember * @see typed * * @beta */ export function defineType< const TType extends IntrinsicTypeName | AutocompleteString, const TName extends string, TSelect extends Record<string, string> | undefined, TPrepareValue extends Record<keyof TSelect, any> | undefined, TAlias extends IntrinsicTypeName | undefined, TStrict extends StrictDefinition, >( schemaDefinition: { type: TType name: TName } & DefineSchemaBase<TType, TAlias> & NarrowPreview<TType, TAlias, TSelect, TPrepareValue> & MaybeAllowUnknownProps<TStrict>, // eslint-disable-next-line @typescript-eslint/no-unused-vars defineOptions?: DefineSchemaOptions<TStrict, TAlias>, ): typeof schemaDefinition { return schemaDefinition } /** * Define a field within a document, object, image or file definition `fields` array. * * This function will narrow the schema type down to fields and options based on the provided * type-string. * * Using `defineField` is optional, but should provide improved autocompletion in your IDE, when building your schema. * Field-properties like `validation` and `initialValue`will also be more specific. * * See {@link defineType} for more examples. * * @param schemaField - should be a valid field type definition. * @param defineOptions - optional param to provide type hints for `schemaField`. * * @see defineField * @see defineArrayMember * @see typed * * @beta */ export function defineField< const TType extends IntrinsicTypeName | AutocompleteString, const TName extends string, TSelect extends Record<string, string> | undefined, TPrepareValue extends Record<keyof TSelect, any> | undefined, TAlias extends IntrinsicTypeName | undefined, TStrict extends StrictDefinition, >( schemaField: { type: TType name: TName } & DefineSchemaBase<TType, TAlias> & NarrowPreview<TType, TAlias, TSelect, TPrepareValue> & MaybeAllowUnknownProps<TStrict> & FieldDefinitionBase, // eslint-disable-next-line @typescript-eslint/no-unused-vars defineOptions?: DefineSchemaOptions<TStrict, TAlias>, ): typeof schemaField & WidenValidation & WidenInitialValue { // TODO: re-evaluate the need for this cast return schemaField as typeof schemaField & WidenValidation & WidenInitialValue } /** * Define an array item member type within an array definition `of`-array. * * This function will narrow the schema type down to fields and options based on the provided * `type` string. * * Using `defineArrayMember` is optional, but should provide improved autocompletion in your IDE, when building your schema. * Field properties like `validation` and `initialValue` will also be more specific. * * See {@link defineType} for example usage. * * @param arrayOfSchema - should be a valid `array.of` member definition. * @param defineOptions - optional param to provide type hints for `arrayOfSchema`. * * @see defineType * @see defineField * @see typed * * @beta */ export function defineArrayMember< const TType extends IntrinsicTypeName | AutocompleteString, const TName extends string, TSelect extends Record<string, string> | undefined, TPrepareValue extends Record<keyof TSelect, any> | undefined, TAlias extends IntrinsicTypeName | undefined, TStrict extends StrictDefinition, >( arrayOfSchema: { type: TType /** * When provided, `name` is used as `_type` for the array item when stored. * * Necessary when an array contains multiple entries with the same `type`, each with * different configuration (title and initialValue for instance). */ name?: TName } & DefineArrayMemberBase<TType, TAlias> & NarrowPreview<TType, TAlias, TSelect, TPrepareValue> & MaybeAllowUnknownProps<TStrict>, // eslint-disable-next-line @typescript-eslint/no-unused-vars defineOptions?: DefineSchemaOptions<TStrict, TAlias>, ): typeof arrayOfSchema & WidenValidation & WidenInitialValue { // TODO: re-evaluate the need for this cast return arrayOfSchema as typeof arrayOfSchema & WidenValidation & WidenInitialValue } /** * `typed` can be used to ensure that an object conforms to an exact interface. * * It can be useful when working with `defineType` and `defineField` on occasions where a wider type with * custom options or properties is required. * * ## Example usage * ```ts * defineField({ * type: 'string', * name: 'nestedField', * options: typed<StringOptions & {myCustomOption: boolean}>({ * layout: 'radio', * // allowed * myCustomOption: true, * //@ts-expect-error unknownProp is not part of StringOptions & {myCustomOption: boolean} * unknownProp: 'not allowed in typed context', * }), * }), * ``` * * @param input - returned directly * * @internal */ export function typed<T>(input: T): T { return input }