UNPKG

@croct/content-model

Version:

A library for modeling, validating and interpolating structured content.

501 lines (500 loc) 17.1 kB
import { JsonPointer } from '@croct/json-pointer'; /** * Provides additional information about definitions for clarity and documentation. * * Annotations are optional properties that enhance the understandability of a definition. * They are not used for validation but as documentation elements to improve the definition's * readability and maintainability. */ export type Annotations = { /** * A human-readable label for the definition. */ title?: string; /** * A human-readable description of the definition. */ description?: string; }; /** * Definition of a single structure attribute. */ export type AttributeDefinition = { /** * The type of the attribute. */ type: ContentDefinition; /** * The label of the attribute. */ label?: string; /** * The description of the attribute. */ description?: string; /** * Whether the attribute is optional. */ optional?: boolean; /** * Whether the attribute is private. */ private?: boolean; /** * The order in which the attribute should be displayed. * * Interfaces should use this value to order the attributes as follows: * * - Attributes with a lower position value must be displayed before * attributes with a higher position value * - If two attributes have the same position value, no specific order * is guaranteed between them * - If no position is specified, no specific order is guaranteed. * * This property is used for display purposes only and does not affect * validation or serialization. */ position?: number; }; /** * Definition of one of the possible choices. */ export type ChoiceDefinition = { /** * The choice label. */ label?: string; /** * The choice description. */ description?: string; /** * Whether the choice is the default one. * * Interfaces should use this value to select the default choice as follows: * * - If exactly one option is marked as default, that option must be selected by default * - If no choice is marked as default, then no choice may be selected by default, * and an explicit choice must be required * * Multiple choices marked as default must be rejected as part of the schema validation. * Since it is possible from a typing perspective, the interface must arbitrarily select * one of the choices as the default one. * * This property is used for display purposes only and does not affect * validation or serialization. */ default?: boolean; /** * The order in which the choice should be displayed. * * Interfaces should use this value to order the choices as follows: * * - Choices with a lower position value must be displayed before * choices with a higher position value * - If two choices have the same position value, no specific order * is guaranteed between them * - If no position is specified, no specific order is guaranteed. * * This property is used for display purposes only and does not affect * validation or serialization. */ position?: number; }; /** * The map of attribute types to their definitions. */ type ContentDefinitionMap = { boolean: { /** * The labels of the two possible values. */ label?: { /** * The label for the true value. */ true: string; /** * The label for the false value. */ false: string; }; /** * The value to pre-select in the user interface. * * Interfaces should use this value to preselect the default value as follows: * * - If a default value is specified, that value must be selected by default * - If no default value is specified, then no value may be selected by default, * and an explicit value must be required * * This property is used for display purposes only and does not affect * validation or serialization. It does not affect the default value of * the attribute when absent. */ default?: boolean; }; text: { /** * The minimum length (inclusive). */ minimumLength?: number; /** * The maximum length (inclusive). */ maximumLength?: number; /** * The predefined format. */ format?: string; /** * The regular expression specifying the allowed pattern. */ pattern?: string; /** * The map of choice values to definitions. */ choices?: Record<string, ChoiceDefinition>; }; number: { /** * Whether to allow integral numbers only. * * JavaScript does not have distinct types for integers and floating-point values. * Therefore, the presence or absence of a decimal point is not enough to distinguish * between integers and non-integers. For example, `1` and `1.0` are two ways to represent * the same value in JSON. Schemas must consider that value an integer no matter which * representation was used. */ integer?: boolean; /** * The minimum allowed value (inclusive). */ minimum?: number; /** * The maximum allowed value (inclusive). */ maximum?: number; }; structure: { /** * The map of attribute names to definitions. */ attributes: { [key: string]: AttributeDefinition; }; }; list: { /** * The type of the list items. */ items: ContentDefinition; /** * A label to prefix items in the user interface. * * Interfaces should use this value to prefix items as follows: * * - If a label is specified, that label must be used to identify items. * For example, if the label is `Tag`, the first item must be labeled * `Tag 1`, the second `Tag 2`, and so on. * - If the item is a structure, and the structure has a title, that * title must be used as the prefix. For example, if the structure * has a title `Card`, the first item must be labeled `Card 1`, the * second `Card 2`, and so on. * - If none of the above applies, a generic label, such as `Item`, * must be used to prefix items. * * This property is used for display purposes only and does not affect * validation or serialization. */ itemLabel?: string; /** * The minimum number of items (inclusive). */ minimumLength?: number; /** * The maximum number of items (inclusive). */ maximumLength?: number; }; union: { /** * The map of type names to definitions. */ types: Record<string, ContentDefinition<'structure' | 'reference'>>; }; reference: { /** * The ID of the referenced structure. */ id: string; /** * Extra properties to merge with the referenced definition. */ properties?: Record<string, any>; }; }; /** * The set of all content definition types. */ export type ContentDefinitionType = keyof ContentDefinitionMap; /** * The map of logical types to their definitions. * * This interface can be augmented to add support for custom types. * For example, to add support for a new type named `color`, * create a new declaration file with the following content: * * @example * declare module '@croct-tech/content-model/definition' { * interface LogicalDefinitionMap { * color: { * type: 'text', * format: 'color'; * } * } * } */ export interface LogicalDefinitionMap { } /** * The set of all logical definition types. */ export type LogicalDefinitionType = keyof LogicalDefinitionMap; /** * An augmented content definition. * * Logical types provide a way to define extensions to the content model * that add semantic meaning to the content. For example, a logical type * named `media` can be used to represent a media type, such as an image * or a video. Consumers of the content model can then use this information * to provide a better editing experience and feedback, such as a file picker * for media types or a rich text editor for rich text types. * * A logical content type must always be validated against the constraints * of the underlying content type. Implementations may ignore unknown logical * types and treat them as regular content types with no extra constraints. * * Logical types are defined by augmenting the {@link LogicalDefinitionMap} * interface. * * @typeParam T The logical type name. */ export type LogicalDefinition<T extends LogicalDefinitionType = LogicalDefinitionType> = BaseContentDefinition<LogicalDefinitionMap[T]['type']> & BaseLogicalDefinition<T>; /** * The base definition of a logical type. * * @typeParam T The logical type name. */ type BaseLogicalDefinition<T extends LogicalDefinitionType = LogicalDefinitionType> = { [P in T]: { logicalType: P; } & Omit<LogicalDefinitionMap[P], 'type'>; }[T]; /** * A template for a content definition. * * @typeParam T The logical type name. */ export type TemplateDefinition<T extends LogicalDefinitionType = LogicalDefinitionType> = BaseContentDefinition<LogicalDefinitionMap[T]['type']> & { template: true; } & Partial<BaseLogicalDefinition<T>>; /** * A content definition augmented with logical types. * * @typeParam T The type of the content definition. */ type DerivedLogicalDefinition<T extends ContentDefinitionType> = { [K in LogicalDefinitionType]: LogicalDefinitionMap[K]['type'] extends T ? (LogicalDefinition<K> & { template?: false; }) | TemplateDefinition<K> : never; }[LogicalDefinitionType]; /** * The base definition of a content definition. */ type BaseContentDefinition<T extends ContentDefinitionType = ContentDefinitionType> = { [K in keyof ContentDefinitionMap]: ContentDefinitionMap[K] & Annotations & { type: K; }; }[T]; /** * The definition of a content type. */ export type ContentDefinition<T extends ContentDefinitionType = ContentDefinitionType> = (BaseContentDefinition<T> & { logicalType?: never; }) | DerivedLogicalDefinition<T>; /** * A root definition. */ export type RootDefinition = ContentDefinition<'structure' | 'union'>; /** * A utility type that excludes content references from a content definition. * * This type works recursively on structure and union definitions, meaning that * it can be used on {@link ContentDefinition}, {@link RootDefinition}, * or any other definition that may contain references. * * @typeParam T The content definition type. */ export type Resolved<T> = T extends Record<string, unknown> ? Exclude<{ [K in keyof T]: Resolved<T[K]>; }, ContentDefinition<'reference'> | TemplateDefinition> : T; /** * The set of all content definition types excluding references. * * This type is the same as {@link ContentDefinitionType} except that it excludes * the `reference` type. */ export type ResolvedDefinitionType = Exclude<ContentDefinitionType, 'reference'>; /** * A content definition excluding references in all its nested structures. * * This type is the same as {@link ContentDefinition} except that it excludes * the `reference` type. * * @typeParam T The content definition type. */ export type ResolvedDefinition<T extends ResolvedDefinitionType = ResolvedDefinitionType> = Resolved<ContentDefinition<T>>; /** * A root definition excluding references in all its nested structures. * * This type is the same as {@link RootDefinition} except that it excludes * the `reference` type. */ export type ResolvedRootDefinition = ResolvedDefinition<'structure' | 'union'>; /** * A self-contained content definition. */ export type ContentDefinitionBundle = { /** * The root content definition. */ root: ContentDefinition; /** * The map of referenced content definitions. */ definitions: Record<string, ContentDefinition>; }; export declare namespace ContentDefinition { /** * A definition visitor. * * Visitor is a generic interface for classes that perform an operation on a * content definition, usually by traversing it. * * @typeParam T The type of the visitor's return value. */ interface Visitor<T> { /** * Visit a content definition. * * @param definition The definition to visit * * @returns {T} The visitor's return value */ visit(definition: ContentDefinition): T; } /** * A visitor that dispatches the call to specific visit methods. * * @typeParam T The type of the visitor's return value. */ abstract class Dispatcher<T> implements Visitor<T> { /** * Visit a content definition. * * @param definition The definition to visit * @param path The path to the definition. * * @returns {T} The visitor's return value */ visit(definition: ContentDefinition, path?: JsonPointer): T; /** * Visit a boolean definition. * * @param definition The definition to visit. * @param path The path to the definition. * * @returns {T} The result. */ protected abstract visitBoolean(definition: ContentDefinition<'boolean'>, path: JsonPointer): T; /** * Visit a text definition. * * @param definition The definition to visit. * @param path The path to the definition. * * @returns {T} The result. */ protected abstract visitText(definition: ContentDefinition<'text'>, path: JsonPointer): T; /** * Visit a number definition. * * @param definition The definition to visit. * @param path The path to the definition. * * @returns {T} The result. */ protected abstract visitNumber(definition: ContentDefinition<'number'>, path: JsonPointer): T; /** * Visit a structure definition. * * @param definition The definition to visit. * @param path The path to the definition. * * @returns {T} The result. */ protected abstract visitStructure(definition: ContentDefinition<'structure'>, path: JsonPointer): T; /** * Visit a list definition. * * @param definition The definition to visit. * @param path The path to the definition. * * @returns {T} The result. */ protected abstract visitList(definition: ContentDefinition<'list'>, path: JsonPointer): T; /** * Visit a union definition. * * @param definition The definition to visit. * @param path The path to the definition. * * @returns {T} The result. */ protected abstract visitUnion(definition: ContentDefinition<'union'>, path: JsonPointer): T; /** * Visit a reference definition. * * @param definition The definition to visit. * @param path The path to the definition. * * @returns {T} The result. */ protected abstract visitReference(definition: ContentDefinition<'reference'>, path: JsonPointer): T; } /** * A visitor that dispatches the call to the corresponding method with a default return value. * * @typeParam T The type of the visitor's return value. */ abstract class PartialVisitor<T> extends Dispatcher<T> { protected visitBoolean(definition: ContentDefinition<'boolean'>, path: JsonPointer): T; protected visitText(definition: ContentDefinition<'text'>, path: JsonPointer): T; protected visitNumber(definition: ContentDefinition<'number'>, path: JsonPointer): T; protected visitStructure(definition: ContentDefinition<'structure'>, path: JsonPointer): T; protected visitList(definition: ContentDefinition<'list'>, path: JsonPointer): T; protected visitUnion(definition: ContentDefinition<'union'>, path: JsonPointer): T; protected visitReference(definition: ContentDefinition<'reference'>, path: JsonPointer): T; /** * The default return value for non-overridden methods. * * @param definition The definition to visit. * @param path The path to the definition. * * @returns {T} The default result. */ protected abstract visitDefinition(definition: ContentDefinition, path: JsonPointer): T; } } export {};