Skip to main content
Glama
form-config.js22.2 kB
/** * Form Configuration Data Models * * This file contains TypeScript interfaces and Zod schemas for form configuration, * questions, validation rules, and conditional logic. These models are used for * creating and modifying Tally forms through natural language processing. */ // =============================== // Enums and Constants // =============================== /** * Supported question types for Tally forms */ export var QuestionType; (function (QuestionType) { QuestionType["TEXT"] = "text"; QuestionType["EMAIL"] = "email"; QuestionType["NUMBER"] = "number"; QuestionType["CHOICE"] = "choice"; QuestionType["RATING"] = "rating"; QuestionType["FILE"] = "file"; QuestionType["DATE"] = "date"; QuestionType["TIME"] = "time"; QuestionType["TEXTAREA"] = "textarea"; QuestionType["DROPDOWN"] = "dropdown"; QuestionType["CHECKBOXES"] = "checkboxes"; QuestionType["LINEAR_SCALE"] = "linear_scale"; QuestionType["MULTIPLE_CHOICE"] = "multiple_choice"; QuestionType["PHONE"] = "phone"; QuestionType["URL"] = "url"; QuestionType["SIGNATURE"] = "signature"; QuestionType["PAYMENT"] = "payment"; QuestionType["MATRIX"] = "matrix"; })(QuestionType || (QuestionType = {})); /** * Form display themes and styles */ export var FormTheme; (function (FormTheme) { FormTheme["DEFAULT"] = "default"; FormTheme["MINIMAL"] = "minimal"; FormTheme["MODERN"] = "modern"; FormTheme["CLASSIC"] = "classic"; FormTheme["CUSTOM"] = "custom"; })(FormTheme || (FormTheme = {})); /** * Form submission behavior options */ export var SubmissionBehavior; (function (SubmissionBehavior) { SubmissionBehavior["REDIRECT"] = "redirect"; SubmissionBehavior["MESSAGE"] = "message"; SubmissionBehavior["CLOSE"] = "close"; SubmissionBehavior["RELOAD"] = "reload"; })(SubmissionBehavior || (SubmissionBehavior = {})); /** * Validation rule types */ export var ValidationType; (function (ValidationType) { ValidationType["REQUIRED"] = "required"; ValidationType["MIN_LENGTH"] = "min_length"; ValidationType["MAX_LENGTH"] = "max_length"; ValidationType["MIN_VALUE"] = "min_value"; ValidationType["MAX_VALUE"] = "max_value"; ValidationType["PATTERN"] = "pattern"; ValidationType["EMAIL_FORMAT"] = "email_format"; ValidationType["URL_FORMAT"] = "url_format"; ValidationType["PHONE_FORMAT"] = "phone_format"; ValidationType["DATE_RANGE"] = "date_range"; ValidationType["FILE_TYPE"] = "file_type"; ValidationType["FILE_SIZE"] = "file_size"; })(ValidationType || (ValidationType = {})); /** * Conditional logic operators */ export var LogicOperator; (function (LogicOperator) { LogicOperator["EQUALS"] = "equals"; LogicOperator["NOT_EQUALS"] = "not_equals"; LogicOperator["CONTAINS"] = "contains"; LogicOperator["NOT_CONTAINS"] = "not_contains"; LogicOperator["GREATER_THAN"] = "greater_than"; LogicOperator["LESS_THAN"] = "less_than"; LogicOperator["GREATER_EQUAL"] = "greater_equal"; LogicOperator["LESS_EQUAL"] = "less_equal"; LogicOperator["IS_EMPTY"] = "is_empty"; LogicOperator["IS_NOT_EMPTY"] = "is_not_empty"; })(LogicOperator || (LogicOperator = {})); /** * Question layout options for choice-based questions */ export var QuestionLayout; (function (QuestionLayout) { QuestionLayout["VERTICAL"] = "vertical"; QuestionLayout["HORIZONTAL"] = "horizontal"; QuestionLayout["GRID"] = "grid"; })(QuestionLayout || (QuestionLayout = {})); /** * Rating display styles */ export var RatingStyle; (function (RatingStyle) { RatingStyle["STARS"] = "stars"; RatingStyle["NUMBERS"] = "numbers"; RatingStyle["THUMBS"] = "thumbs"; RatingStyle["HEARTS"] = "hearts"; RatingStyle["FACES"] = "faces"; })(RatingStyle || (RatingStyle = {})); /** * Phone number format types */ export var PhoneFormat; (function (PhoneFormat) { PhoneFormat["US"] = "US"; PhoneFormat["INTERNATIONAL"] = "INTERNATIONAL"; PhoneFormat["CUSTOM"] = "CUSTOM"; })(PhoneFormat || (PhoneFormat = {})); /** * Time format options */ export var TimeFormat; (function (TimeFormat) { TimeFormat["TWELVE_HOUR"] = "12"; TimeFormat["TWENTY_FOUR_HOUR"] = "24"; })(TimeFormat || (TimeFormat = {})); /** * Payment methods */ export var PaymentMethod; (function (PaymentMethod) { PaymentMethod["CARD"] = "card"; PaymentMethod["PAYPAL"] = "paypal"; PaymentMethod["APPLE_PAY"] = "apple_pay"; PaymentMethod["GOOGLE_PAY"] = "google_pay"; })(PaymentMethod || (PaymentMethod = {})); /** * Matrix question response types */ export var MatrixResponseType; (function (MatrixResponseType) { MatrixResponseType["SINGLE_SELECT"] = "single_select"; MatrixResponseType["MULTI_SELECT"] = "multi_select"; MatrixResponseType["TEXT_INPUT"] = "text_input"; MatrixResponseType["RATING"] = "rating"; })(MatrixResponseType || (MatrixResponseType = {})); // =============================== // Type Guards and Utility Types // =============================== /** * Type guard to check if a question has options */ export function questionHasOptions(question) { return question.type === QuestionType.MULTIPLE_CHOICE || question.type === QuestionType.DROPDOWN || question.type === QuestionType.CHECKBOXES; } /** * Type guard to check if a question is text-based */ export function isTextBasedQuestion(question) { return question.type === QuestionType.TEXT || question.type === QuestionType.TEXTAREA || question.type === QuestionType.EMAIL || question.type === QuestionType.PHONE || question.type === QuestionType.URL; } /** * Type guard to check if a question is numeric */ export function isNumericQuestion(question) { return question.type === QuestionType.NUMBER || question.type === QuestionType.RATING || question.type === QuestionType.LINEAR_SCALE; } /** * Type guard to check if a question is date/time related */ export function isDateTimeQuestion(question) { return question.type === QuestionType.DATE || question.type === QuestionType.TIME; } /** * Type guard to check if a question supports file uploads */ export function isFileQuestion(question) { return question.type === QuestionType.FILE || question.type === QuestionType.SIGNATURE; } /** * Type guard to check if a question is a matrix question */ export function isMatrixQuestion(question) { return question.type === QuestionType.MATRIX; } // =============================== // Validation Rule Type Guards and Utilities // =============================== /** * Type guard for required validation */ export function isRequiredValidation(rule) { return rule.type === 'required'; } /** * Type guard for length validation */ export function isLengthValidation(rule) { return rule.type === 'length'; } /** * Type guard for numeric validation */ export function isNumericValidation(rule) { return rule.type === 'numeric'; } /** * Type guard for pattern validation */ export function isPatternValidation(rule) { return rule.type === 'pattern'; } /** * Type guard for email validation */ export function isEmailValidation(rule) { return rule.type === 'email'; } /** * Type guard for URL validation */ export function isUrlValidation(rule) { return rule.type === 'url'; } /** * Type guard for phone validation */ export function isPhoneValidation(rule) { return rule.type === 'phone'; } /** * Type guard for date validation */ export function isDateValidation(rule) { return rule.type === 'date'; } /** * Type guard for time validation */ export function isTimeValidation(rule) { return rule.type === 'time'; } /** * Type guard for file validation */ export function isFileValidation(rule) { return rule.type === 'file'; } /** * Type guard for choice validation */ export function isChoiceValidation(rule) { return rule.type === 'choice'; } /** * Type guard for rating validation */ export function isRatingValidation(rule) { return rule.type === 'rating'; } /** * Type guard for custom validation */ export function isCustomValidation(rule) { return rule.type === 'custom'; } /** * Get compatible validation rules for a specific question type */ export function getCompatibleValidationTypes(questionType) { const baseTypes = ['required', 'custom']; switch (questionType) { case QuestionType.TEXT: case QuestionType.TEXTAREA: return [...baseTypes, 'length', 'pattern']; case QuestionType.EMAIL: return [...baseTypes, 'length', 'pattern', 'email']; case QuestionType.PHONE: return [...baseTypes, 'length', 'pattern', 'phone']; case QuestionType.URL: return [...baseTypes, 'length', 'pattern', 'url']; case QuestionType.NUMBER: return [...baseTypes, 'numeric']; case QuestionType.DATE: return [...baseTypes, 'date']; case QuestionType.TIME: return [...baseTypes, 'time']; case QuestionType.MULTIPLE_CHOICE: case QuestionType.DROPDOWN: case QuestionType.CHECKBOXES: return [...baseTypes, 'choice']; case QuestionType.RATING: case QuestionType.LINEAR_SCALE: return [...baseTypes, 'rating', 'numeric']; case QuestionType.FILE: case QuestionType.SIGNATURE: return [...baseTypes, 'file']; case QuestionType.MATRIX: return [...baseTypes, 'choice']; case QuestionType.PAYMENT: return [...baseTypes, 'numeric']; default: return baseTypes; } } /** * Check if a validation rule is compatible with a question type */ export function isValidationRuleCompatible(rule, questionType) { const compatibleTypes = getCompatibleValidationTypes(questionType); return compatibleTypes.includes(rule.type); } /** * Filter validation rules to only include compatible ones for a question type */ export function filterCompatibleValidationRules(rules, questionType) { return rules.filter(rule => isValidationRuleCompatible(rule, questionType)); } /** * Utility function to create typed validation rules for a specific question type */ export function createValidationRulesForQuestion(rules) { return { rules: rules, validateOnChange: true, validateOnBlur: true, stopOnFirstError: false, }; } /** * Combine multiple validation rule sets */ export function combineValidationRules(...ruleSets) { const combinedRules = []; const combinedCustomMessages = {}; let validateOnChange = false; let validateOnBlur = false; let stopOnFirstError = false; const allDependencies = []; for (const ruleSet of ruleSets) { if (ruleSet.rules) { combinedRules.push(...ruleSet.rules); } if (ruleSet.customMessages) { Object.assign(combinedCustomMessages, ruleSet.customMessages); } if (ruleSet.validateOnChange) validateOnChange = true; if (ruleSet.validateOnBlur) validateOnBlur = true; if (ruleSet.stopOnFirstError) stopOnFirstError = true; if (ruleSet.dependencies) { allDependencies.push(...ruleSet.dependencies); } } const result = { rules: combinedRules, validateOnChange, validateOnBlur, stopOnFirstError, }; if (Object.keys(combinedCustomMessages).length > 0) { result.customMessages = combinedCustomMessages; } if (allDependencies.length > 0) { result.dependencies = [...new Set(allDependencies)]; } return result; } /** * Helper function to create a required validation rule */ export function createRequiredRule(errorMessage) { const rule = { type: 'required', required: true, }; if (errorMessage !== undefined) { rule.errorMessage = errorMessage; } return rule; } /** * Helper function to create a length validation rule */ export function createLengthRule(options) { const rule = { type: 'length', }; if (options.minLength !== undefined) rule.minLength = options.minLength; if (options.maxLength !== undefined) rule.maxLength = options.maxLength; if (options.errorMessage !== undefined) rule.errorMessage = options.errorMessage; return rule; } /** * Helper function to create a numeric validation rule */ export function createNumericRule(options) { const rule = { type: 'numeric', }; if (options.min !== undefined) rule.min = options.min; if (options.max !== undefined) rule.max = options.max; if (options.step !== undefined) rule.step = options.step; if (options.decimalPlaces !== undefined) rule.decimalPlaces = options.decimalPlaces; if (options.errorMessage !== undefined) rule.errorMessage = options.errorMessage; return rule; } /** * Helper function to create a pattern validation rule */ export function createPatternRule(pattern, options) { const rule = { type: 'pattern', pattern, }; if (options?.flags !== undefined) rule.flags = options.flags; if (options?.caseSensitive !== undefined) rule.caseSensitive = options.caseSensitive; if (options?.errorMessage !== undefined) rule.errorMessage = options.errorMessage; return rule; } /** * Helper function to create an email validation rule */ export function createEmailRule(options) { const rule = { type: 'email', }; if (options?.allowedDomains !== undefined) rule.allowedDomains = options.allowedDomains; if (options?.blockedDomains !== undefined) rule.blockedDomains = options.blockedDomains; if (options?.requireTLD !== undefined) rule.requireTLD = options.requireTLD; if (options?.errorMessage !== undefined) rule.errorMessage = options.errorMessage; return rule; } /** * Helper function to create a choice validation rule */ export function createChoiceRule(options) { const rule = { type: 'choice', }; if (options.minSelections !== undefined) rule.minSelections = options.minSelections; if (options.maxSelections !== undefined) rule.maxSelections = options.maxSelections; if (options.requiredOptions !== undefined) rule.requiredOptions = options.requiredOptions; if (options.forbiddenCombinations !== undefined) rule.forbiddenCombinations = options.forbiddenCombinations; if (options.errorMessage !== undefined) rule.errorMessage = options.errorMessage; return rule; } // ======================================= // Conditional Logic Type Guards & Utils // ======================================= /** * Type guard to check if an action is a show/hide action */ export function isShowHideAction(action) { return action.action === ConditionalAction.SHOW || action.action === ConditionalAction.HIDE; } /** * Type guard to check if an action is a require action */ export function isRequireAction(action) { return action.action === ConditionalAction.REQUIRE || action.action === ConditionalAction.MAKE_OPTIONAL; } /** * Type guard to check if an action is a jump to action */ export function isJumpToAction(action) { return action.action === ConditionalAction.JUMP_TO; } /** * Type guard to check if an action is a jump to page action */ export function isJumpToPageAction(action) { return action.action === ConditionalAction.JUMP_TO_PAGE; } /** * Type guard to check if an action is a set value action */ export function isSetValueAction(action) { return action.action === ConditionalAction.SET_VALUE; } /** * Type guard to check if an action is a clear value action */ export function isClearValueAction(action) { return action.action === ConditionalAction.CLEAR_VALUE; } /** * Type guard to check if an action is an enable/disable action */ export function isEnableDisableAction(action) { return action.action === ConditionalAction.ENABLE || action.action === ConditionalAction.DISABLE; } /** * Type guard to check if an action is a show message action */ export function isShowMessageAction(action) { return action.action === ConditionalAction.SHOW_MESSAGE; } /** * Type guard to check if an action is a redirect action */ export function isRedirectAction(action) { return action.action === ConditionalAction.REDIRECT; } /** * Type guard to check if an action is a submit form action */ export function isSubmitFormAction(action) { return action.action === ConditionalAction.SUBMIT_FORM; } /** * Type guard to check if an action is a skip action */ export function isSkipAction(action) { return action.action === ConditionalAction.SKIP; } /** * Get all question IDs referenced in a conditional logic rule */ export function getReferencedQuestionIds(logic) { const questionIds = new Set(); function processConditionGroup(group) { group.conditions.forEach(condition => { questionIds.add(condition.questionId); }); group.groups?.forEach(nestedGroup => { processConditionGroup(nestedGroup); }); } function processActions(actions) { actions.forEach(action => { if (isJumpToAction(action)) { questionIds.add(action.targetQuestionId); } else if (isSetValueAction(action) || isClearValueAction(action)) { questionIds.add(action.targetQuestionId); } }); } processConditionGroup(logic.conditionGroup); processActions(logic.actions); if (logic.elseActions) { processActions(logic.elseActions); } return Array.from(questionIds); } /** * Check if a conditional logic rule references a specific question */ export function logicReferencesQuestion(logic, questionId) { return getReferencedQuestionIds(logic).includes(questionId); } /** * Validate that all referenced questions exist in the form */ export function validateLogicReferences(logic, existingQuestionIds) { const referencedIds = getReferencedQuestionIds(logic); const missingQuestions = referencedIds.filter(id => !existingQuestionIds.includes(id)); return { isValid: missingQuestions.length === 0, missingQuestions }; } /** * Create a simple show/hide conditional logic rule */ export function createSimpleShowHideLogic(questionId, triggerQuestionId, operator, value, action = 'show') { return { id: `${action}_${questionId}_when_${triggerQuestionId}_${operator}_${value}`, name: `${action === 'show' ? 'Show' : 'Hide'} ${questionId} when ${triggerQuestionId} ${operator} ${value}`, enabled: true, conditionGroup: { combinator: LogicCombinator.AND, conditions: [ { questionId: triggerQuestionId, operator, value } ] }, actions: [ { action: action === 'show' ? ConditionalAction.SHOW : ConditionalAction.HIDE } ] }; } /** * Create a conditional logic rule for making questions required */ export function createRequiredLogic(questionId, triggerQuestionId, operator, value, validationMessage) { return { id: `require_${questionId}_when_${triggerQuestionId}_${operator}_${value}`, name: `Require ${questionId} when ${triggerQuestionId} ${operator} ${value}`, enabled: true, conditionGroup: { combinator: LogicCombinator.AND, conditions: [ { questionId: triggerQuestionId, operator, value } ] }, actions: [ { action: ConditionalAction.REQUIRE, validationMessage } ] }; } /** * Create a jump logic rule */ export function createJumpLogic(triggerQuestionId, operator, value, targetQuestionId, skipValidation = false) { return { id: `jump_to_${targetQuestionId}_when_${triggerQuestionId}_${operator}_${value}`, name: `Jump to ${targetQuestionId} when ${triggerQuestionId} ${operator} ${value}`, enabled: true, conditionGroup: { combinator: LogicCombinator.AND, conditions: [ { questionId: triggerQuestionId, operator, value } ] }, actions: [ { action: ConditionalAction.JUMP_TO, targetQuestionId, skipValidation } ] }; } // =============================== // Conditional Logic and Branching // =============================== /** * Action types for conditional logic */ export var ConditionalAction; (function (ConditionalAction) { ConditionalAction["SHOW"] = "show"; ConditionalAction["HIDE"] = "hide"; ConditionalAction["REQUIRE"] = "require"; ConditionalAction["MAKE_OPTIONAL"] = "make_optional"; ConditionalAction["SKIP"] = "skip"; ConditionalAction["JUMP_TO"] = "jump_to"; ConditionalAction["JUMP_TO_PAGE"] = "jump_to_page"; ConditionalAction["SUBMIT_FORM"] = "submit_form"; ConditionalAction["SET_VALUE"] = "set_value"; ConditionalAction["CLEAR_VALUE"] = "clear_value"; ConditionalAction["DISABLE"] = "disable"; ConditionalAction["ENABLE"] = "enable"; ConditionalAction["SHOW_MESSAGE"] = "show_message"; ConditionalAction["REDIRECT"] = "redirect"; })(ConditionalAction || (ConditionalAction = {})); /** * Logic combination operators */ export var LogicCombinator; (function (LogicCombinator) { LogicCombinator["AND"] = "and"; LogicCombinator["OR"] = "or"; LogicCombinator["XOR"] = "xor"; LogicCombinator["NAND"] = "nand"; LogicCombinator["NOR"] = "nor"; })(LogicCombinator || (LogicCombinator = {}));

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/learnwithcc/tally-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server