Initial commit
This commit is contained in:
195
mcp-server/utils/fieldHelpers.js
Normal file
195
mcp-server/utils/fieldHelpers.js
Normal file
@@ -0,0 +1,195 @@
|
||||
/**
|
||||
* Field and schema manipulation helpers
|
||||
*/
|
||||
|
||||
export const LIST_OPTION_ALIAS_KEYS = ["options", "optionsList", "optionList", "choices", "values", "items"];
|
||||
|
||||
export const optionEntryToLine = (entry) => {
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof entry === "string") {
|
||||
const trimmed = entry.trim();
|
||||
if (!trimmed) return null;
|
||||
if (trimmed.includes("|")) return trimmed.replace(/\r/g, "");
|
||||
return `${trimmed}|${trimmed}`;
|
||||
}
|
||||
|
||||
if (Array.isArray(entry)) {
|
||||
const [valueRaw, labelRaw] = entry;
|
||||
const value = valueRaw ?? labelRaw;
|
||||
const label = labelRaw ?? valueRaw;
|
||||
if (value == null && label == null) return null;
|
||||
const valueStr = `${value ?? ""}`.trim();
|
||||
const labelStr = `${label ?? valueStr}`.trim();
|
||||
if (!valueStr) return null;
|
||||
return `${valueStr}|${labelStr || valueStr}`;
|
||||
}
|
||||
|
||||
if (typeof entry === "object") {
|
||||
const value =
|
||||
entry.value ??
|
||||
entry.id ??
|
||||
entry.key ??
|
||||
entry.slug ??
|
||||
entry.code ??
|
||||
entry.name ??
|
||||
entry.label ??
|
||||
entry.text;
|
||||
const label = entry.label ?? entry.text ?? entry.name ?? entry.title ?? value;
|
||||
if (value == null && label == null) return null;
|
||||
const valueStr = `${value ?? ""}`.trim();
|
||||
const labelStr = `${label ?? valueStr}`.trim();
|
||||
if (!valueStr) return null;
|
||||
return `${valueStr}|${labelStr || valueStr}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const buildOptionsTextFromInput = (input) => {
|
||||
if (input == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (Array.isArray(input)) {
|
||||
return input.map(optionEntryToLine).filter(Boolean).join("\n");
|
||||
}
|
||||
|
||||
if (typeof input === "object") {
|
||||
return Object.entries(input)
|
||||
.map(([value, label]) => optionEntryToLine({ value, label }))
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
if (typeof input === "string") {
|
||||
const trimmed = input.trim();
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (trimmed.includes("|")) {
|
||||
return trimmed
|
||||
.replace(/\r/g, "")
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
const hasNewLines = /\r|\n/.test(trimmed);
|
||||
const separator = hasNewLines ? /\r?\n/ : /,/;
|
||||
return trimmed
|
||||
.split(separator)
|
||||
.map((token) => token.trim())
|
||||
.filter(Boolean)
|
||||
.map((token) => `${token}|${token}`)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
export const normalizeListFieldDefinition = (field = {}) => {
|
||||
if (!field || field.type !== "list") {
|
||||
return field;
|
||||
}
|
||||
|
||||
if (!field.listType) {
|
||||
field.listType = "pulldown";
|
||||
}
|
||||
|
||||
if (!field.optionsType) {
|
||||
field.optionsType = "text";
|
||||
}
|
||||
|
||||
if (field.optionsType === "text") {
|
||||
let source = field.optionsText;
|
||||
|
||||
if (Array.isArray(source) || (source && typeof source === "object" && !Array.isArray(source))) {
|
||||
field.optionsText = buildOptionsTextFromInput(source);
|
||||
} else {
|
||||
let aliasValue;
|
||||
for (const aliasKey of LIST_OPTION_ALIAS_KEYS) {
|
||||
if (field[aliasKey] != null) {
|
||||
aliasValue = field[aliasKey];
|
||||
delete field[aliasKey];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (aliasValue != null) {
|
||||
field.optionsText = buildOptionsTextFromInput(aliasValue);
|
||||
} else if (typeof field.optionsText === "string") {
|
||||
field.optionsText = buildOptionsTextFromInput(field.optionsText);
|
||||
} else {
|
||||
field.optionsText = field.optionsText ?? "";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Ensure plain text payload for non-text option sources.
|
||||
if (field.optionsText && typeof field.optionsText !== "string") {
|
||||
field.optionsText = "";
|
||||
}
|
||||
for (const aliasKey of LIST_OPTION_ALIAS_KEYS) {
|
||||
if (field[aliasKey] != null) {
|
||||
delete field[aliasKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return field;
|
||||
};
|
||||
|
||||
export const normalizeSchemaForSave = (schema = {}) => {
|
||||
if (!schema || typeof schema !== "object") {
|
||||
return schema;
|
||||
}
|
||||
|
||||
if (schema.schema && typeof schema.schema === "object") {
|
||||
const normalized = {};
|
||||
for (const [fieldName, fieldDefinition] of Object.entries(schema.schema)) {
|
||||
if (!fieldDefinition || typeof fieldDefinition !== "object") {
|
||||
normalized[fieldName] = fieldDefinition;
|
||||
continue;
|
||||
}
|
||||
const clonedDefinition = { ...fieldDefinition };
|
||||
if (clonedDefinition.type === "list") {
|
||||
normalized[fieldName] = normalizeListFieldDefinition(clonedDefinition);
|
||||
} else {
|
||||
normalized[fieldName] = clonedDefinition;
|
||||
}
|
||||
}
|
||||
schema.schema = normalized;
|
||||
}
|
||||
|
||||
return schema;
|
||||
};
|
||||
|
||||
export const mergeTableSchemas = (currentTable, incomingSchema = {}) => {
|
||||
const merged = {
|
||||
...currentTable,
|
||||
schema: { ...(currentTable?.schema || {}) },
|
||||
schemaInfo: { ...(currentTable?.schemaInfo || {}) },
|
||||
};
|
||||
|
||||
if (!incomingSchema || typeof incomingSchema !== "object") {
|
||||
return merged;
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(incomingSchema)) {
|
||||
if (key === "schema" && value && typeof value === "object") {
|
||||
merged.schema = { ...(currentTable?.schema || {}), ...value };
|
||||
} else if (key === "schemaInfo" && value && typeof value === "object") {
|
||||
merged.schemaInfo = { ...(currentTable?.schemaInfo || {}), ...value };
|
||||
} else {
|
||||
merged[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return merged;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user