UNPKG

@sanity/schema

Version:
1 lines 163 kB
{"version":3,"file":"_internal.mjs","sources":["../src/descriptors/convert.ts","../src/descriptors/sync.ts","../src/legacy/actionUtils.ts","../src/sanity/builtinTypes/assetSourceData.ts","../src/sanity/builtinTypes/fileAsset.ts","../src/sanity/builtinTypes/geopoint.ts","../src/sanity/builtinTypes/imageAsset.ts","../src/sanity/builtinTypes/imageCrop.ts","../src/sanity/builtinTypes/imageDimensions.ts","../src/sanity/builtinTypes/imageHotspot.ts","../src/sanity/builtinTypes/imageMetadata.ts","../src/sanity/builtinTypes/imagePalette.ts","../src/sanity/builtinTypes/imagePaletteSwatch.ts","../src/sanity/builtinTypes/slug.ts","../src/sanity/builtinTypes/index.ts","../src/sanity/extractSchema.ts","../src/sanity/validation/createValidationResult.ts","../src/sanity/groupProblems.ts","../src/sanity/validation/utils/getDupes.ts","../src/core/traverseSchema.ts","../src/sanity/coreTypes.ts","../src/sanity/traverseSchema.ts","../src/sanity/validation/types/array.ts","../src/sanity/validation/utils/isJSONTypeOf.ts","../src/sanity/validation/types/block.ts","../src/sanity/validation/utils/validateNonObjectFieldsProp.ts","../src/sanity/validation/utils/validateTypeName.ts","../src/sanity/validation/types/deprecated.ts","../src/sanity/validation/types/common.ts","../src/sanity/validation/types/crossDatasetReference.ts","../src/sanity/validation/utils/isComponent.ts","../src/sanity/validation/utils/validateComponent.ts","../src/sanity/validation/types/object.ts","../src/sanity/validation/types/document.ts","../src/sanity/validation/types/file.ts","../src/sanity/validation/types/globalDocumentReference.ts","../src/sanity/validation/types/image.ts","../src/sanity/validation/types/reference.ts","../src/sanity/validation/types/rootType.ts","../src/sanity/validation/types/slug.ts","../src/sanity/validateSchema.ts","../src/sanity/validateMediaLibraryAssetAspect.ts"],"sourcesContent":["import {\n type EncodableObject,\n type EncodableValue,\n SetBuilder,\n type SetSynchronization,\n} from '@sanity/descriptors'\nimport {\n type ArraySchemaType,\n type FieldGroupDefinition,\n type FieldsetDefinition,\n type ObjectSchemaType,\n type ReferenceSchemaType,\n type Schema,\n type SchemaType,\n} from '@sanity/types'\n\nimport {OWN_PROPS_NAME} from '../legacy/types/constants'\nimport {\n type ArrayTypeDef,\n type CommonTypeDef,\n type CoreTypeDef,\n type CyclicMarker,\n type DepthMarker,\n type FunctionMarker,\n type JSXMarker,\n type ObjectField,\n type ObjectFieldset,\n type ObjectGroup,\n type ReferenceTypeDef,\n type RegistryType,\n type SubtypeDef,\n type TypeDef,\n type UndefinedMarker,\n type UnknownMarker,\n} from './types'\n\nconst MAX_DEPTH_UKNOWN = 5\n\ntype UnknownRecord<T> = {[P in keyof T]: unknown}\n\nexport class DescriptorConverter {\n opts: Options\n cache: WeakMap<Schema, SetSynchronization<RegistryType>> = new WeakMap()\n\n constructor(opts: Options) {\n this.opts = opts\n }\n\n /**\n * Returns a synchronization object for a schema.\n *\n * This is automatically cached in a weak map.\n */\n get(schema: Schema): SetSynchronization<RegistryType> {\n let value = this.cache.get(schema)\n if (value) return value\n\n const builder = new SetBuilder()\n for (const name of schema.getLocalTypeNames()) {\n const typeDef = convertTypeDef(schema.get(name)!, this.opts)\n builder.addObject('sanity.schema.namedType', {name, typeDef})\n }\n\n if (schema.parent) {\n builder.addSet(this.get(schema.parent))\n }\n\n value = builder.build('sanity.schema.registry')\n this.cache.set(schema, value)\n return value\n }\n}\n\nfunction convertCommonTypeDef(schemaType: SchemaType, opts: Options): CommonTypeDef {\n // Note that OWN_PROPS_NAME is only set on subtypes, not the core types.\n // We might consider setting OWN_PROPS_NAME on _all_ types to avoid this branch.\n const ownProps = OWN_PROPS_NAME in schemaType ? (schemaType as any)[OWN_PROPS_NAME] : schemaType\n\n let fields: ObjectField[] | undefined\n if (Array.isArray(ownProps.fields)) {\n fields = (ownProps.fields as ObjectSchemaType['fields']).map(\n ({name, group, fieldset, type}) => ({\n name,\n typeDef: convertTypeDef(type, opts),\n groups: arrayifyString(group),\n fieldset,\n }),\n )\n }\n\n let fieldsets: ObjectFieldset[] | undefined\n if (Array.isArray(ownProps.fieldsets)) {\n fieldsets = filterStringKey(\n 'name',\n (ownProps.fieldsets as Array<UnknownRecord<FieldsetDefinition>>).map(\n ({name, title, description, group, hidden, readOnly, options}) => ({\n name,\n title: maybeString(title),\n description: maybeString(description),\n group: maybeString(group),\n hidden: conditionalTrue(hidden),\n readOnly: conditionalTrue(readOnly),\n options: convertUnknown(options),\n }),\n ),\n )\n }\n\n let groups: ObjectGroup[] | undefined\n if (Array.isArray(ownProps.groups)) {\n groups = filterStringKey(\n 'name',\n (ownProps.groups as Array<UnknownRecord<FieldGroupDefinition>>).map(\n ({name, title, hidden, default: def}) => ({\n name,\n title: maybeString(title),\n hidden: conditionalTrue(hidden),\n default: maybeTrue(def),\n }),\n ),\n )\n }\n\n const reason = ownProps.deprecated?.reason\n\n return {\n title: maybeString(ownProps.title),\n description: maybeStringOrJSX(ownProps.description),\n readOnly: conditionalTrue(ownProps.readOnly),\n hidden: conditionalTrue(ownProps.hidden),\n liveEdit: maybeTrue(ownProps.liveEdit),\n options: convertUnknown(ownProps.options),\n initialValue: convertUnknown(ownProps.initialValue),\n deprecated: typeof reason === 'string' ? {reason} : undefined,\n placeholder: maybeString(ownProps.placeholder),\n rows: maybeNumberAsString(ownProps.rows),\n fields,\n fieldsets,\n groups,\n }\n}\n\n/**\n * Options used when converting the schema.\n *\n * We know we need this in order to handle validations.\n **/\nexport type Options = Record<never, never>\n\nexport function convertTypeDef(schemaType: SchemaType, opts: Options): TypeDef {\n const common = convertCommonTypeDef(schemaType, opts)\n\n if (!schemaType.type) {\n return {\n extends: null,\n jsonType: schemaType.jsonType,\n ...common,\n } satisfies CoreTypeDef\n }\n\n // The types below are somewhat magical: It's only direct subtypes of array/reference which\n // are allowed to have of/to assigned to them. We handle them specifically here since this\n // gives us more control over the types.\n\n switch (schemaType.type.name) {\n case 'array': {\n return {\n extends: 'array',\n of: (schemaType as ArraySchemaType).of.map((ofType) => ({\n name: ofType.name,\n typeDef: convertTypeDef(ofType, opts),\n })),\n ...common,\n } satisfies ArrayTypeDef\n }\n case 'reference':\n case 'globalDocumentReference':\n case 'crossDatasetReference':\n return {\n extends: schemaType.type.name,\n to: filterStringKey(\n 'name',\n (schemaType as ReferenceSchemaType).to\n // The `toType.type` case is for crossDatasetReferences/crossDatasetReference\n .map((toType) => ({name: toType.name || toType.type?.name || toType.type})),\n ),\n ...common,\n } satisfies ReferenceTypeDef\n default:\n return {extends: schemaType.type.name, ...common} satisfies SubtypeDef\n }\n}\n\nfunction maybeString(val: unknown): string | undefined {\n return typeof val === 'string' ? val : undefined\n}\n\nfunction maybeNumberAsString(val: unknown): string | undefined {\n return typeof val === 'number' ? val.toString() : undefined\n}\n\n/** Returns `true` for `true` and undefined for everything else. */\nfunction maybeTrue(val: unknown): true | undefined {\n return val === true ? true : undefined\n}\n\nfunction conditionalTrue(val: unknown): true | undefined | FunctionMarker {\n if (typeof val === 'function') return FUNCTION_MARKER\n return maybeTrue(val)\n}\n\nfunction filterStringKey<T, K extends keyof T>(key: K, arr: T[]): Array<T & {[key in K]: string}> {\n return arr.filter((obj): obj is T & {[key in K]: string} => typeof obj[key] === 'string')\n}\n\nfunction arrayifyString(val: unknown): string[] | undefined {\n if (typeof val === 'string') {\n return [val]\n }\n\n if (Array.isArray(val)) {\n return val.filter((elem) => typeof elem === 'string')\n }\n\n return undefined\n}\n\nconst FUNCTION_MARKER: FunctionMarker = {__type: 'function'}\nconst UNKNOWN_MARKER: UnknownMarker = {__type: 'unknown'}\nconst UNDEFINED_MARKER: UndefinedMarker = {__type: 'undefined'}\nconst CYCLIC_MARKER: CyclicMarker = {__type: 'cyclic'}\nconst MAX_DEPTH_MARKER: DepthMarker = {__type: 'maxDepth'}\n\nfunction convertUnknown(\n val: unknown,\n seen = new Set(),\n maxDepth = MAX_DEPTH_UKNOWN,\n): EncodableValue | undefined {\n if (maxDepth === 0) return MAX_DEPTH_MARKER\n\n if (typeof val === 'string' || typeof val === 'boolean' || val === null || val === undefined) {\n return val\n }\n\n if (typeof val === 'number') {\n return {__type: 'number', value: val.toString()}\n }\n\n if (typeof val === 'function') return FUNCTION_MARKER\n\n if (seen.has(val)) {\n return CYCLIC_MARKER\n }\n\n seen.add(val)\n\n if (typeof val === 'object') {\n if (Array.isArray(val)) {\n return val.map((elem) => {\n const res = convertUnknown(elem, seen, maxDepth - 1)\n return res === undefined ? UNDEFINED_MARKER : res\n })\n }\n\n if ('$$typeof' in val && 'type' in val && 'props' in val) {\n // React element:\n const {type, props} = val\n const strType = typeof type === 'function' ? type.name : type\n if (typeof strType !== 'string') return undefined\n return {\n __type: 'jsx',\n type: strType,\n props: convertUnknown(props, seen, maxDepth - 1) as EncodableObject,\n }\n }\n\n let hasType = false\n const result: EncodableObject = {}\n for (const [key, field] of Object.entries(val)) {\n if (key === '__type') hasType = true\n result[key] = convertUnknown(field, seen, maxDepth - 1)\n }\n\n return hasType ? {__type: 'object', value: result} : result\n }\n\n return UNKNOWN_MARKER\n}\n\nfunction maybeStringOrJSX(val: unknown): string | undefined | JSXMarker {\n if (typeof val === 'string') return val\n if (val && typeof val === 'object' && '$$typeof' in val && 'type' in val && 'props' in val) {\n const {type, props} = val\n const strType = typeof type === 'function' ? type.name : type\n if (typeof strType !== 'string') return undefined\n return {__type: 'jsx', type: strType, props: convertUnknown(props) as EncodableObject}\n }\n return undefined\n}\n","import {\n processSetSynchronization,\n type SetSynchronization,\n type SynchronizationRequest,\n type SynchronizationResult,\n} from '@sanity/descriptors'\n\nimport {type RegistryType} from './types'\n\n// This file provides wrapper types/functions for synchronizing a schema.\n// This avoids users of `@sanity/schema` to have to depend on `@sanity/descriptors`.\n\nexport type SchemaSynchronizationRequest = SynchronizationRequest\nexport type SchemaSynchronizationResult = SynchronizationResult\n\n/**\n * Returns the next request that should be generated for synchronizing the\n * schema, based on the previous response from the /synchronize endpoint.\n *\n * @param response - The previous response, or `null` if it's the first request.\n * @returns The next request, or `null` if it's been fully synchronized.\n */\nexport function processSchemaSynchronization(\n sync: SetSynchronization<RegistryType>,\n response: SchemaSynchronizationResult | null,\n): SchemaSynchronizationRequest | null {\n return processSetSynchronization(sync, response)\n}\n","import {generateHelpUrl} from '@sanity/generate-help-url'\nimport {type SchemaType} from '@sanity/types'\nimport {difference} from 'lodash'\n\nconst ACTIONS_FLAG = '__experimental_actions'\n\nconst DEFAULT_ACTIONS = ['create', 'update', 'delete', 'publish']\nconst VALID_ACTIONS = DEFAULT_ACTIONS\n\n// todo: enable this when officially deprecating experimental actions\nconst DEPRECATE_EXPERIMENTAL_ACTIONS = false\n\nconst hasWarned = {}\nconst readActions = (schemaType: SchemaType): string[] => {\n if (DEPRECATE_EXPERIMENTAL_ACTIONS && !(schemaType.name in hasWarned)) {\n console.warn(`Heads up! Experimental actions is now deprecated and replaced by Document Actions. Read more about how to migrate on ${generateHelpUrl(\n 'experimental-actions-replaced-by-document-actions',\n )}\".\n`)\n ;(hasWarned as any)[schemaType.name] = true\n }\n\n return ACTIONS_FLAG in schemaType ? (schemaType[ACTIONS_FLAG] as string[]) : DEFAULT_ACTIONS\n}\n\nconst validateActions = (typeName: string, actions: string[]) => {\n if (!Array.isArray(actions)) {\n throw new Error(\n `The value of <type>.${ACTIONS_FLAG} should be an array with any of the actions ${VALID_ACTIONS.join(\n ', ',\n )}`,\n )\n }\n\n const invalid = difference(actions, VALID_ACTIONS)\n\n if (invalid.length > 0) {\n throw new Error(\n `Invalid action${\n invalid.length > 1 ? 's' : ''\n } configured for schema type \"${typeName}\": ${invalid.join(\n ', ',\n )}. Valid actions are: ${VALID_ACTIONS.join(', ')}`,\n )\n }\n\n return actions\n}\n\nexport const resolveEnabledActions = (schemaType: SchemaType): string[] =>\n validateActions(schemaType.name, readActions(schemaType))\n\nexport const isActionEnabled = (schemaType: SchemaType, action: string): boolean =>\n resolveEnabledActions(schemaType).includes(action)\n","export default {\n name: 'sanity.assetSourceData',\n title: 'Asset Source Data',\n type: 'object',\n fields: [\n {\n name: 'name',\n title: 'Source name',\n description: 'A canonical name for the source this asset is originating from',\n type: 'string',\n },\n {\n name: 'id',\n title: 'Asset Source ID',\n description:\n 'The unique ID for the asset within the originating source so you can programatically find back to it',\n type: 'string',\n },\n {\n name: 'url',\n title: 'Asset information URL',\n description: 'A URL to find more information about this asset in the originating source',\n type: 'string',\n },\n ],\n}\n","export default {\n name: 'sanity.fileAsset',\n title: 'File',\n type: 'document',\n fieldsets: [\n {\n name: 'system',\n title: 'System fields',\n description: 'These fields are managed by the system and not editable',\n },\n ],\n fields: [\n {\n name: 'originalFilename',\n type: 'string',\n title: 'Original file name',\n readOnly: true,\n },\n {\n name: 'label',\n type: 'string',\n title: 'Label',\n },\n {\n name: 'title',\n type: 'string',\n title: 'Title',\n },\n {\n name: 'description',\n type: 'string',\n title: 'Description',\n },\n {\n name: 'altText',\n type: 'string',\n title: 'Alternative text',\n },\n {\n name: 'sha1hash',\n type: 'string',\n title: 'SHA1 hash',\n readOnly: true,\n fieldset: 'system',\n },\n {\n name: 'extension',\n type: 'string',\n title: 'File extension',\n readOnly: true,\n fieldset: 'system',\n },\n {\n name: 'mimeType',\n type: 'string',\n title: 'Mime type',\n readOnly: true,\n fieldset: 'system',\n },\n {\n name: 'size',\n type: 'number',\n title: 'File size in bytes',\n readOnly: true,\n fieldset: 'system',\n },\n {\n name: 'assetId',\n type: 'string',\n title: 'Asset ID',\n readOnly: true,\n fieldset: 'system',\n },\n {\n name: 'uploadId',\n type: 'string',\n readOnly: true,\n hidden: true,\n fieldset: 'system',\n },\n {\n name: 'path',\n type: 'string',\n title: 'Path',\n readOnly: true,\n fieldset: 'system',\n },\n {\n name: 'url',\n type: 'string',\n title: 'Url',\n readOnly: true,\n fieldset: 'system',\n },\n {\n name: 'source',\n type: 'sanity.assetSourceData',\n title: 'Source',\n readOnly: true,\n fieldset: 'system',\n },\n ],\n preview: {\n select: {\n title: 'originalFilename',\n path: 'path',\n mimeType: 'mimeType',\n size: 'size',\n },\n prepare(doc: Record<string, any>) {\n return {\n title: doc.title || doc.path.split('/').slice(-1)[0],\n subtitle: `${doc.mimeType} (${(doc.size / 1024 / 1024).toFixed(2)} MB)`,\n }\n },\n },\n orderings: [\n {\n title: 'File size',\n name: 'fileSizeDesc',\n by: [{field: 'size', direction: 'desc'}],\n },\n ],\n}\n","export default {\n title: 'Geographical Point',\n name: 'geopoint',\n type: 'object',\n fields: [\n {\n name: 'lat',\n type: 'number',\n title: 'Latitude',\n },\n {\n name: 'lng',\n type: 'number',\n title: 'Longitude',\n },\n {\n name: 'alt',\n type: 'number',\n title: 'Altitude',\n },\n ],\n}\n","import {type SanityDocument} from '@sanity/types'\n\nexport default {\n name: 'sanity.imageAsset',\n title: 'Image',\n type: 'document',\n fieldsets: [\n {\n name: 'system',\n title: 'System fields',\n description: 'These fields are managed by the system and not editable',\n },\n ],\n fields: [\n {\n name: 'originalFilename',\n type: 'string',\n title: 'Original file name',\n readOnly: true,\n },\n {\n name: 'label',\n type: 'string',\n title: 'Label',\n },\n {\n name: 'title',\n type: 'string',\n title: 'Title',\n },\n {\n name: 'description',\n type: 'string',\n title: 'Description',\n },\n {\n name: 'altText',\n type: 'string',\n title: 'Alternative text',\n },\n {\n name: 'sha1hash',\n type: 'string',\n title: 'SHA1 hash',\n readOnly: true,\n fieldset: 'system',\n },\n {\n name: 'extension',\n type: 'string',\n readOnly: true,\n title: 'File extension',\n fieldset: 'system',\n },\n {\n name: 'mimeType',\n type: 'string',\n readOnly: true,\n title: 'Mime type',\n fieldset: 'system',\n },\n {\n name: 'size',\n type: 'number',\n title: 'File size in bytes',\n readOnly: true,\n fieldset: 'system',\n },\n {\n name: 'assetId',\n type: 'string',\n title: 'Asset ID',\n readOnly: true,\n fieldset: 'system',\n },\n {\n name: 'uploadId',\n type: 'string',\n readOnly: true,\n hidden: true,\n fieldset: 'system',\n },\n {\n name: 'path',\n type: 'string',\n title: 'Path',\n readOnly: true,\n fieldset: 'system',\n },\n {\n name: 'url',\n type: 'string',\n title: 'Url',\n readOnly: true,\n fieldset: 'system',\n },\n {\n name: 'metadata',\n type: 'sanity.imageMetadata',\n title: 'Metadata',\n },\n {\n name: 'source',\n type: 'sanity.assetSourceData',\n title: 'Source',\n readOnly: true,\n fieldset: 'system',\n },\n ],\n preview: {\n select: {\n id: '_id',\n title: 'originalFilename',\n mimeType: 'mimeType',\n size: 'size',\n },\n prepare(doc: Partial<SanityDocument>) {\n return {\n title: doc.title || (typeof doc.path === 'string' && doc.path.split('/').slice(-1)[0]),\n media: {asset: {_ref: doc.id}},\n subtitle: `${doc.mimeType} (${(Number(doc.size) / 1024 / 1024).toFixed(2)} MB)`,\n }\n },\n },\n orderings: [\n {\n title: 'File size',\n name: 'fileSizeDesc',\n by: [{field: 'size', direction: 'desc'}],\n },\n ],\n}\n","export default {\n name: 'sanity.imageCrop',\n title: 'Image crop',\n type: 'object',\n fields: [\n {\n name: 'top',\n type: 'number',\n },\n {\n name: 'bottom',\n type: 'number',\n },\n {\n name: 'left',\n type: 'number',\n },\n {\n name: 'right',\n type: 'number',\n },\n ],\n}\n","export default {\n name: 'sanity.imageDimensions',\n type: 'object',\n title: 'Image dimensions',\n fields: [\n {name: 'height', type: 'number', title: 'Height', readOnly: true},\n {name: 'width', type: 'number', title: 'Width', readOnly: true},\n {name: 'aspectRatio', type: 'number', title: 'Aspect ratio', readOnly: true},\n ],\n}\n","export default {\n name: 'sanity.imageHotspot',\n title: 'Image hotspot',\n type: 'object',\n fields: [\n {\n name: 'x',\n type: 'number',\n },\n {\n name: 'y',\n type: 'number',\n },\n {\n name: 'height',\n type: 'number',\n },\n {\n name: 'width',\n type: 'number',\n },\n ],\n}\n","export default {\n name: 'sanity.imageMetadata',\n title: 'Image metadata',\n type: 'object',\n fieldsets: [\n {\n name: 'extra',\n title: 'Extra metadata…',\n options: {\n collapsable: true,\n },\n },\n ],\n fields: [\n {\n name: 'location',\n type: 'geopoint',\n },\n {\n name: 'dimensions',\n title: 'Dimensions',\n type: 'sanity.imageDimensions',\n fieldset: 'extra',\n },\n {\n name: 'palette',\n type: 'sanity.imagePalette',\n title: 'Palette',\n fieldset: 'extra',\n },\n {\n name: 'lqip',\n title: 'LQIP (Low-Quality Image Placeholder)',\n type: 'string',\n readOnly: true,\n },\n {\n name: 'blurHash',\n title: 'BlurHash',\n type: 'string',\n readOnly: true,\n },\n {\n name: 'hasAlpha',\n title: 'Has alpha channel',\n type: 'boolean',\n readOnly: true,\n },\n {\n name: 'isOpaque',\n title: 'Is opaque',\n type: 'boolean',\n readOnly: true,\n },\n ],\n}\n","export default {\n name: 'sanity.imagePalette',\n title: 'Image palette',\n type: 'object',\n fields: [\n {name: 'darkMuted', type: 'sanity.imagePaletteSwatch', title: 'Dark Muted'},\n {name: 'lightVibrant', type: 'sanity.imagePaletteSwatch', title: 'Light Vibrant'},\n {name: 'darkVibrant', type: 'sanity.imagePaletteSwatch', title: 'Dark Vibrant'},\n {name: 'vibrant', type: 'sanity.imagePaletteSwatch', title: 'Vibrant'},\n {name: 'dominant', type: 'sanity.imagePaletteSwatch', title: 'Dominant'},\n {name: 'lightMuted', type: 'sanity.imagePaletteSwatch', title: 'Light Muted'},\n {name: 'muted', type: 'sanity.imagePaletteSwatch', title: 'Muted'},\n ],\n}\n","export default {\n name: 'sanity.imagePaletteSwatch',\n title: 'Image palette swatch',\n type: 'object',\n fields: [\n {name: 'background', type: 'string', title: 'Background', readOnly: true},\n {name: 'foreground', type: 'string', title: 'Foreground', readOnly: true},\n {name: 'population', type: 'number', title: 'Population', readOnly: true},\n {name: 'title', type: 'string', title: 'String', readOnly: true},\n ],\n}\n","import {type Rule} from '@sanity/types'\n\nexport default {\n title: 'Slug',\n name: 'slug',\n type: 'object',\n fields: [\n {\n name: 'current',\n title: 'Current slug',\n type: 'string',\n validation: (Rule: Rule): Rule => Rule.required(),\n },\n {\n // The source field is deprecated/unused, but leaving it included and hidden\n // to prevent rendering \"Unknown field\" warnings on legacy data\n name: 'source',\n title: 'Source field',\n type: 'string',\n hidden: true,\n },\n ],\n}\n","import assetSourceData from './assetSourceData'\nimport fileAsset from './fileAsset'\nimport geopoint from './geopoint'\nimport imageAsset from './imageAsset'\nimport imageCrop from './imageCrop'\nimport imageDimensions from './imageDimensions'\nimport imageHotspot from './imageHotspot'\nimport imageMetadata from './imageMetadata'\nimport imagePalette from './imagePalette'\nimport imagePaletteSwatch from './imagePaletteSwatch'\nimport slug from './slug'\n\nexport const builtinTypes = [\n assetSourceData,\n slug,\n geopoint,\n // legacyRichDate,\n imageAsset,\n fileAsset,\n imageCrop,\n imageHotspot,\n imageMetadata,\n imageDimensions,\n imagePalette,\n imagePaletteSwatch,\n]\n","import {\n type ArraySchemaType,\n type NumberSchemaType,\n type ObjectField,\n type ObjectFieldType,\n type ObjectSchemaType,\n type ReferenceSchemaType,\n type Rule,\n type Schema as SchemaDef,\n type SchemaType as SanitySchemaType,\n type StringSchemaType,\n} from '@sanity/types'\nimport {\n type ArrayTypeNode,\n createReferenceTypeNode,\n type DocumentSchemaType,\n type InlineTypeNode,\n type NullTypeNode,\n type NumberTypeNode,\n type ObjectAttribute,\n type ObjectTypeNode,\n type SchemaType,\n type StringTypeNode,\n type TypeDeclarationSchemaType,\n type TypeNode,\n type UnionTypeNode,\n type UnknownTypeNode,\n} from 'groq-js'\n\nconst documentDefaultFields = (typeName: string): Record<string, ObjectAttribute> => ({\n _id: {\n type: 'objectAttribute',\n value: {type: 'string'},\n },\n _type: {\n type: 'objectAttribute',\n value: {type: 'string', value: typeName},\n },\n _createdAt: {\n type: 'objectAttribute',\n value: {type: 'string'},\n },\n _updatedAt: {\n type: 'objectAttribute',\n value: {type: 'string'},\n },\n _rev: {\n type: 'objectAttribute',\n value: {type: 'string'},\n },\n})\nconst typesMap = new Map<string, TypeNode>([\n ['text', {type: 'string'}],\n ['url', {type: 'string'}],\n ['datetime', {type: 'string'}],\n ['date', {type: 'string'}],\n ['boolean', {type: 'boolean'}],\n ['email', {type: 'string'}],\n])\n\nexport interface ExtractSchemaOptions {\n enforceRequiredFields?: boolean\n}\n\nexport function extractSchema(\n schemaDef: SchemaDef,\n extractOptions: ExtractSchemaOptions = {},\n): SchemaType {\n const inlineFields = new Set<SanitySchemaType>()\n const documentTypes = new Map<string, DocumentSchemaType>()\n const schema: SchemaType = []\n\n // get a list of all the types in the schema, sorted by their dependencies. This ensures that when we check for inline/reference types, we have already processed the type\n const sortedSchemaTypeNames = sortByDependencies(schemaDef)\n sortedSchemaTypeNames.forEach((typeName) => {\n const schemaType = schemaDef.get(typeName)\n if (schemaType === undefined) {\n return\n }\n const base = convertBaseType(schemaType)\n if (base === null) {\n return\n }\n if (base.type === 'type') {\n inlineFields.add(schemaType)\n }\n if (base.type === 'document') {\n documentTypes.set(typeName, base)\n }\n\n schema.push(base)\n })\n\n function convertBaseType(\n schemaType: SanitySchemaType,\n ): DocumentSchemaType | TypeDeclarationSchemaType | null {\n let typeName: string | undefined\n if (schemaType.type) {\n typeName = schemaType.type.name\n } else if ('jsonType' in schemaType) {\n typeName = schemaType.jsonType\n }\n\n if (typeName === 'document' && isObjectType(schemaType)) {\n const defaultAttributes = documentDefaultFields(schemaType.name)\n\n const object = createObject(schemaType)\n if (object.type === 'unknown') {\n return null\n }\n\n return {\n name: schemaType.name,\n type: 'document',\n attributes: {\n ...defaultAttributes,\n ...object.attributes,\n },\n }\n }\n\n const value = convertSchemaType(schemaType)\n if (value.type === 'unknown') {\n return null\n }\n if (value.type === 'object') {\n value.attributes = {\n _type: {\n type: 'objectAttribute',\n value: {\n type: 'string',\n value: schemaType.name,\n },\n },\n ...value.attributes,\n }\n return {\n name: schemaType.name,\n type: 'type',\n value,\n }\n }\n\n return {\n name: schemaType.name,\n type: 'type',\n value,\n }\n }\n\n function convertSchemaType(schemaType: SanitySchemaType): TypeNode {\n // if we have already seen the base type, we can just reference it\n if (inlineFields.has(schemaType.type!)) {\n return {type: 'inline', name: schemaType.type!.name} satisfies InlineTypeNode\n }\n\n // If we have a type that is point to a type, that is pointing to a type, we assume this is a circular reference\n // and we return an inline type referencing it instead\n if (schemaType.type?.type?.name === 'object') {\n return {type: 'inline', name: schemaType.type.name} satisfies InlineTypeNode\n }\n\n if (isStringType(schemaType)) {\n return createStringTypeNodeDefintion(schemaType)\n }\n\n if (isNumberType(schemaType)) {\n return createNumberTypeNodeDefintion(schemaType)\n }\n\n // map some known types\n if (schemaType.type && typesMap.has(schemaType.type.name)) {\n return typesMap.get(schemaType.type.name)!\n }\n\n // Cross dataset references are not supported\n if (isCrossDatasetReferenceType(schemaType)) {\n return {type: 'unknown'} satisfies UnknownTypeNode // we don't support cross-dataset references at the moment\n }\n\n // Global document references are not supported\n if (isGlobalDocumentReferenceType(schemaType)) {\n return {type: 'unknown'} satisfies UnknownTypeNode // we don't support global document references at the moment\n }\n\n if (isReferenceType(schemaType)) {\n return createReferenceTypeNodeDefintion(schemaType)\n }\n\n if (isArrayType(schemaType)) {\n return createArray(schemaType)\n }\n\n if (isObjectType(schemaType)) {\n return createObject(schemaType)\n }\n\n if (lastType(schemaType)?.name === 'document') {\n const doc = documentTypes.get(schemaType.name)\n if (doc === undefined) {\n return {type: 'unknown'} satisfies UnknownTypeNode\n }\n return {type: 'object', attributes: doc?.attributes} satisfies ObjectTypeNode\n }\n\n throw new Error(`Type \"${schemaType.name}\" not found`)\n }\n\n function createObject(\n schemaType: ObjectSchemaType | SanitySchemaType,\n ): ObjectTypeNode | UnknownTypeNode {\n const attributes: Record<string, ObjectAttribute> = {}\n\n const fields = gatherFields(schemaType)\n for (const field of fields) {\n const fieldIsRequired = isFieldRequired(field)\n const value = convertSchemaType(field.type)\n if (value === null) {\n continue\n }\n\n // if the field sets assetRequired() we will mark the asset attribute as required\n // also guard against the case where the field is not an object, though type validation should catch this\n if (hasAssetRequired(field) && value.type === 'object') {\n value.attributes.asset.optional = false\n }\n\n // if we extract with enforceRequiredFields, we will mark the field as optional only if it is not a required field,\n // else we will always mark it as optional\n const optional = extractOptions.enforceRequiredFields ? fieldIsRequired === false : true\n\n attributes[field.name] = {\n type: 'objectAttribute',\n value,\n optional,\n }\n }\n\n // Ignore empty objects\n if (Object.keys(attributes).length === 0) {\n return {type: 'unknown'} satisfies UnknownTypeNode\n }\n\n if (schemaType.type?.name !== 'document' && schemaType.name !== 'object') {\n attributes._type = {\n type: 'objectAttribute',\n value: {\n type: 'string',\n value: schemaType.name,\n },\n }\n }\n\n return {\n type: 'object',\n attributes,\n }\n }\n\n function createArray(arraySchemaType: ArraySchemaType): ArrayTypeNode | NullTypeNode {\n const of: TypeNode[] = []\n for (const item of arraySchemaType.of) {\n const field = convertSchemaType(item)\n if (field.type === 'inline') {\n of.push({\n type: 'object',\n attributes: {\n _key: createKeyField(),\n },\n rest: field,\n } satisfies ObjectTypeNode)\n } else if (field.type === 'object') {\n field.rest = {\n type: 'object',\n attributes: {\n _key: createKeyField(),\n },\n }\n of.push(field)\n } else {\n of.push(field)\n }\n }\n\n if (of.length === 0) {\n return {type: 'null'}\n }\n\n return {\n type: 'array',\n of:\n of.length > 1\n ? {\n type: 'union',\n of,\n }\n : of[0],\n }\n }\n\n return schema\n}\n\nfunction createKeyField(): ObjectAttribute<StringTypeNode> {\n return {\n type: 'objectAttribute',\n value: {\n type: 'string',\n },\n }\n}\n\nfunction isFieldRequired(field: ObjectField): boolean {\n const {validation} = field.type\n if (!validation) {\n return false\n }\n const rules = Array.isArray(validation) ? validation : [validation]\n for (const rule of rules) {\n let required = false\n\n // hack to check if a field is required. We create a proxy that returns itself when a method is called,\n // if the method is \"required\" we set a flag\n const proxy = new Proxy(\n {},\n {\n get: (target, methodName) => () => {\n if (methodName === 'required') {\n required = true\n }\n return proxy\n },\n },\n ) as Rule\n\n if (typeof rule === 'function') {\n rule(proxy)\n if (required) {\n return true\n }\n }\n\n if (typeof rule === 'object' && rule !== null && '_required' in rule) {\n if (rule._required === 'required') {\n return true\n }\n }\n }\n\n return false\n}\n\nfunction hasAssetRequired(field: ObjectField): boolean {\n const {validation} = field.type\n if (!validation) {\n return false\n }\n const rules = Array.isArray(validation) ? validation : [validation]\n for (const rule of rules) {\n let assetRequired = false\n\n // hack to check if a field is required. We create a proxy that returns itself when a method is called,\n // if the method is \"required\" we set a flag\n const proxy = new Proxy(\n {},\n {\n get: (target, methodName) => () => {\n if (methodName === 'assetRequired') {\n assetRequired = true\n }\n return proxy\n },\n },\n ) as Rule\n\n if (typeof rule === 'function') {\n rule(proxy)\n if (assetRequired) {\n return true\n }\n }\n\n if (\n typeof rule === 'object' &&\n rule !== null &&\n '_rules' in rule &&\n Array.isArray(rule._rules)\n ) {\n if (rule._rules.some((r) => r.flag === 'assetRequired')) {\n return true\n }\n }\n }\n\n return false\n}\n\nfunction isObjectType(typeDef: SanitySchemaType): typeDef is ObjectSchemaType {\n return isType(typeDef, 'object') || typeDef.jsonType === 'object' || 'fields' in typeDef\n}\nfunction isArrayType(typeDef: SanitySchemaType): typeDef is ArraySchemaType {\n return isType(typeDef, 'array')\n}\nfunction isReferenceType(typeDef: SanitySchemaType): typeDef is ReferenceSchemaType {\n return isType(typeDef, 'reference')\n}\nfunction isCrossDatasetReferenceType(typeDef: SanitySchemaType) {\n return isType(typeDef, 'crossDatasetReference')\n}\nfunction isGlobalDocumentReferenceType(typeDef: SanitySchemaType) {\n return isType(typeDef, 'globalDocumentReference')\n}\nfunction isStringType(typeDef: SanitySchemaType): typeDef is StringSchemaType {\n return isType(typeDef, 'string')\n}\nfunction isNumberType(typeDef: SanitySchemaType): typeDef is NumberSchemaType {\n return isType(typeDef, 'number')\n}\nfunction createStringTypeNodeDefintion(\n stringSchemaType: StringSchemaType,\n): StringTypeNode | UnionTypeNode<StringTypeNode> {\n const listOptions = stringSchemaType.options?.list\n if (listOptions && Array.isArray(listOptions)) {\n return {\n type: 'union',\n of: listOptions.map((v) => ({\n type: 'string',\n value: typeof v === 'string' ? v : v.value,\n })),\n }\n }\n return {\n type: 'string',\n }\n}\n\nfunction createNumberTypeNodeDefintion(\n numberSchemaType: NumberSchemaType,\n): NumberTypeNode | UnionTypeNode<NumberTypeNode> {\n const listOptions = numberSchemaType.options?.list\n if (listOptions && Array.isArray(listOptions)) {\n return {\n type: 'union',\n of: listOptions.map((v) => ({\n type: 'number',\n value: typeof v === 'number' ? v : v.value,\n })),\n }\n }\n return {\n type: 'number',\n }\n}\n\nfunction createReferenceTypeNodeDefintion(\n reference: ReferenceSchemaType,\n): ObjectTypeNode | UnionTypeNode<ObjectTypeNode> {\n const references = gatherReferenceNames(reference)\n if (references.length === 1) {\n return createReferenceTypeNode(references[0])\n }\n\n return {\n type: 'union',\n of: references.map((name) => createReferenceTypeNode(name)),\n }\n}\n\n// Traverse the reference type tree and gather all the reference names\nfunction gatherReferenceNames(type: ReferenceSchemaType): string[] {\n const allReferences = gatherReferenceTypes(type)\n // Remove duplicates\n return [...new Set(allReferences.map((ref) => ref.name))]\n}\n\nfunction gatherReferenceTypes(type: ReferenceSchemaType): ObjectSchemaType[] {\n const refTo = 'to' in type ? type.to : []\n if ('type' in type && isReferenceType(type.type!)) {\n return [...gatherReferenceTypes(type.type), ...refTo]\n }\n\n return refTo\n}\n\n// Traverse the type tree and gather all the fields\nfunction gatherFields(type: SanitySchemaType | ObjectSchemaType): ObjectField[] {\n if ('fields' in type) {\n return type.type ? gatherFields(type.type).concat(type.fields) : type.fields\n }\n\n return []\n}\n\n// Traverse the type tree and check if the type or any of its subtypes are of the given type\nfunction isType(\n typeDef: SanitySchemaType | ObjectField | ObjectFieldType,\n typeName: string,\n): boolean {\n let type: SchemaType | ObjectField | ObjectFieldType | undefined = typeDef\n while (type) {\n if (type.name === typeName || (type.type && type.type.name === typeName)) {\n return true\n }\n\n type = type.type\n }\n return false\n}\n\n// Traverse the type tree and return the \"last\" type, ie deepest type in the tree\nfunction lastType(typeDef: SanitySchemaType): SanitySchemaType | undefined {\n let type: SchemaType | ObjectField | ObjectFieldType | undefined = typeDef\n while (type) {\n if (!type.type) {\n return type\n }\n type = type.type\n }\n\n return undefined\n}\n\n// Sorts the types by their dependencies by using a topological sort depth-first algorithm.\nfunction sortByDependencies(compiledSchema: SchemaDef): string[] {\n const seen = new Set<SanitySchemaType>()\n\n // Walks the dependencies of a schema type and adds them to the dependencies set\n function walkDependencies(\n schemaType: SanitySchemaType,\n dependencies: Set<SanitySchemaType>,\n ): void {\n if (seen.has(schemaType)) {\n return\n }\n seen.add(schemaType)\n\n if ('fields' in schemaType) {\n for (const field of gatherFields(schemaType)) {\n const last = lastType(field.type)\n if (last!.name === 'document') {\n dependencies.add(last!)\n continue\n }\n\n let schemaTypeName: string | undefined\n if (schemaType.type!.type) {\n schemaTypeName = field.type.type!.name\n } else if ('jsonType' in schemaType.type!) {\n schemaTypeName = field.type.jsonType\n }\n\n if (schemaTypeName === 'object' || schemaTypeName === 'block') {\n if (isReferenceType(field.type)) {\n field.type.to.forEach((ref) => dependencies.add(ref.type!))\n } else {\n dependencies.add(field.type)\n }\n }\n walkDependencies(field.type, dependencies)\n }\n } else if ('of' in schemaType) {\n for (const item of schemaType.of) {\n walkDependencies(item, dependencies)\n }\n }\n }\n const dependencyMap = new Map<SanitySchemaType, Set<SanitySchemaType>>()\n compiledSchema.getTypeNames().forEach((typeName) => {\n const schemaType = compiledSchema.get(typeName)\n if (schemaType === undefined || schemaType.type === null) {\n return\n }\n const dependencies = new Set<SanitySchemaType>()\n\n walkDependencies(schemaType, dependencies)\n dependencyMap.set(schemaType, dependencies)\n seen.clear() // Clear the seen set for the next type\n })\n\n // Sorts the types by their dependencies\n const typeNames: string[] = []\n // holds a temporary mark for types that are currently being visited, to detect cyclic dependencies\n const currentlyVisiting = new Set<SanitySchemaType>()\n\n // holds a permanent mark for types that have been already visited\n const visited = new Set<SanitySchemaType>()\n\n // visit implements a depth-first search\n function visit(type: SanitySchemaType) {\n if (visited.has(type)) {\n return\n }\n // If we find a type that is already in the temporary mark, we have a cyclic dependency.\n if (currentlyVisiting.has(type)) {\n return\n }\n // mark this as a temporary mark, meaning it's being visited\n currentlyVisiting.add(type)\n const deps = dependencyMap.get(type)\n if (deps !== undefined) {\n deps.forEach((dep) => visit(dep))\n }\n currentlyVisiting.delete(type)\n visited.add(type)\n\n if (!typeNames.includes(type.name)) {\n typeNames.unshift(type.name)\n }\n }\n // Visit all types in the dependency map\n for (const [type] of dependencyMap) {\n visit(type)\n }\n\n return typeNames\n}\n","import {type SchemaValidationResult} from '../typedefs'\n\n// Temporary solution to ensure we have a central registry over used helpIds\nexport const HELP_IDS = {\n TYPE_INVALID: 'schema-type-invalid',\n TYPE_IS_ESM_MODULE: 'schema-type-is-esm-module',\n TYPE_NAME_RESERVED: 'schema-type-name-reserved',\n TYPE_MISSING_NAME: 'schema-type-missing-name-or-type',\n TYPE_MISSING_TYPE: 'schema-type-missing-name-or-type',\n TYPE_TITLE_RECOMMENDED: 'schema-type-title-is-recommended',\n TYPE_TITLE_INVALID: 'schema-type-title-is-recommended',\n OBJECT_FIELDS_INVALID: 'schema-object-fields-invalid',\n OBJECT_FIELD_NOT_UNIQUE: 'schema-object-fields-invalid',\n OBJECT_FIELD_NAME_INVALID: 'schema-object-fields-invalid',\n OBJECT_FIELD_DEFINITION_INVALID_TYPE: 'schema-object-fields-invalid',\n ARRAY_PREDEFINED_CHOICES_INVALID: 'schema-predefined-choices-invalid',\n ARRAY_OF_ARRAY: 'schema-array-of-array',\n ARRAY_OF_INVALID: 'schema-array-of-invalid',\n ARRAY_OF_NOT_UNIQUE: 'schema-array-of-invalid',\n ARRAY_OF_TYPE_GLOBAL_TYPE_CONFLICT: 'schema-array-of-type-global-type-conflict',\n ARRAY_OF_TYPE_BUILTIN_TYPE_CONFLICT: 'schema-array-of-type-builtin-type-conflict',\n REFERENCE_TO_INVALID: 'schema-reference-to-invalid',\n REFERENCE_TO_NOT_UNIQUE: 'schema-reference-to-invalid',\n REFERENCE_INVALID_OPTIONS: 'schema-reference-invalid-options',\n REFERENCE_INVALID_OPTIONS_LOCATION: 'schema-reference-options-nesting',\n REFERENCE_INVALID_FILTER_PARAMS_COMBINATION: 'schema-reference-filter-params-combination',\n SLUG_SLUGIFY_FN_RENAMED: 'slug-slugifyfn-renamed',\n ASSET_METADATA_FIELD_INVALID: 'asset-metadata-field-invalid',\n CROSS_DATASET_REFERENCE_INVALID: 'cross-dataset-reference-invalid',\n GLOBAL_DOCUMENT_REFERENCE_INVALID: 'global-document-reference-invalid',\n DEPRECATED_BLOCKEDITOR_KEY: 'schema-deprecated-blockeditor-key',\n STANDALONE_BLOCK_TYPE: 'schema-standalone-block-type',\n}\n\nfunction createValidationResult(\n severity: SchemaValidationResult['severity'],\n message: string,\n helpId: string | null,\n): SchemaValidationResult {\n if (helpId && !Object.keys(HELP_IDS).some((id) => (HELP_IDS as any)[id] === helpId)) {\n throw new Error(\n `Used the unknown helpId \"${helpId}\", please add it to the array in createValidationResult.js`,\n )\n }\n return {\n severity,\n message,\n helpId: helpId!,\n }\n}\n\nexport const error = (message: string, helpId?: string | null): SchemaValidationResult =>\n createValidationResult('error', message, helpId!)\n\nexport const warning = (message: string, helpId?: string | null): SchemaValidationResult =>\n createValidationResult('warning', message, helpId!)\n","import {\n type SchemaType,\n type SchemaTypeDefinition,\n type SchemaValidationProblemGroup,\n} from '@sanity/types'\nimport {flatten, get} from 'lodash'\n\nimport {type ProblemPath, type ProblemPathPropertySegment, type TypeWithProblems} from './typedefs'\nimport {error} from './validation/createValidationResult'\n\n/**\n * @internal\n */\nexport function groupProblems(types: SchemaTypeDefinition[]): SchemaValidationProblemGroup[] {\n return flatten<TypeWithProblems>(types.map((type) => getTypeProblems(type))).filter(\n (type) => type.problems.length > 0,\n )\n}\n\nfunction createTypeWithMembersProblemsAccessor(\n memberPropertyName: string,\n getMembers = (type: SchemaType) => get(type, memberPropertyName),\n) {\n return function getProblems(type: any, parentPath: ProblemPath): TypeWithProblems[] {\n const currentPath: ProblemPath = [\n ...parentPath,\n {kind: 'type', type: type.type, name: type.name},\n ]\n\n const members = getMembers(type) || []\n\n const memberProblems: TypeWithProblems[][] = Array.isArray(members)\n ? members.map((memberType) => {\n const propertySegment: ProblemPathPropertySegment = {\n kind: 'property',\n name: memberPropertyName,\n }\n const memberPath: ProblemPath = [...currentPath, propertySegment]\n return getTypeProblems(memberType, memberPath as any)\n })\n : [\n [\n {\n path: currentPath,\n problems: [error(`Member declaration (${memberPropertyName}) is not an array`)],\n },\n ],\n ]\n\n return [\n {\n path: currentPath,\n problems: type._problems || [],\n },\n ...flatten(memberProblems),\n ]\n }\n}\n\nconst arrify = (val: any) =>\n Array.isArray(val) ? val : (typeof val === 'undefined' && []) || [val]\n\nconst getObjectProblems = createTypeWithMembersProblemsAccessor('fields')\nconst getImageProblems = createTypeWithMembersProblemsAccessor('fields')\nconst getFileProblems = createTypeWithMembersProblemsAccessor('fields')\nconst getArrayProblems = createTypeWithMembersProblemsAccessor('of')\nconst getReferenceProblems = createTypeWithMembersProblemsAccessor('to', (type) =>\n 'to' in type ? arrify(type.to) : [],\n)\nconst getBlockAnnotationProblems = createTypeWithMembersProblemsAccessor('marks.annotations')\nconst getBlockMemberProblems = createTypeWithMembersProblemsAccessor('of')\nconst getBlockProblems = (type: any, problems: any) => [\n ...getBlockAnnotationProblems(type, problems),\n ...getBlockMemberProblems(type, problems),\n]\n\nfunction getDefaultProblems(type: any, path = []): TypeWithProblems[] {\n return [\n {\n path: [...path, {kind: 'type', type: type.type, name: type.name}],\n problems: type._problems || [],\n },\n ]\n}\n\nfunction getTypeProblems(type: SchemaTypeDefinition, path = []): TypeWithProblems[] {\n switch (type.type) {\n case 'object': {\n return getObjectProblems(type, path)\n }\n case 'document': {\n return getObjectProblems(type, path)\n }\n case 'array': {\n return getArrayProblems(type, path)\n }\n case 'reference': {\n return getReferenceProblems(type, path)\n }\n case 'block': {\n return getBlockProblems(type, path)\n }\n case 'image': {\n return getImageProblems(type, path)\n }\n case 'file': {\n return getFileProblems(type, path)\n }\n default: {\n return getDefaultProblems(type, path)\n }\n }\n}\n","export function getDupes(array: any, selector = (v: any) => v) {\n const dupes = array.reduce((acc: any, item: any) => {\n const key = selector(item)\n if (!acc[key]) {\n acc[key] = []\n }\n acc[key].push(item)\n return acc\n }, {})\n\n return Object.keys(dupes)\n .map((key) => (dupes[key].length > 1 ? dupes[key] : null))\n .filter(Boolean)\n}\n","import {flatten, uniq} from 'lodash'\n\nimport {getDupes} from '../sanity/validation/utils/getDupes'\n\ntype SchemaType = Record<string, any>\ntype SchemaTypeDef = Record<string, any>\n\ntype VisitContext = {\n isRoot: boolean\n isReserved: (typeName: string) => boolean\n visit: Visitor\n index: number\n isDuplicate: (typeName: string) => boolean\n getType: (typeName: string) => null | SchemaType\n getTypeNames: () => Array<string>\n}\n\nexport type Visitor = (typeDef: SchemaTypeDef, arg1: VisitContext) => SchemaType\n\nconst NOOP_VISITOR: Visitor = (typeDef) => typeDef\n\nexport class UnknownType {\n name: string\n\n constructor(name: string) {\n this.name = name\n }\n}\n\nconst TYPE_TYPE = {name: 'type', type: null}\n\nconst FUTURE_RESERVED = ['any', 'time', 'date']\n\nexport function traverseSchema(\n types: SchemaTypeDef[] = [],\n coreTypes: SchemaTypeDef[] = [],\n visitor