@mindmakr/gs-websdk
Version:
Web SDK for Guru SaaS System - Complete JavaScript/TypeScript SDK for building applications with dynamic schema management
438 lines • 16.7 kB
JavaScript
;
/**
* Schema Helper Utilities
* Provides utility functions for working with JSON schemas and form generation
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.cleanSchemaForStorage = exports.mergeSchemas = exports.getFieldTypeFromSchema = exports.isFieldRequired = exports.getFieldConfig = exports.extractFieldKeys = exports.validateFormData = exports.generateDefaultFormData = exports.convertSchemaToFormConfig = exports.createSchemaWithUISchema = exports.generateUISchemaFromJSONSchema = void 0;
/**
* Generate UI schema from JSON schema for better form rendering
*/
function generateUISchemaFromJSONSchema(jsonSchema) {
const uiSchema = {};
if (jsonSchema.properties) {
Object.entries(jsonSchema.properties).forEach(([key, value]) => {
const fieldUISchema = {};
// Set widget based on format
if (value.format === 'textarea') {
fieldUISchema['ui:widget'] = 'textarea';
fieldUISchema['ui:options'] = { rows: 4 };
}
else if (value.format === 'password') {
fieldUISchema['ui:widget'] = 'password';
}
else if (value.format === 'date') {
fieldUISchema['ui:widget'] = 'date';
}
else if (value.format === 'datetime') {
fieldUISchema['ui:widget'] = 'datetime';
}
else if (value.format === 'email') {
fieldUISchema['ui:widget'] = 'email';
}
else if (value.format === 'color') {
fieldUISchema['ui:widget'] = 'color';
}
else if (value.format === 'richtext') {
fieldUISchema['ui:widget'] = 'richtext';
}
else if (value.format === 'image-url-or-upload') {
fieldUISchema['ui:widget'] = 'ImageInputWidget';
}
else if (value.format === 'video-url-or-upload') {
fieldUISchema['ui:widget'] = 'VideoInputWidget';
}
else if (value.format === 'audio-url-or-upload') {
fieldUISchema['ui:widget'] = 'AudioInputWidget';
}
else if (value.format === 'reference') {
fieldUISchema['ui:widget'] = 'ReferenceFieldWidget';
}
// Set widget for arrays and selection types
if (value.type === 'array' && value.items?.enum) {
fieldUISchema['ui:widget'] = 'checkboxes';
}
else if (value.enum && value['ui:widget'] === 'radio') {
fieldUISchema['ui:widget'] = 'radio';
}
// Add placeholder if available
if (value.description) {
fieldUISchema['ui:placeholder'] = value.description;
}
// Add help text
if (value.helpText) {
fieldUISchema['ui:help'] = value.helpText;
}
// Layout information
if (value['ui:layout'] || value.layout) {
const layout = value['ui:layout'] || value.layout;
fieldUISchema['ui:layout'] = layout;
}
// Add custom options
if (value['ui:options']) {
fieldUISchema['ui:options'] = value['ui:options'];
}
// Only add if we have UI-specific properties
if (Object.keys(fieldUISchema).length > 0) {
uiSchema[key] = fieldUISchema;
}
// Handle nested objects
if (value.type === 'object' && value.properties) {
uiSchema[key] = {
...fieldUISchema,
...generateUISchemaFromJSONSchema(value)
};
}
});
}
// Add field ordering if available
if (jsonSchema['x-field-order']) {
uiSchema['ui:order'] = jsonSchema['x-field-order'];
}
return uiSchema;
}
exports.generateUISchemaFromJSONSchema = generateUISchemaFromJSONSchema;
/**
* Create schema with UI schema information embedded
*/
function createSchemaWithUISchema(jsonSchema, uiSchema) {
const enhancedSchema = JSON.parse(JSON.stringify(jsonSchema)); // Deep clone
if (enhancedSchema.properties && uiSchema) {
Object.entries(uiSchema).forEach(([key, uiConfig]) => {
if (enhancedSchema.properties[key] && typeof uiConfig === 'object') {
// Merge UI config into the property
enhancedSchema.properties[key] = {
...enhancedSchema.properties[key],
...Object.fromEntries(Object.entries(uiConfig).filter(([k]) => k.startsWith('ui:')))
};
}
});
}
return enhancedSchema;
}
exports.createSchemaWithUISchema = createSchemaWithUISchema;
/**
* Convert schema template to a format suitable for form rendering
*/
function convertSchemaToFormConfig(template) {
const schema = template.schema_definition;
const uiSchema = generateUISchemaFromJSONSchema(schema);
// Generate default form data
const formData = generateDefaultFormData(schema);
return {
schema,
uiSchema,
formData
};
}
exports.convertSchemaToFormConfig = convertSchemaToFormConfig;
/**
* Generate default form data from schema
*/
function generateDefaultFormData(schema) {
const formData = {};
if (schema.properties) {
Object.entries(schema.properties).forEach(([key, value]) => {
if (value.default !== undefined) {
formData[key] = value.default;
}
else if (value.type === 'boolean') {
formData[key] = false;
}
else if (value.type === 'array') {
formData[key] = [];
}
else if (value.type === 'object') {
formData[key] = value.properties ? generateDefaultFormData(value) : {};
}
});
}
return formData;
}
exports.generateDefaultFormData = generateDefaultFormData;
/**
* Validate form data against schema
*/
function validateFormData(formData, schema) {
const errors = [];
if (!schema.properties) {
return { isValid: true, errors: [] };
}
// Check required fields
if (schema.required && Array.isArray(schema.required)) {
schema.required.forEach((field) => {
if (!formData[field] || formData[field] === '') {
errors.push({
field,
message: `${field} is required`,
type: 'required'
});
}
});
}
// Validate field types and constraints
Object.entries(schema.properties).forEach(([field, fieldSchema]) => {
const value = formData[field];
if (value !== undefined && value !== null && value !== '') {
// Type validation
if (fieldSchema.type === 'string' && typeof value !== 'string') {
errors.push({
field,
message: `${field} must be a string`,
type: 'type'
});
}
else if (fieldSchema.type === 'number' && typeof value !== 'number') {
errors.push({
field,
message: `${field} must be a number`,
type: 'type'
});
}
else if (fieldSchema.type === 'boolean' && typeof value !== 'boolean') {
errors.push({
field,
message: `${field} must be a boolean`,
type: 'type'
});
}
else if (fieldSchema.type === 'array' && !Array.isArray(value)) {
errors.push({
field,
message: `${field} must be an array`,
type: 'type'
});
}
// String constraints
if (fieldSchema.type === 'string' && typeof value === 'string') {
if (fieldSchema.minLength && value.length < fieldSchema.minLength) {
errors.push({
field,
message: `${field} must be at least ${fieldSchema.minLength} characters`,
type: 'minLength'
});
}
if (fieldSchema.maxLength && value.length > fieldSchema.maxLength) {
errors.push({
field,
message: `${field} must be no more than ${fieldSchema.maxLength} characters`,
type: 'maxLength'
});
}
if (fieldSchema.pattern && !new RegExp(fieldSchema.pattern).test(value)) {
errors.push({
field,
message: `${field} format is invalid`,
type: 'pattern'
});
}
}
// Number constraints
if (fieldSchema.type === 'number' && typeof value === 'number') {
if (fieldSchema.minimum !== undefined && value < fieldSchema.minimum) {
errors.push({
field,
message: `${field} must be at least ${fieldSchema.minimum}`,
type: 'minimum'
});
}
if (fieldSchema.maximum !== undefined && value > fieldSchema.maximum) {
errors.push({
field,
message: `${field} must be no more than ${fieldSchema.maximum}`,
type: 'maximum'
});
}
}
// Array constraints
if (fieldSchema.type === 'array' && Array.isArray(value)) {
if (fieldSchema.minItems && value.length < fieldSchema.minItems) {
errors.push({
field,
message: `${field} must have at least ${fieldSchema.minItems} items`,
type: 'minItems'
});
}
if (fieldSchema.maxItems && value.length > fieldSchema.maxItems) {
errors.push({
field,
message: `${field} must have no more than ${fieldSchema.maxItems} items`,
type: 'maxItems'
});
}
}
// Enum validation
if (fieldSchema.enum && !fieldSchema.enum.includes(value)) {
errors.push({
field,
message: `${field} must be one of: ${fieldSchema.enum.join(', ')}`,
type: 'enum'
});
}
// Format validation
if (fieldSchema.format && typeof value === 'string') {
const formatValidators = {
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
url: /^https?:\/\/.+/,
date: /^\d{4}-\d{2}-\d{2}$/,
time: /^\d{2}:\d{2}$/,
'date-time': /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/,
phone: /^[\+]?[\d\s\-\(\)]+$/
};
const validator = formatValidators[fieldSchema.format];
if (validator && !validator.test(value)) {
errors.push({
field,
message: `${field} must be a valid ${fieldSchema.format}`,
type: 'format'
});
}
}
}
});
return {
isValid: errors.length === 0,
errors
};
}
exports.validateFormData = validateFormData;
/**
* Extract field keys from schema in order
*/
function extractFieldKeys(schema) {
if (!schema.properties) {
return [];
}
// Use custom field order if available
if (schema['x-field-order'] && Array.isArray(schema['x-field-order'])) {
return schema['x-field-order'];
}
// Otherwise, use the order from properties
return Object.keys(schema.properties);
}
exports.extractFieldKeys = extractFieldKeys;
/**
* Get field configuration from schema
*/
function getFieldConfig(schema, fieldKey) {
if (!schema.properties || !schema.properties[fieldKey]) {
return null;
}
return schema.properties[fieldKey];
}
exports.getFieldConfig = getFieldConfig;
/**
* Check if field is required
*/
function isFieldRequired(schema, fieldKey) {
return schema.required && Array.isArray(schema.required) && schema.required.includes(fieldKey);
}
exports.isFieldRequired = isFieldRequired;
/**
* Generate field type from JSON schema property
*/
function getFieldTypeFromSchema(fieldSchema) {
if (fieldSchema.enum)
return 'select';
if (fieldSchema.format === 'textarea')
return 'textarea';
if (fieldSchema.format === 'email')
return 'email';
if (fieldSchema.format === 'password')
return 'password';
if (fieldSchema.format === 'date')
return 'date';
if (fieldSchema.format === 'datetime')
return 'datetime';
if (fieldSchema.format === 'time')
return 'time';
if (fieldSchema.format === 'url')
return 'url';
if (fieldSchema.format === 'color')
return 'color';
if (fieldSchema.format === 'richtext')
return 'richtext';
if (fieldSchema.format === 'image-url-or-upload')
return 'image';
if (fieldSchema.format === 'video-url-or-upload')
return 'video';
if (fieldSchema.format === 'audio-url-or-upload')
return 'audio';
if (fieldSchema.format === 'reference')
return 'reference';
if (fieldSchema.type === 'boolean')
return 'boolean';
if (fieldSchema.type === 'number' || fieldSchema.type === 'integer')
return 'number';
if (fieldSchema.type === 'array' && fieldSchema.items?.enum)
return 'multiselect';
if (fieldSchema.type === 'array')
return 'array';
if (fieldSchema.type === 'object')
return 'object';
return 'text';
}
exports.getFieldTypeFromSchema = getFieldTypeFromSchema;
/**
* Merge schemas (useful for composition)
*/
function mergeSchemas(baseSchema, extendingSchema, strategy = 'merge') {
const merged = JSON.parse(JSON.stringify(baseSchema)); // Deep clone
if (extendingSchema.properties) {
merged.properties = merged.properties || {};
if (strategy === 'override') {
Object.assign(merged.properties, extendingSchema.properties);
}
else {
// Merge strategy
Object.entries(extendingSchema.properties).forEach(([key, value]) => {
if (merged.properties[key] && typeof merged.properties[key] === 'object' && typeof value === 'object') {
// Deep merge for objects
merged.properties[key] = { ...merged.properties[key], ...value };
}
else {
merged.properties[key] = value;
}
});
}
}
// Merge other properties
['title', 'description', 'required'].forEach(prop => {
if (extendingSchema[prop]) {
if (prop === 'required' && Array.isArray(extendingSchema[prop])) {
merged[prop] = [...(merged[prop] || []), ...extendingSchema[prop]];
// Remove duplicates
merged[prop] = [...new Set(merged[prop])];
}
else {
merged[prop] = extendingSchema[prop];
}
}
});
return merged;
}
exports.mergeSchemas = mergeSchemas;
/**
* Clean schema by removing UI-specific properties for storage
*/
function cleanSchemaForStorage(schema) {
const cleaned = JSON.parse(JSON.stringify(schema)); // Deep clone
function removeUIProperties(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(removeUIProperties);
}
const cleanedObj = {};
Object.entries(obj).forEach(([key, value]) => {
if (!key.startsWith('ui:') && key !== 'layout') {
cleanedObj[key] = removeUIProperties(value);
}
});
return cleanedObj;
}
return removeUIProperties(cleaned);
}
exports.cleanSchemaForStorage = cleanSchemaForStorage;
//# sourceMappingURL=schema-helpers.js.map