UNPKG

@sanity/assist

Version:

You create the instructions; Sanity AI Assist does the rest.

225 lines (209 loc) 6.87 kB
import { ArraySchemaType, ImageOptions, isArraySchemaType, ObjectSchemaType, ReferenceOptions, ReferenceSchemaType, Schema, SchemaType, typed, } from 'sanity' import {isAssistSupported} from '../../helpers/assistSupported' import {isType} from '../../helpers/typeUtils' import { assistSchemaIdPrefix, assistSerializedFieldTypeName, assistSerializedTypeName, SerializedSchemaMember, SerializedSchemaType, } from '../../types' import {hiddenTypes} from './schemaUtils' interface Options { leanFormat?: boolean } const inlineTypes = ['document', 'object', 'image', 'file'] export function serializeSchema(schema: Schema, options?: Options): SerializedSchemaType[] { const list = schema .getTypeNames() .filter((t) => !(hiddenTypes.includes(t) || t.startsWith('sanity.'))) .map((t) => schema.get(t)) .filter((t): t is SchemaType => !!t) // because a field can override exclude at the type level, we have to also serialize excluded types // so don't do this: .filter((t) => isAssistSupported(t)) .map((t) => getSchemaStub(t, schema, options)) .filter((t) => { if ('to' in t && t.to && !t.to.length) { return false } if ('of' in t && t.of && !t.of.length) { return false } if ('fields' in t && t.fields && !t.fields.length) { return false } return true }) list.sort((a, b) => (a?.name ?? '').localeCompare(b?.name ?? '')) return list } function getSchemaStub( schemaType: SchemaType, schema: Schema, options?: Options, ): SerializedSchemaType { if (!schemaType.type?.name) { // eslint-disable-next-line no-console -- log error console.error('Missing type name', schemaType.type) throw new Error('Type is missing name!') } const baseSchema: SerializedSchemaType = { // we dont need type or id when we send using POST, so leave these out to save bandwidth ...(options?.leanFormat ? {} : {_id: `${assistSchemaIdPrefix}${schemaType.name}`, _type: assistSerializedTypeName}), name: schemaType.name, title: schemaType.title, type: schemaType.type.name, ...getBaseFields(schema, schemaType, schemaType.type.name, options), } return removeUndef(baseSchema) } function getBaseFields( schema: Schema, type: SchemaType, typeName: string, options: Options | undefined, ) { const schemaOptions: SerializedSchemaType['options'] = removeUndef({ imagePromptField: (type.options as ImageOptions)?.aiAssist?.imageInstructionField, embeddingsIndex: (type.options as ReferenceOptions)?.aiAssist?.embeddingsIndex, }) return removeUndef({ options: Object.keys(schemaOptions).length ? schemaOptions : undefined, values: Array.isArray(type?.options?.list) ? type?.options?.list.map((v: string | {value: string; title: string}) => typeof v === 'string' ? v : (v.value ?? `${v.title}`), ) : undefined, of: 'of' in type && typeName === 'array' ? arrayOf(type, schema, options) : undefined, to: 'to' in type && typeName === 'reference' ? refToTypeNames(type as ReferenceSchemaType) : undefined, fields: 'fields' in type && inlineTypes.includes(typeName) ? serializeFields(schema, type, options) : undefined, annotations: typeName === 'block' && 'fields' in type ? serializeAnnotations(type, schema, options) : undefined, inlineOf: typeName === 'block' && 'fields' in type ? serializeInlineOf(type, schema, options) : undefined, hidden: typeof type.hidden === 'function' ? ('function' as const) : type.hidden ? true : undefined, readOnly: typeof type.readOnly === 'function' ? ('function' as const) : type.readOnly ? true : undefined, }) } function serializeFields( schema: Schema, schemaType: ObjectSchemaType, options: Options | undefined, ) { const fields = schemaType.fieldsets ? schemaType.fieldsets.flatMap((fs) => fs.single ? fs.field : fs.fields.map((f) => ({ ...f, type: { ...f.type, // if fieldset is (conditionally) hidden, the field must be considered the same way // ie, if the field does not show up in conditionalMembers, it is hidden // regardless of weather or not it is the field function or the fieldset function that hides it hidden: typeof fs.hidden === 'function' ? fs.hidden : fs.hidden ? true : f.type.hidden, }, })), ) : schemaType.fields return fields .filter((f) => !['sanity.imageHotspot', 'sanity.imageCrop'].includes(f.type?.name ?? '')) .filter((f) => isAssistSupported(f.type)) .map((field) => serializeMember(schema, field.type, field.name, options)) } function serializeMember( schema: Schema, type: SchemaType, name: string, options: Options | undefined, ): SerializedSchemaMember { const typeNameExists = !!schema.get(type?.name) const typeName = typeNameExists ? type.name : (type.type?.name ?? '') return removeUndef({ ...(options?.leanFormat ? {} : {_type: assistSerializedFieldTypeName}), name: name, type: typeName, title: type.title, ...getBaseFields(schema, type, typeName, options), }) } function serializeInlineOf( blockSchemaType: ObjectSchemaType, schema: Schema, options: Options | undefined, ): SerializedSchemaMember[] | undefined { const childrenField = blockSchemaType.fields.find((f) => f.name === 'children') const childrenType = childrenField?.type if (!childrenType || !isArraySchemaType(childrenType)) { return undefined } return arrayOf( { ...childrenType, of: childrenType.of.filter((t) => !isType(t, 'span')), }, schema, options, ) } function serializeAnnotations( blockSchemaType: ObjectSchemaType, schema: Schema, options: Options | undefined, ): SerializedSchemaMember[] | undefined { const markDefs = blockSchemaType.fields.find((f) => f.name === 'markDefs') const marksType = markDefs?.type if (!marksType || !isArraySchemaType(marksType)) { return undefined } return arrayOf(marksType, schema, options) } function arrayOf( arrayType: ArraySchemaType, schema: Schema, options: Options | undefined, ): SerializedSchemaMember[] { return arrayType.of .filter((type) => isAssistSupported(type)) .map((t) => { return serializeMember(schema, t, t.name, options) }) } function refToTypeNames(type: ReferenceSchemaType) { return type.to.map((t) => ({ type: typed<string>(t.name), })) } function removeUndef<T extends Record<string, any>>(obj: T): T { Object.keys(obj).forEach((key) => (obj[key] === undefined ? delete obj[key] : {})) return obj }