@ai-stack/payloadcms
Version:
<p align="center"> <img alt="Payload AI Plugin" src="assets/payload-ai-intro.gif" width="100%" /> </p>
274 lines (273 loc) • 8.44 kB
JavaScript
/**
* fieldToJsonSchema
* Convert a Payload Field (server or client) into a minimal JSON Schema object,
* wrapped as { type: 'object', properties: { [name]: valueSchema }, required: [...] }
*
* Supported types:
* - text, textarea, select, number, date, code, email, json
* Arrays are emitted only when field.hasMany is true and the field type supports hasMany
* (text, textarea, select; for others only if your config truly sets hasMany).
*/ function isString(s) {
return typeof s === 'string';
}
function isPlainObject(o) {
return !!o && typeof o === 'object' && !Array.isArray(o);
}
function getDescription(field) {
const d = field?.admin?.description;
return typeof d === 'string' ? d : undefined;
}
function stringWithDescription(field) {
const out = {
type: 'string'
};
const description = getDescription(field);
if (description) {
out.description = description;
}
return out;
}
function numberWithBounds(field) {
const out = {
type: 'number'
};
if (typeof field.min === 'number') {
out.minimum = field.min;
}
if (typeof field.max === 'number') {
out.maximum = field.max;
}
const description = getDescription(field);
if (description) {
out.description = description;
}
return out;
}
function dateSchema(field) {
const out = {
type: 'string',
format: 'date-time'
};
const description = getDescription(field);
if (description) {
out.description = description;
}
return out;
}
function codeSchema(field) {
const out = {
type: 'string'
};
let description = getDescription(field);
const lang = field?.admin?.language;
if (typeof lang === 'string' && lang.trim()) {
description = description ? `${description} (language: ${lang})` : `language: ${lang}`;
}
if (description) {
out.description = description;
}
return out;
}
function emailSchema(field) {
const out = {
type: 'string',
format: 'email'
};
const description = getDescription(field);
if (description) {
out.description = description;
}
return out;
}
function jsonValueSchema(field) {
// Prefer a provided JSON Schema object
if (isPlainObject(field.jsonSchema)) {
return field.jsonSchema;
}
if (isPlainObject(field.schema)) {
return field.schema;
}
// typescriptSchema cannot be executed here; default to object
return {
type: 'object'
};
}
function normalizeSelectOptions(field) {
const raw = field.options || [];
const values = [];
for (const opt of raw){
if (typeof opt === 'string' || typeof opt === 'number') {
values.push(opt);
} else if (isPlainObject(opt) && 'value' in opt) {
const v = opt.value;
if (typeof v === 'string' || typeof v === 'number') {
values.push(v);
}
}
}
// Infer primitive type
const allNumbers = values.length > 0 && values.every((v)=>typeof v === 'number');
const valueType = allNumbers ? 'number' : 'string';
return {
values,
valueType
};
}
function supportsHasMany(fieldType) {
// Out of the box: text, textarea, select support hasMany
// Others can be arrays only if your config truly sets hasMany; we return boolean based on type here.
return fieldType === 'text' || fieldType === 'textarea' || fieldType === 'select';
}
export function fieldToJsonSchema(fieldInput, opts) {
const field = fieldInput || {};
const name = isString(opts?.nameOverride) && opts?.nameOverride.length ? opts.nameOverride : field.name || 'value';
const type = field.type;
let valueSchema = null;
switch(type){
case 'code':
{
const base = codeSchema(field);
if (field.hasMany) {
valueSchema = {
type: 'array',
items: base
};
} else {
valueSchema = base;
}
break;
}
case 'date':
{
const base = dateSchema(field);
if (field.hasMany) {
valueSchema = {
type: 'array',
items: base
};
} else {
valueSchema = base;
}
break;
}
case 'email':
{
const base = emailSchema(field);
if (field.hasMany) {
valueSchema = {
type: 'array',
items: base
};
} else {
valueSchema = base;
}
break;
}
case 'json':
{
const base = jsonValueSchema(field);
if (field.hasMany) {
valueSchema = {
type: 'array',
items: base
};
} else {
valueSchema = base;
}
break;
}
case 'number':
{
const base = numberWithBounds(field);
if (field.hasMany) {
// Respect hasMany only if truly configured; Payload rarely uses hasMany for number, but allow if present.
valueSchema = {
type: 'array',
items: base
};
} else {
valueSchema = base;
}
break;
}
case 'select':
{
const { values, valueType } = normalizeSelectOptions(field);
const baseSingle = {
type: valueType,
enum: values
};
const description = getDescription(field);
if (description) {
baseSingle.description = description;
}
if (field.hasMany && supportsHasMany(type)) {
valueSchema = {
type: 'array',
items: {
type: valueType,
enum: values
},
...description ? {
description
} : {}
};
} else {
valueSchema = baseSingle;
}
break;
}
case 'text':
case 'textarea':
{
const base = stringWithDescription(field);
if (field.hasMany && supportsHasMany(type)) {
const arr = {
type: 'array',
items: {
type: 'string'
}
};
if (typeof field.minRows === 'number') {
arr.minItems = field.minRows;
}
if (typeof field.maxRows === 'number') {
arr.maxItems = field.maxRows;
}
if (base.description) {
arr.description = base.description;
}
valueSchema = arr;
} else {
valueSchema = base;
}
break;
}
default:
{
// Unsupported type: return null to allow caller to decide how to proceed (e.g., no schema)
valueSchema = null;
break;
}
}
const wrap = opts?.wrapObject !== false;
if (!wrap) {
return valueSchema || {};
}
if (!valueSchema) {
// Return undefined-like schema if not supported; caller may choose to skip passing schema
return {};
}
const schema = {
type: 'object',
additionalProperties: false,
properties: {
[name]: valueSchema
},
required: [
name
]
};
return schema;
}
//# sourceMappingURL=fieldToJsonSchema.js.map