@sanity/schema
Version:
## Terminology
1,438 lines • 83.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: !0 });
var descriptors = require("@sanity/descriptors"), resolve = require("./_chunks-cjs/resolve.js"), difference = require("lodash/difference.js"), groqJs = require("groq-js"), flatten = require("lodash/flatten.js"), get = require("lodash/get.js"), uniq = require("lodash/uniq.js"), humanizeList = require("humanize-list"), partition = require("lodash/partition.js"), isPlainObject = require("lodash/isPlainObject.js"), omit = require("lodash/omit.js"), leven = require("leven"), inspect = require("object-inspect");
function _interopDefaultCompat(e) {
return e && typeof e == "object" && "default" in e ? e : { default: e };
}
var difference__default = /* @__PURE__ */ _interopDefaultCompat(difference), flatten__default = /* @__PURE__ */ _interopDefaultCompat(flatten), get__default = /* @__PURE__ */ _interopDefaultCompat(get), uniq__default = /* @__PURE__ */ _interopDefaultCompat(uniq), humanizeList__default = /* @__PURE__ */ _interopDefaultCompat(humanizeList), partition__default = /* @__PURE__ */ _interopDefaultCompat(partition), isPlainObject__default = /* @__PURE__ */ _interopDefaultCompat(isPlainObject), omit__default = /* @__PURE__ */ _interopDefaultCompat(omit), leven__default = /* @__PURE__ */ _interopDefaultCompat(leven), inspect__default = /* @__PURE__ */ _interopDefaultCompat(inspect);
const MAX_DEPTH_UKNOWN = 5;
class DescriptorConverter {
opts;
cache = /* @__PURE__ */ new WeakMap();
constructor(opts) {
this.opts = opts;
}
/**
* Returns a synchronization object for a schema.
*
* This is automatically cached in a weak map.
*/
get(schema) {
let value = this.cache.get(schema);
if (value) return value;
const builder = new descriptors.SetBuilder();
for (const name of schema.getLocalTypeNames()) {
const typeDef = convertTypeDef(schema.get(name));
builder.addObject("sanity.schema.namedType", { name, typeDef });
}
return schema.parent && builder.addSet(this.get(schema.parent)), value = builder.build("sanity.schema.registry"), this.cache.set(schema, value), value;
}
}
function convertCommonTypeDef(schemaType, opts) {
const ownProps = resolve.OWN_PROPS_NAME in schemaType ? schemaType[resolve.OWN_PROPS_NAME] : schemaType;
let fields;
Array.isArray(ownProps.fields) && (fields = ownProps.fields.map(
({ name, group, fieldset, type }) => ({
name,
typeDef: convertTypeDef(type),
groups: arrayifyString(group),
fieldset
})
));
let fieldsets;
Array.isArray(ownProps.fieldsets) && (fieldsets = filterStringKey(
"name",
ownProps.fieldsets.map(
({ name, title, description, group, hidden, readOnly, options }) => ({
name,
title: maybeString(title),
description: maybeString(description),
group: maybeString(group),
hidden: conditionalTrue(hidden),
readOnly: conditionalTrue(readOnly),
options: convertUnknown(options)
})
)
));
let groups;
Array.isArray(ownProps.groups) && (groups = filterStringKey(
"name",
ownProps.groups.map(
({ name, title, hidden, default: def }) => ({
name,
title: maybeString(title),
hidden: conditionalTrue(hidden),
default: maybeTrue(def)
})
)
));
const reason = ownProps.deprecated?.reason;
return {
title: maybeString(ownProps.title),
description: maybeStringOrJSX(ownProps.description),
readOnly: conditionalTrue(ownProps.readOnly),
hidden: conditionalTrue(ownProps.hidden),
liveEdit: maybeTrue(ownProps.liveEdit),
options: convertUnknown(ownProps.options),
initialValue: convertUnknown(ownProps.initialValue),
deprecated: typeof reason == "string" ? { reason } : void 0,
placeholder: maybeString(ownProps.placeholder),
rows: maybeNumberAsString(ownProps.rows),
fields,
fieldsets,
groups
};
}
function convertTypeDef(schemaType, opts) {
const common2 = convertCommonTypeDef(schemaType);
if (!schemaType.type)
return {
extends: null,
jsonType: schemaType.jsonType,
...common2
};
switch (schemaType.type.name) {
case "array":
return {
extends: "array",
of: schemaType.of.map((ofType) => ({
name: ofType.name,
typeDef: convertTypeDef(ofType)
})),
...common2
};
case "reference":
case "globalDocumentReference":
case "crossDatasetReference":
return {
extends: schemaType.type.name,
to: filterStringKey(
"name",
schemaType.to.map((toType) => ({ name: toType.name || toType.type?.name || toType.type }))
),
...common2
};
default:
return { extends: schemaType.type.name, ...common2 };
}
}
function maybeString(val) {
return typeof val == "string" ? val : void 0;
}
function maybeNumberAsString(val) {
return typeof val == "number" ? val.toString() : void 0;
}
function maybeTrue(val) {
return val === !0 ? !0 : void 0;
}
function conditionalTrue(val) {
return typeof val == "function" ? FUNCTION_MARKER : maybeTrue(val);
}
function filterStringKey(key, arr) {
return arr.filter((obj) => typeof obj[key] == "string");
}
function arrayifyString(val) {
if (typeof val == "string")
return [val];
if (Array.isArray(val))
return val.filter((elem) => typeof elem == "string");
}
const FUNCTION_MARKER = { __type: "function" }, UNKNOWN_MARKER = { __type: "unknown" }, UNDEFINED_MARKER = { __type: "undefined" }, CYCLIC_MARKER = { __type: "cyclic" }, MAX_DEPTH_MARKER = { __type: "maxDepth" };
function convertUnknown(val, seen = /* @__PURE__ */ new Set(), maxDepth = MAX_DEPTH_UKNOWN) {
if (maxDepth === 0) return MAX_DEPTH_MARKER;
if (typeof val == "string" || typeof val == "boolean" || val === null || val === void 0)
return val;
if (typeof val == "number")
return { __type: "number", value: val.toString() };
if (typeof val == "function") return FUNCTION_MARKER;
if (seen.has(val))
return CYCLIC_MARKER;
if (seen.add(val), typeof val == "object") {
if (Array.isArray(val))
return val.map((elem) => {
const res = convertUnknown(elem, seen, maxDepth - 1);
return res === void 0 ? UNDEFINED_MARKER : res;
});
if ("$$typeof" in val && "type" in val && "props" in val) {
const { type, props } = val, strType = typeof type == "function" ? type.name : type;
return typeof strType != "string" ? void 0 : {
__type: "jsx",
type: strType,
props: convertUnknown(props, seen, maxDepth - 1)
};
}
let hasType = !1;
const result = {};
for (const [key, field] of Object.entries(val))
key === "__type" && (hasType = !0), result[key] = convertUnknown(field, seen, maxDepth - 1);
return hasType ? { __type: "object", value: result } : result;
}
return UNKNOWN_MARKER;
}
function maybeStringOrJSX(val) {
if (typeof val == "string") return val;
if (val && typeof val == "object" && "$$typeof" in val && "type" in val && "props" in val) {
const { type, props } = val, strType = typeof type == "function" ? type.name : type;
return typeof strType != "string" ? void 0 : { __type: "jsx", type: strType, props: convertUnknown(props) };
}
}
function processSchemaSynchronization(sync, response) {
return descriptors.processSetSynchronization(sync, response);
}
const ACTIONS_FLAG = "__experimental_actions", DEFAULT_ACTIONS = ["create", "update", "delete", "publish"], VALID_ACTIONS = DEFAULT_ACTIONS, readActions = (schemaType) => ACTIONS_FLAG in schemaType ? schemaType[ACTIONS_FLAG] : DEFAULT_ACTIONS, validateActions = (typeName, actions) => {
if (!Array.isArray(actions))
throw new Error(
`The value of <type>.${ACTIONS_FLAG} should be an array with any of the actions ${VALID_ACTIONS.join(
", "
)}`
);
const invalid = difference__default.default(actions, VALID_ACTIONS);
if (invalid.length > 0)
throw new Error(
`Invalid action${invalid.length > 1 ? "s" : ""} configured for schema type "${typeName}": ${invalid.join(
", "
)}. Valid actions are: ${VALID_ACTIONS.join(", ")}`
);
return actions;
}, resolveEnabledActions = (schemaType) => validateActions(schemaType.name, readActions(schemaType)), isActionEnabled = (schemaType, action) => resolveEnabledActions(schemaType).includes(action);
var assetSourceData = {
name: "sanity.assetSourceData",
title: "Asset Source Data",
type: "object",
fields: [
{
name: "name",
title: "Source name",
description: "A canonical name for the source this asset is originating from",
type: "string"
},
{
name: "id",
title: "Asset Source ID",
description: "The unique ID for the asset within the originating source so you can programatically find back to it",
type: "string"
},
{
name: "url",
title: "Asset information URL",
description: "A URL to find more information about this asset in the originating source",
type: "string"
}
]
}, fileAsset = {
name: "sanity.fileAsset",
title: "File",
type: "document",
fieldsets: [
{
name: "system",
title: "System fields",
description: "These fields are managed by the system and not editable"
}
],
fields: [
{
name: "originalFilename",
type: "string",
title: "Original file name",
readOnly: !0
},
{
name: "label",
type: "string",
title: "Label"
},
{
name: "title",
type: "string",
title: "Title"
},
{
name: "description",
type: "string",
title: "Description"
},
{
name: "altText",
type: "string",
title: "Alternative text"
},
{
name: "sha1hash",
type: "string",
title: "SHA1 hash",
readOnly: !0,
fieldset: "system"
},
{
name: "extension",
type: "string",
title: "File extension",
readOnly: !0,
fieldset: "system"
},
{
name: "mimeType",
type: "string",
title: "Mime type",
readOnly: !0,
fieldset: "system"
},
{
name: "size",
type: "number",
title: "File size in bytes",
readOnly: !0,
fieldset: "system"
},
{
name: "assetId",
type: "string",
title: "Asset ID",
readOnly: !0,
fieldset: "system"
},
{
name: "uploadId",
type: "string",
readOnly: !0,
hidden: !0,
fieldset: "system"
},
{
name: "path",
type: "string",
title: "Path",
readOnly: !0,
fieldset: "system"
},
{
name: "url",
type: "string",
title: "Url",
readOnly: !0,
fieldset: "system"
},
{
name: "source",
type: "sanity.assetSourceData",
title: "Source",
readOnly: !0,
fieldset: "system"
}
],
preview: {
select: {
title: "originalFilename",
path: "path",
mimeType: "mimeType",
size: "size"
},
prepare(doc) {
return {
title: doc.title || doc.path.split("/").slice(-1)[0],
subtitle: `${doc.mimeType} (${(doc.size / 1024 / 1024).toFixed(2)} MB)`
};
}
},
orderings: [
{
title: "File size",
name: "fileSizeDesc",
by: [{ field: "size", direction: "desc" }]
}
]
}, geopoint = {
title: "Geographical Point",
name: "geopoint",
type: "object",
fields: [
{
name: "lat",
type: "number",
title: "Latitude"
},
{
name: "lng",
type: "number",
title: "Longitude"
},
{
name: "alt",
type: "number",
title: "Altitude"
}
]
}, imageAsset = {
name: "sanity.imageAsset",
title: "Image",
type: "document",
fieldsets: [
{
name: "system",
title: "System fields",
description: "These fields are managed by the system and not editable"
}
],
fields: [
{
name: "originalFilename",
type: "string",
title: "Original file name",
readOnly: !0
},
{
name: "label",
type: "string",
title: "Label"
},
{
name: "title",
type: "string",
title: "Title"
},
{
name: "description",
type: "string",
title: "Description"
},
{
name: "altText",
type: "string",
title: "Alternative text"
},
{
name: "sha1hash",
type: "string",
title: "SHA1 hash",
readOnly: !0,
fieldset: "system"
},
{
name: "extension",
type: "string",
readOnly: !0,
title: "File extension",
fieldset: "system"
},
{
name: "mimeType",
type: "string",
readOnly: !0,
title: "Mime type",
fieldset: "system"
},
{
name: "size",
type: "number",
title: "File size in bytes",
readOnly: !0,
fieldset: "system"
},
{
name: "assetId",
type: "string",
title: "Asset ID",
readOnly: !0,
fieldset: "system"
},
{
name: "uploadId",
type: "string",
readOnly: !0,
hidden: !0,
fieldset: "system"
},
{
name: "path",
type: "string",
title: "Path",
readOnly: !0,
fieldset: "system"
},
{
name: "url",
type: "string",
title: "Url",
readOnly: !0,
fieldset: "system"
},
{
name: "metadata",
type: "sanity.imageMetadata",
title: "Metadata"
},
{
name: "source",
type: "sanity.assetSourceData",
title: "Source",
readOnly: !0,
fieldset: "system"
}
],
preview: {
select: {
id: "_id",
title: "originalFilename",
mimeType: "mimeType",
size: "size"
},
prepare(doc) {
return {
title: doc.title || typeof doc.path == "string" && doc.path.split("/").slice(-1)[0],
media: { asset: { _ref: doc.id } },
subtitle: `${doc.mimeType} (${(Number(doc.size) / 1024 / 1024).toFixed(2)} MB)`
};
}
},
orderings: [
{
title: "File size",
name: "fileSizeDesc",
by: [{ field: "size", direction: "desc" }]
}
]
}, imageCrop = {
name: "sanity.imageCrop",
title: "Image crop",
type: "object",
fields: [
{
name: "top",
type: "number"
},
{
name: "bottom",
type: "number"
},
{
name: "left",
type: "number"
},
{
name: "right",
type: "number"
}
]
}, imageDimensions = {
name: "sanity.imageDimensions",
type: "object",
title: "Image dimensions",
fields: [
{ name: "height", type: "number", title: "Height", readOnly: !0 },
{ name: "width", type: "number", title: "Width", readOnly: !0 },
{ name: "aspectRatio", type: "number", title: "Aspect ratio", readOnly: !0 }
]
}, imageHotspot = {
name: "sanity.imageHotspot",
title: "Image hotspot",
type: "object",
fields: [
{
name: "x",
type: "number"
},
{
name: "y",
type: "number"
},
{
name: "height",
type: "number"
},
{
name: "width",
type: "number"
}
]
}, imageMetadata = {
name: "sanity.imageMetadata",
title: "Image metadata",
type: "object",
fieldsets: [
{
name: "extra",
title: "Extra metadata\u2026",
options: {
collapsable: !0
}
}
],
fields: [
{
name: "location",
type: "geopoint"
},
{
name: "dimensions",
title: "Dimensions",
type: "sanity.imageDimensions",
fieldset: "extra"
},
{
name: "palette",
type: "sanity.imagePalette",
title: "Palette",
fieldset: "extra"
},
{
name: "lqip",
title: "LQIP (Low-Quality Image Placeholder)",
type: "string",
readOnly: !0
},
{
name: "blurHash",
title: "BlurHash",
type: "string",
readOnly: !0
},
{
name: "hasAlpha",
title: "Has alpha channel",
type: "boolean",
readOnly: !0
},
{
name: "isOpaque",
title: "Is opaque",
type: "boolean",
readOnly: !0
}
]
}, imagePalette = {
name: "sanity.imagePalette",
title: "Image palette",
type: "object",
fields: [
{ name: "darkMuted", type: "sanity.imagePaletteSwatch", title: "Dark Muted" },
{ name: "lightVibrant", type: "sanity.imagePaletteSwatch", title: "Light Vibrant" },
{ name: "darkVibrant", type: "sanity.imagePaletteSwatch", title: "Dark Vibrant" },
{ name: "vibrant", type: "sanity.imagePaletteSwatch", title: "Vibrant" },
{ name: "dominant", type: "sanity.imagePaletteSwatch", title: "Dominant" },
{ name: "lightMuted", type: "sanity.imagePaletteSwatch", title: "Light Muted" },
{ name: "muted", type: "sanity.imagePaletteSwatch", title: "Muted" }
]
}, imagePaletteSwatch = {
name: "sanity.imagePaletteSwatch",
title: "Image palette swatch",
type: "object",
fields: [
{ name: "background", type: "string", title: "Background", readOnly: !0 },
{ name: "foreground", type: "string", title: "Foreground", readOnly: !0 },
{ name: "population", type: "number", title: "Population", readOnly: !0 },
{ name: "title", type: "string", title: "String", readOnly: !0 }
]
}, slug$1 = {
title: "Slug",
name: "slug",
type: "object",
fields: [
{
name: "current",
title: "Current slug",
type: "string",
validation: (Rule) => Rule.required()
},
{
// The source field is deprecated/unused, but leaving it included and hidden
// to prevent rendering "Unknown field" warnings on legacy data
name: "source",
title: "Source field",
type: "string",
hidden: !0
}
]
};
const builtinTypes = [
assetSourceData,
slug$1,
geopoint,
// legacyRichDate,
imageAsset,
fileAsset,
imageCrop,
imageHotspot,
imageMetadata,
imageDimensions,
imagePalette,
imagePaletteSwatch
], documentDefaultFields = (typeName) => ({
_id: {
type: "objectAttribute",
value: { type: "string" }
},
_type: {
type: "objectAttribute",
value: { type: "string", value: typeName }
},
_createdAt: {
type: "objectAttribute",
value: { type: "string" }
},
_updatedAt: {
type: "objectAttribute",
value: { type: "string" }
},
_rev: {
type: "objectAttribute",
value: { type: "string" }
}
}), typesMap = /* @__PURE__ */ new Map([
["text", { type: "string" }],
["url", { type: "string" }],
["datetime", { type: "string" }],
["date", { type: "string" }],
["boolean", { type: "boolean" }],
["email", { type: "string" }]
]);
function extractSchema(schemaDef, extractOptions = {}) {
const inlineFields = /* @__PURE__ */ new Set(), documentTypes = /* @__PURE__ */ new Map(), schema = [];
sortByDependencies(schemaDef).forEach((typeName) => {
const schemaType = schemaDef.get(typeName);
if (schemaType === void 0)
return;
const base = convertBaseType(schemaType);
base !== null && (base.type === "type" && inlineFields.add(schemaType), base.type === "document" && documentTypes.set(typeName, base), schema.push(base));
});
function convertBaseType(schemaType) {
let typeName;
if (schemaType.type ? typeName = schemaType.type.name : "jsonType" in schemaType && (typeName = schemaType.jsonType), typeName === "document" && isObjectType(schemaType)) {
const defaultAttributes = documentDefaultFields(schemaType.name), object2 = createObject(schemaType);
return object2.type === "unknown" ? null : {
name: schemaType.name,
type: "document",
attributes: {
...defaultAttributes,
...object2.attributes
}
};
}
const value = convertSchemaType(schemaType);
return value.type === "unknown" ? null : value.type === "object" ? (value.attributes = {
_type: {
type: "objectAttribute",
value: {
type: "string",
value: schemaType.name
}
},
...value.attributes
}, {
name: schemaType.name,
type: "type",
value
}) : {
name: schemaType.name,
type: "type",
value
};
}
function convertSchemaType(schemaType) {
if (inlineFields.has(schemaType.type))
return { type: "inline", name: schemaType.type.name };
if (schemaType.type?.type?.name === "object")
return { type: "inline", name: schemaType.type.name };
if (isStringType(schemaType))
return createStringTypeNodeDefintion(schemaType);
if (isNumberType(schemaType))
return createNumberTypeNodeDefintion(schemaType);
if (schemaType.type && typesMap.has(schemaType.type.name))
return typesMap.get(schemaType.type.name);
if (isCrossDatasetReferenceType(schemaType))
return { type: "unknown" };
if (isGlobalDocumentReferenceType(schemaType))
return { type: "unknown" };
if (isReferenceType(schemaType))
return createReferenceTypeNodeDefintion(schemaType);
if (isArrayType(schemaType))
return createArray(schemaType);
if (isObjectType(schemaType))
return createObject(schemaType);
if (lastType(schemaType)?.name === "document") {
const doc = documentTypes.get(schemaType.name);
return doc === void 0 ? { type: "unknown" } : { type: "object", attributes: doc?.attributes };
}
throw new Error(`Type "${schemaType.name}" not found`);
}
function createObject(schemaType) {
const attributes = {}, fields = gatherFields(schemaType);
for (const field of fields) {
const fieldIsRequired = isFieldRequired(field), value = convertSchemaType(field.type);
if (value === null)
continue;
hasAssetRequired(field) && value.type === "object" && (value.attributes.asset.optional = !1);
const optional = extractOptions.enforceRequiredFields ? fieldIsRequired === !1 : !0;
attributes[field.name] = {
type: "objectAttribute",
value,
optional
};
}
return Object.keys(attributes).length === 0 ? { type: "unknown" } : (schemaType.type?.name !== "document" && schemaType.name !== "object" && (attributes._type = {
type: "objectAttribute",
value: {
type: "string",
value: schemaType.name
}
}), {
type: "object",
attributes
});
}
function createArray(arraySchemaType) {
const of = [];
for (const item of arraySchemaType.of) {
const field = convertSchemaType(item);
field.type === "inline" ? of.push({
type: "object",
attributes: {
_key: createKeyField()
},
rest: field
}) : (field.type === "object" && (field.rest = {
type: "object",
attributes: {
_key: createKeyField()
}
}), of.push(field));
}
return of.length === 0 ? { type: "null" } : {
type: "array",
of: of.length > 1 ? {
type: "union",
of
} : of[0]
};
}
return schema;
}
function createKeyField() {
return {
type: "objectAttribute",
value: {
type: "string"
}
};
}
function isFieldRequired(field) {
const { validation } = field.type;
if (!validation)
return !1;
const rules = Array.isArray(validation) ? validation : [validation];
for (const rule of rules) {
let required = !1;
const proxy = new Proxy(
{},
{
get: (target, methodName) => () => (methodName === "required" && (required = !0), proxy)
}
);
if (typeof rule == "function" && (rule(proxy), required) || typeof rule == "object" && rule !== null && "_required" in rule && rule._required === "required")
return !0;
}
return !1;
}
function hasAssetRequired(field) {
const { validation } = field.type;
if (!validation)
return !1;
const rules = Array.isArray(validation) ? validation : [validation];
for (const rule of rules) {
let assetRequired = !1;
const proxy = new Proxy(
{},
{
get: (target, methodName) => () => (methodName === "assetRequired" && (assetRequired = !0), proxy)
}
);
if (typeof rule == "function" && (rule(proxy), assetRequired) || typeof rule == "object" && rule !== null && "_rules" in rule && Array.isArray(rule._rules) && rule._rules.some((r) => r.flag === "assetRequired"))
return !0;
}
return !1;
}
function isObjectType(typeDef) {
return isType(typeDef, "object") || typeDef.jsonType === "object" || "fields" in typeDef;
}
function isArrayType(typeDef) {
return isType(typeDef, "array");
}
function isReferenceType(typeDef) {
return isType(typeDef, "reference");
}
function isCrossDatasetReferenceType(typeDef) {
return isType(typeDef, "crossDatasetReference");
}
function isGlobalDocumentReferenceType(typeDef) {
return isType(typeDef, "globalDocumentReference");
}
function isStringType(typeDef) {
return isType(typeDef, "string");
}
function isNumberType(typeDef) {
return isType(typeDef, "number");
}
function createStringTypeNodeDefintion(stringSchemaType) {
const listOptions = stringSchemaType.options?.list;
return listOptions && Array.isArray(listOptions) ? {
type: "union",
of: listOptions.map((v) => ({
type: "string",
value: typeof v == "string" ? v : v.value
}))
} : {
type: "string"
};
}
function createNumberTypeNodeDefintion(numberSchemaType) {
const listOptions = numberSchemaType.options?.list;
return listOptions && Array.isArray(listOptions) ? {
type: "union",
of: listOptions.map((v) => ({
type: "number",
value: typeof v == "number" ? v : v.value
}))
} : {
type: "number"
};
}
function createReferenceTypeNodeDefintion(reference2) {
const references = gatherReferenceNames(reference2);
return references.length === 1 ? groqJs.createReferenceTypeNode(references[0]) : {
type: "union",
of: references.map((name) => groqJs.createReferenceTypeNode(name))
};
}
function gatherReferenceNames(type) {
const allReferences = gatherReferenceTypes(type);
return [...new Set(allReferences.map((ref) => ref.name))];
}
function gatherReferenceTypes(type) {
const refTo = "to" in type ? type.to : [];
return "type" in type && isReferenceType(type.type) ? [...gatherReferenceTypes(type.type), ...refTo] : refTo;
}
function gatherFields(type) {
return "fields" in type ? type.type ? gatherFields(type.type).concat(type.fields) : type.fields : [];
}
function isType(typeDef, typeName) {
let type = typeDef;
for (; type; ) {
if (type.name === typeName || type.type && type.type.name === typeName)
return !0;
type = type.type;
}
return !1;
}
function lastType(typeDef) {
let type = typeDef;
for (; type; ) {
if (!type.type)
return type;
type = type.type;
}
}
function sortByDependencies(compiledSchema) {
const seen = /* @__PURE__ */ new Set();
function walkDependencies(schemaType, dependencies) {
if (!seen.has(schemaType)) {
if (seen.add(schemaType), "fields" in schemaType)
for (const field of gatherFields(schemaType)) {
const last = lastType(field.type);
if (last.name === "document") {
dependencies.add(last);
continue;
}
let schemaTypeName;
schemaType.type.type ? schemaTypeName = field.type.type.name : "jsonType" in schemaType.type && (schemaTypeName = field.type.jsonType), (schemaTypeName === "object" || schemaTypeName === "block") && (isReferenceType(field.type) ? field.type.to.forEach((ref) => dependencies.add(ref.type)) : dependencies.add(field.type)), walkDependencies(field.type, dependencies);
}
else if ("of" in schemaType)
for (const item of schemaType.of)
walkDependencies(item, dependencies);
}
}
const dependencyMap = /* @__PURE__ */ new Map();
compiledSchema.getTypeNames().forEach((typeName) => {
const schemaType = compiledSchema.get(typeName);
if (schemaType === void 0 || schemaType.type === null)
return;
const dependencies = /* @__PURE__ */ new Set();
walkDependencies(schemaType, dependencies), dependencyMap.set(schemaType, dependencies), seen.clear();
});
const typeNames = [], currentlyVisiting = /* @__PURE__ */ new Set(), visited = /* @__PURE__ */ new Set();
function visit(type) {
if (visited.has(type) || currentlyVisiting.has(type))
return;
currentlyVisiting.add(type);
const deps = dependencyMap.get(type);
deps !== void 0 && deps.forEach((dep) => visit(dep)), currentlyVisiting.delete(type), visited.add(type), typeNames.includes(type.name) || typeNames.unshift(type.name);
}
for (const [type] of dependencyMap)
visit(type);
return typeNames;
}
const HELP_IDS = {
TYPE_INVALID: "schema-type-invalid",
TYPE_IS_ESM_MODULE: "schema-type-is-esm-module",
TYPE_NAME_RESERVED: "schema-type-name-reserved",
TYPE_MISSING_NAME: "schema-type-missing-name-or-type",
TYPE_MISSING_TYPE: "schema-type-missing-name-or-type",
TYPE_TITLE_RECOMMENDED: "schema-type-title-is-recommended",
TYPE_TITLE_INVALID: "schema-type-title-is-recommended",
OBJECT_FIELDS_INVALID: "schema-object-fields-invalid",
OBJECT_FIELD_NOT_UNIQUE: "schema-object-fields-invalid",
OBJECT_FIELD_NAME_INVALID: "schema-object-fields-invalid",
OBJECT_FIELD_DEFINITION_INVALID_TYPE: "schema-object-fields-invalid",
ARRAY_PREDEFINED_CHOICES_INVALID: "schema-predefined-choices-invalid",
ARRAY_OF_ARRAY: "schema-array-of-array",
ARRAY_OF_INVALID: "schema-array-of-invalid",
ARRAY_OF_NOT_UNIQUE: "schema-array-of-invalid",
ARRAY_OF_TYPE_GLOBAL_TYPE_CONFLICT: "schema-array-of-type-global-type-conflict",
ARRAY_OF_TYPE_BUILTIN_TYPE_CONFLICT: "schema-array-of-type-builtin-type-conflict",
REFERENCE_TO_INVALID: "schema-reference-to-invalid",
REFERENCE_TO_NOT_UNIQUE: "schema-reference-to-invalid",
REFERENCE_INVALID_OPTIONS: "schema-reference-invalid-options",
REFERENCE_INVALID_OPTIONS_LOCATION: "schema-reference-options-nesting",
REFERENCE_INVALID_FILTER_PARAMS_COMBINATION: "schema-reference-filter-params-combination",
SLUG_SLUGIFY_FN_RENAMED: "slug-slugifyfn-renamed",
ASSET_METADATA_FIELD_INVALID: "asset-metadata-field-invalid",
CROSS_DATASET_REFERENCE_INVALID: "cross-dataset-reference-invalid",
GLOBAL_DOCUMENT_REFERENCE_INVALID: "global-document-reference-invalid",
DEPRECATED_BLOCKEDITOR_KEY: "schema-deprecated-blockeditor-key",
STANDALONE_BLOCK_TYPE: "schema-standalone-block-type"
};
function createValidationResult(severity, message, helpId) {
if (helpId && !Object.keys(HELP_IDS).some((id) => HELP_IDS[id] === helpId))
throw new Error(
`Used the unknown helpId "${helpId}", please add it to the array in createValidationResult.js`
);
return {
severity,
message,
helpId
};
}
const error = (message, helpId) => createValidationResult("error", message, helpId), warning = (message, helpId) => createValidationResult("warning", message, helpId);
function groupProblems(types) {
return flatten__default.default(types.map((type) => getTypeProblems(type))).filter(
(type) => type.problems.length > 0
);
}
function createTypeWithMembersProblemsAccessor(memberPropertyName, getMembers = (type) => get__default.default(type, memberPropertyName)) {
return function(type, parentPath) {
const currentPath = [
...parentPath,
{ kind: "type", type: type.type, name: type.name }
], members = getMembers(type) || [], memberProblems = Array.isArray(members) ? members.map((memberType) => {
const propertySegment = {
kind: "property",
name: memberPropertyName
}, memberPath = [...currentPath, propertySegment];
return getTypeProblems(memberType, memberPath);
}) : [
[
{
path: currentPath,
problems: [error(`Member declaration (${memberPropertyName}) is not an array`)]
}
]
];
return [
{
path: currentPath,
problems: type._problems || []
},
...flatten__default.default(memberProblems)
];
};
}
const arrify = (val) => Array.isArray(val) ? val : typeof val > "u" && [] || [val], getObjectProblems = createTypeWithMembersProblemsAccessor("fields"), getImageProblems = createTypeWithMembersProblemsAccessor("fields"), getFileProblems = createTypeWithMembersProblemsAccessor("fields"), getArrayProblems = createTypeWithMembersProblemsAccessor("of"), getReferenceProblems = createTypeWithMembersProblemsAccessor(
"to",
(type) => "to" in type ? arrify(type.to) : []
), getBlockAnnotationProblems = createTypeWithMembersProblemsAccessor("marks.annotations"), getBlockMemberProblems = createTypeWithMembersProblemsAccessor("of"), getBlockProblems = (type, problems) => [
...getBlockAnnotationProblems(type, problems),
...getBlockMemberProblems(type, problems)
];
function getDefaultProblems(type, path = []) {
return [
{
path: [...path, { kind: "type", type: type.type, name: type.name }],
problems: type._problems || []
}
];
}
function getTypeProblems(type, path = []) {
switch (type.type) {
case "object":
return getObjectProblems(type, path);
case "document":
return getObjectProblems(type, path);
case "array":
return getArrayProblems(type, path);
case "reference":
return getReferenceProblems(type, path);
case "block":
return getBlockProblems(type, path);
case "image":
return getImageProblems(type, path);
case "file":
return getFileProblems(type, path);
default:
return getDefaultProblems(type, path);
}
}
function getDupes(array2, selector = (v) => v) {
const dupes = array2.reduce((acc, item) => {
const key = selector(item);
return acc[key] || (acc[key] = []), acc[key].push(item), acc;
}, {});
return Object.keys(dupes).map((key) => dupes[key].length > 1 ? dupes[key] : null).filter(Boolean);
}
const NOOP_VISITOR = (typeDef) => typeDef, TYPE_TYPE = { name: "type", type: null }, FUTURE_RESERVED = ["any", "time", "date"];
function traverseSchema(types = [], coreTypes2 = [], visitor = NOOP_VISITOR) {
const coreTypesRegistry = /* @__PURE__ */ Object.create(null), registry = /* @__PURE__ */ Object.create(null), coreTypeNames2 = coreTypes2.map((typeDef) => typeDef.name), reservedTypeNames = FUTURE_RESERVED.concat(coreTypeNames2), typeNames = types.map((typeDef) => typeDef && typeDef.name).filter(Boolean);
coreTypes2.forEach((coreType) => {
coreTypesRegistry[coreType.name] = coreType;
}), types.forEach((type, i) => {
registry[type && type.name || `__unnamed_${i}`] = {};
});
function getType(typeName) {
return typeName === "type" ? TYPE_TYPE : coreTypesRegistry[typeName] || registry[typeName] || null;
}
const duplicateNames = uniq__default.default(flatten__default.default(getDupes(typeNames)));
function isDuplicate(typeName) {
return duplicateNames.includes(typeName);
}
function getTypeNames() {
return typeNames.concat(coreTypeNames2);
}
function isReserved(typeName) {
return typeName === "type" || reservedTypeNames.includes(typeName);
}
const visitType = (isRoot) => (typeDef, index) => visitor(typeDef, {
visit: visitType(!1),
isRoot,
getType,
getTypeNames,
isReserved,
isDuplicate,
index
});
return coreTypes2.forEach((coreTypeDef) => {
Object.assign(coreTypesRegistry[coreTypeDef.name], visitType(coreTypeDef));
}), types.forEach((typeDef, i) => {
Object.assign(
registry[typeDef && typeDef.name || `__unnamed_${i}`],
visitType(!0)(typeDef, i)
);
}), {
get(typeName) {
const res = registry[typeName] || coreTypesRegistry[typeName];
if (res)
return res;
throw new Error(`No such type: ${typeName}`);
},
has(typeName) {
return typeName in registry || typeName in coreTypesRegistry;
},
getTypeNames() {
return Object.keys(registry);
},
getTypes() {
return this.getTypeNames().map(this.get);
},
toJSON() {
return this.getTypes();
}
};
}
const coreTypes = [
{ name: "array", jsonType: "array", type: "type" },
{ name: "block", jsonType: "object", type: "type" },
{ name: "boolean", jsonType: "boolean", type: "type" },
{ name: "datetime", jsonType: "string", type: "type" },
{ name: "date", jsonType: "string", type: "type" },
{ name: "document", jsonType: "object", type: "type" },
{ name: "email", jsonType: "string", type: "type" },
{ name: "file", jsonType: "object", type: "type" },
{ name: "geopoint", jsonType: "object", type: "type" },
{ name: "image", jsonType: "object", type: "type" },
{ name: "number", jsonType: "number", type: "type" },
{ name: "object", jsonType: "object", type: "type" },
{ name: "reference", jsonType: "object", type: "type" },
{ name: "crossDatasetReference", jsonType: "object", type: "type" },
{ name: "globalDocumentReference", jsonType: "object", type: "type" },
{ name: "slug", jsonType: "object", type: "type" },
{ name: "span", jsonType: "object", type: "type" },
{ name: "string", jsonType: "string", type: "type" },
{ name: "telephone", jsonType: "string", type: "type" },
{ name: "text", jsonType: "string", type: "type" },
{ name: "url", jsonType: "string", type: "type" }
], coreTypeNames = coreTypes.map((t) => t.name);
function traverseSanitySchema(schemaTypes, visitor) {
return traverseSchema(schemaTypes, coreTypes, visitor);
}
function isPrimitiveTypeName(typeName) {
return typeName === "string" || typeName === "number" || typeName === "boolean";
}
function isAssignable(typeName, type) {
return (typeof type.name == "string" ? type.name : type.type) === typeName;
}
function quote$2(n) {
return `"${n}"`;
}
function pluralize(arr, suf = "s") {
return arr.length === 1 ? "" : suf;
}
function format(value) {
return Array.isArray(value) ? `array with ${value.length} entries` : typeof value == "object" && value !== null ? `object with keys ${humanizeList__default.default(Object.keys(value).map(quote$2))}` : quote$2(value);
}
var array = (typeDef, visitorContext) => {
const ofIsArray = Array.isArray(typeDef.of);
if (ofIsArray) {
const invalid = typeDef.of.reduce((errs, def, idx) => {
if (typeof def.name == "string" && // specifying the same name as the type is redundant, but should not be a hard error at this point
// Consider showing a warning for this and deprecate this ability eventually
def.name !== def.type && coreTypeNames.includes(def.name))
return errs.concat(
error(
`Found array member declaration with the same type name as a built-in type ("${def.name}"). Array members can not be given the same name as a built-in type.`,
HELP_IDS.ARRAY_OF_TYPE_BUILTIN_TYPE_CONFLICT
)
);
if (def.type === "object" && def.name && visitorContext.getType(def.name))
return errs.concat(
warning(
`Found array member declaration with the same name as the global schema type "${def.name}". It's recommended to use a unique name to avoid possibly incompatible data types that shares the same name.`,
HELP_IDS.ARRAY_OF_TYPE_GLOBAL_TYPE_CONFLICT
)
);
if (def.type === "array")
return errs.concat(
error(
'Found array member declaration of type "array" - multidimensional arrays are not currently supported by Sanity',
HELP_IDS.ARRAY_OF_ARRAY
)
);
if (def)
return errs;
const err = `Found ${def === null ? "null" : typeof def}, expected member declaration`;
return errs.concat(
error(
`Found invalid type member declaration in array at index ${idx}: ${err}`,
HELP_IDS.ARRAY_OF_INVALID
)
);
}, []);
if (invalid.length > 0)
return {
...typeDef,
of: [],
_problems: invalid
};
}
const problems = flatten__default.default([
ofIsArray ? getDupes(typeDef.of, (t) => `${t.name};${t.type}`).map(
(dupes) => error(
`Found ${dupes.length} members with same type, but not unique names "${dupes[0].type}" in array. This makes it impossible to tell their values apart and you should consider naming them`,
HELP_IDS.ARRAY_OF_NOT_UNIQUE
)
) : error(
'The array type is missing or having an invalid value for the required "of" property',
HELP_IDS.ARRAY_OF_INVALID
)
]), of = ofIsArray ? typeDef.of : [], hasObjectTypesWithoutName = of.some(
(type) => type.type === "object" && typeof type.name > "u"
);
of.some((ofType) => ofType.type === "block") && hasObjectTypesWithoutName && problems.push(
error(
"The array type's 'of' property can't have an object type without a 'name' property as member, when the 'block' type is also a member of that array.",
HELP_IDS.ARRAY_OF_INVALID
)
);
const [primitiveTypes, objectTypes] = partition__default.default(
of,
(ofType) => isPrimitiveTypeName(ofType.type) || isPrimitiveTypeName(visitorContext.getType(ofType.type)?.jsonType)
), isMixedArray = primitiveTypes.length > 0 && objectTypes.length > 0;
if (isMixedArray) {
const primitiveTypeNames = primitiveTypes.map((t) => t.type), objectTypeNames = objectTypes.map((t) => t.type);
problems.push(
error(
`The array type's 'of' property can't have both object types and primitive types (found primitive type ${pluralize(
primitiveTypeNames
)} ${humanizeList__default.default(primitiveTypeNames.map(quote$2))} and object type${pluralize(
objectTypeNames
)} ${humanizeList__default.default(objectTypeNames.map(quote$2))})`,
HELP_IDS.ARRAY_OF_INVALID
)
);
}
const list = typeDef?.options?.list;
return !isMixedArray && Array.isArray(list) && (primitiveTypes.length > 0 ? list.forEach((option) => {
const value = option?.value ?? option;
if (!primitiveTypes.some((primitiveType) => typeof value === visitorContext.getType(primitiveType.type).jsonType)) {
const formattedTypeList = humanizeList__default.default(
primitiveTypes.map((t) => t.name || t.type),
{ conjunction: "or" }
);
problems.push(
error(
`An invalid entry found in options.list: ${format(
value
)}. Must be either a value of type ${formattedTypeList}, or an object with {title: string, value: ${formattedTypeList}}`,
HELP_IDS.ARRAY_PREDEFINED_CHOICES_INVALID
)
);
}
}) : list.forEach((option) => {
const optionTypeName = option._type || "object";
objectTypes.some(
(validObjectType) => isAssignable(optionTypeName, validObjectType)
) || problems.push(
error(
`An invalid entry found in options.list: ${format(
option
)}. Must be an object with "_type" set to ${humanizeList__default.default(
objectTypes.map((t) => t.name || t.type).map((t) => t === "object" ? "undefined" : quote$2(t)),
{ conjunction: "or" }
)}`,
HELP_IDS.ARRAY_PREDEFINED_CHOICES_INVALID
)
);
})), typeDef?.options?.list && typeDef?.options?.layout === "tags" && problems.push(
warning(
"Found array member declaration with both tags layout and a list of predefined values. If you want to display a list of predefined values, remove the tags layout from `options`."
)
), {
...typeDef,
of: of.map(visitorContext.visit),
_problems: problems
};
};
function isJSONTypeOf(type, jsonType, visitorContext) {
if ("jsonType" in type)
return type.jsonType === jsonType;
const parentType = visitorContext.getType(type.type);
if (!parentType)
throw new Error(`Could not resolve jsonType of ${type.name}. No parent type found`);
return isJSONTypeOf(parentType, jsonType, visitorContext);
}
const getTypeOf = (thing) => Array.isArray(thing) ? "array" : typeof thing, quote$1 = (str) => `"${str}"`, allowedKeys = [
"components",
"lists",
"marks",
"name",
"of",
"options",
"styles",
"title",
"type",
"validation"
], allowedMarkKeys = ["decorators", "annotations"], allowedStyleKeys = ["blockEditor", "title", "value", "icon", "component"], allowedDecoratorKeys = ["blockEditor", "title", "value", "icon", "component"], allowedListKeys = ["title", "value", "icon", "component"], supportedBuiltInObjectTypes = [
"file",
"image",
"object",
"reference",
"crossDatasetReference",
"globalDocumentReference"
];
function validateBlockType(typeDef, visitorContext) {
const problems = [];
let styles = typeDef.styles, lists = typeDef.lists, marks = typeDef.marks, members = typeDef.of;
const disallowedKeys = Object.keys(typeDef).filter(
(key) => !allowedKeys.includes(key) && !key.startsWith("_")
);
return disallowedKeys.length > 0 && problems.push(
error(
`Found unknown properties for block declaration: ${humanizeList__default.default(
disallowedKeys.map(quote$1)
)}`
)
), marks && (marks = validateMarks(typeDef.marks, visitorContext, problems)), styles && (styles = validateStyles(styles, visitorContext, problems)), lists && (lists = validateLists(lists, visitorContext, problems)), members && (members = validateMembers(members, visitorContext, problems)), {
...omit__default.default(typeDef, disallowedKeys),
marks,
styles,
name: typeDef.name || typeDef.type,
of: members,
_problems: problems
};
}
function validateMarks(marks, visitorContext, problems) {
let decorators = marks.decorators, annotations = marks.annotations;
if (!isPlainObject__default.default(marks))
return problems.push(error(`"marks" declaration should be an object, got ${getTypeOf(marks)}`)), problems;
const disallowedMarkKeys = Object.keys(marks).filter(
(key) => !allowedMarkKeys.includes(key) && !key.startsWith("_")
);
return disallowedMarkKeys.length > 0 && problems.push(
error(
`Found unknown properties for block declaration: ${humanizeList__default.default(
disallowedMarkKeys.map(quote$1)
)}`
)
), decorators && !Array.isArray(decorators) ? problems.push(
error(`"marks.decorators" declaration should be an array, got ${getTypeOf(decorators)}`)
) : decorators && (decorators.filter((dec) => !!dec.blockEditor).forEach((dec) => {
dec.icon = dec.blockEditor.icon, dec.component = dec.blockEditor.render;
}), decorators = validateDecorators(decorators, visitorContext, problems)), annotations && !Array.isArray(annotations) ? problems.push(
error(`"marks.annotations" declaration should be an array, got ${getTypeOf(annotations)}`)
) : annotations && (annotations = validateAnnotations(annotations, visitorContext, problems)), { ...marks, decorators, annotations };
}
function validateLists(lists, visitorContext, problems) {
return Array.isArray(lists) ? (lists.forEach((list, index) => {
if (!isPlainObject__default.default(list)) {
problems.push(error(`List must be an object, got ${getTypeOf(list)}`));
return;
}
const name = list.value || `#${index}`, disallowedKeys = Object.keys(list).filter(
(key) => !allowedListKeys.includes(key) && !key.startsWith("_")
);
disallowedKeys.length > 0 && problems.push(
error(
`Found unknown properties for list ${name}: ${humanizeList__default.default(disallowedKeys.map(quote$1))}`
)
), list.value ? typeof list.value != "string" ? problems.push(
error(
`List type #${index} has an invalid "value" property, expected string, got ${getTypeOf(
list.value
)}`
)
) : list.title || problems.push(warning(`List type ${name} is missing recommended "title" property`)) : problems.push(error(`List #${index} is missing required "value" property`));
}), lists) : (problems.push(error(`"lists" declaration should be an array, got ${getTypeOf(lists)}`)), problems);
}
function validateStyles(styles, visitorContext, problems) {
return Array.isArray(styles) ? (styles.forEach((style, index) => {
if (!isPlainObject__default.default(style)) {
problems.push(error(`Style must be an object, got ${getTypeOf(style)}`));
return;
}
const name = style.value || `#${index}`, disallowedKeys = Object.keys(style).filter(
(key) => !allowedStyleKeys.includes(key) && !key.startsWith("_")
);
disallowedKeys.length > 0 && problems.push(
error(
`Found unknown properties for style ${name}: ${humanizeList__default.default(disallowedKeys.map(quote$1))}`
)
), style.value ? typeof style.value != "string" ? problems.push(
error(
`Style #${index} has an invalid "value" property, expected string, got ${getTypeOf(
style.value
)}`
)
) : style.title || problems.push(warning(`Style ${name} is missing recommended "title" property`)) : problems.push(error(`Style #${index} is missing required "value" property`)), typeof style.blockEditor < "u" && (problems.push(
warning(
'Style has deprecated key "blockEditor", please refer to the documentation on how to configure the block type for version 3.',
HELP_IDS.DEPRECATED_BLOCKEDITOR_KEY
)
), style.component = style.component || style.blockEditor.render);
}), styles) : (problems.push(error(`"styles" declaration should be an array, got ${getTypeOf(styles)}`)), problems);
}
function validateDecorators(decorators, visitorContext, problems) {
return decorators.forEach((decorator, index) => {
if (!isPlainObject__default.default(decorator)) {
problems.push(error(`Annotation must be an object, got ${getTypeOf(decorator)}`));
return;
}
const name = decorator.value || `#${index}`, disallowedKeys = Object.keys(decorator).filter(
(key) => !allowedDecoratorKeys.includes(key) && !key.startsWith("_")
);
disallowedKey