Skip to main content
Glama
validators.ts11.8 kB
/** * Regular expression for validating email addresses. * Checks for basic email format: localpart@domain.tld */ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; /** * Regular expression for validating URLs. * Checks for http:// or https:// protocol with domain and TLD. */ const URL_REGEX = /^https?:\/\/.+\..+$/; /** * Validates an email address format using RFC-compliant regex. * * @param {string} email - The email address to validate * @returns {boolean} True if the email format is valid, false otherwise * @example * ```typescript * isValidEmail('user@example.com'); // true * isValidEmail('invalid.email'); // false * isValidEmail('user@domain'); // false * ``` */ export function isValidEmail(email: string): boolean { return EMAIL_REGEX.test(email); } /** * Validates a URL format, requiring http:// or https:// protocol. * * @param {string} url - The URL to validate * @returns {boolean} True if the URL format is valid, false otherwise * @example * ```typescript * isValidUrl('https://example.com'); // true * isValidUrl('http://example.com/path'); // true * isValidUrl('example.com'); // false * isValidUrl('ftp://example.com'); // false * ``` */ export function isValidUrl(url: string): boolean { return URL_REGEX.test(url); } /** * Validates a date string in ISO 8601 format or any parseable date format. * * @param {string} dateStr - The date string to validate * @returns {boolean} True if the date is valid and parseable, false otherwise * @example * ```typescript * isValidDate('2023-01-15'); // true * isValidDate('2023-01-15T10:30:00Z'); // true * isValidDate('invalid-date'); // false * ``` */ export function isValidDate(dateStr: string): boolean { const date = new Date(dateStr); return !isNaN(date.getTime()); } /** * Validates that a date string represents a date in the past. * Useful for validating birthdates, historical events, etc. * * @param {string} dateStr - The date string to validate * @returns {boolean} True if the date is valid and in the past, false otherwise * @example * ```typescript * isDateInPast('2020-01-01'); // true * isDateInPast('2099-01-01'); // false * isDateInPast('invalid-date'); // false * ``` */ export function isDateInPast(dateStr: string): boolean { if (!isValidDate(dateStr)) { return false; } return new Date(dateStr) < new Date(); } /** * Validates a phone number format (basic check for digit count). * Accepts various international formats with at least 7 digits. * * @param {string} phone - The phone number to validate * @returns {boolean} True if the phone number has 7-15 digits, false otherwise * @example * ```typescript * isValidPhone('+1-555-123-4567'); // true * isValidPhone('555-1234'); // true * isValidPhone('123'); // false (too short) * ``` */ export function isValidPhone(phone: string): boolean { // Basic check: at least 7 digits const digits = phone.replace(/\D/g, ''); return digits.length >= 7 && digits.length <= 15; } /** * Validates that a string is non-empty and within maximum length. * * @param {string} str - The string to validate * @param {number} [maxLength=255] - Maximum allowed length (defaults to 255) * @returns {boolean} True if the string is non-empty and within length limit, false otherwise * @example * ```typescript * isValidString('hello'); // true * isValidString(''); // false * isValidString('a'.repeat(300)); // false (exceeds default 255) * isValidString('hello', 10); // true * ``` */ export function isValidString(str: string, maxLength = 255): boolean { return typeof str === 'string' && str.length > 0 && str.length <= maxLength; } /** * Validates that a number is within a specified range (inclusive). * * @param {number} num - The number to validate * @param {number} min - Minimum allowed value (inclusive) * @param {number} max - Maximum allowed value (inclusive) * @returns {boolean} True if the number is valid and within range, false otherwise * @example * ```typescript * isInRange(50, 0, 100); // true * isInRange(0, 0, 100); // true * isInRange(100, 0, 100); // true * isInRange(150, 0, 100); // false * isInRange(NaN, 0, 100); // false * ``` */ export function isInRange(num: number, min: number, max: number): boolean { return typeof num === 'number' && !isNaN(num) && num >= min && num <= max; } /** * Validates a regular expression pattern string by attempting to compile it. * * @param {string} pattern - The regex pattern to validate * @returns {boolean} True if the pattern is valid regex syntax, false otherwise * @example * ```typescript * isValidRegex('[A-Z]{3}'); // true * isValidRegex('\\d{4}'); // true * isValidRegex('[invalid'); // false * ``` */ export function isValidRegex(pattern: string): boolean { try { new RegExp(pattern); return true; } catch { return false; } } /** * Validates that an array is non-empty. * * @template T - Type of array elements * @param {T[]} arr - The array to validate * @returns {boolean} True if the array exists and has at least one element, false otherwise * @example * ```typescript * isNonEmptyArray([1, 2, 3]); // true * isNonEmptyArray([]); // false * isNonEmptyArray('not an array'); // false * ``` */ export function isNonEmptyArray<T>(arr: T[]): boolean { return Array.isArray(arr) && arr.length > 0; } /** * Validates a complete dataset schema for structural correctness and referential integrity. * Checks entity counts, field definitions, relationship references, and circular dependencies. * * @param {Object} schema - The dataset schema to validate * @param {Record<string, Object>} schema.entities - Map of entity names to definitions * @returns {{ valid: boolean; errors: string[] }} Validation result with list of errors * @example * ```typescript * const schema = { * entities: { * users: { count: 10, type: 'person' }, * orders: { * count: 50, * type: 'custom', * fields: ['amount'], * relationships: { * userId: { references: 'users', type: 'one-to-many' } * } * } * } * }; * const result = validateDatasetSchema(schema); * if (!result.valid) { * console.error('Schema errors:', result.errors); * } * ``` */ export function validateDatasetSchema(schema: { entities: Record< string, { count: number; type: string; fields?: string[]; relationships?: Record< string, { references: string; type: string; nullable?: boolean; } >; } >; }): { valid: boolean; errors: string[] } { const errors: string[] = []; // Check if entities object is empty if (Object.keys(schema.entities).length === 0) { errors.push('Schema must contain at least one entity'); return { valid: false, errors }; } // Validate each entity for (const [entityName, entity] of Object.entries(schema.entities)) { // Validate entity count const countValidation = validateEntityCount(entity.count); if (!countValidation.valid) { errors.push(`Entity '${entityName}': ${countValidation.error}`); } // Validate custom entities have fields if (entity.type === 'custom' && (!entity.fields || entity.fields.length === 0)) { errors.push(`Custom entity '${entityName}' must have fields defined`); } // Validate relationships reference existing entities if (entity.relationships) { for (const [fieldName, relationship] of Object.entries(entity.relationships)) { if (!schema.entities[relationship.references]) { errors.push( `Entity '${entityName}' field '${fieldName}' references non-existent entity '${relationship.references}'` ); } } } } // Check for circular dependencies const circularDeps = detectCircularDependencies(schema); if (circularDeps.length > 0) { errors.push(`Circular dependencies detected: ${circularDeps.join(', ')}`); } return { valid: errors.length === 0, errors, }; } /** * Detects circular dependencies in entity relationships using depth-first search. * Circular dependencies prevent proper entity ordering and must be avoided. * * @param {Object} schema - The dataset schema to analyze * @param {Record<string, Object>} schema.entities - Map of entity names to definitions * @returns {string[]} Array of circular dependency paths (empty if none found) * @example * ```typescript * // Circular dependency: orders -> products -> suppliers -> orders * const schema = { * entities: { * orders: { * relationships: { productId: { references: 'products' } } * }, * products: { * relationships: { supplierId: { references: 'suppliers' } } * }, * suppliers: { * relationships: { defaultOrderId: { references: 'orders' } } * } * } * }; * const cycles = detectCircularDependencies(schema); * // Returns: ['orders -> products -> suppliers -> orders'] * ``` */ export function detectCircularDependencies(schema: { entities: Record< string, { relationships?: Record< string, { references: string; nullable?: boolean; } >; } >; }): string[] { const circularPaths: string[] = []; // Build dependency graph const graph = new Map<string, string[]>(); for (const [entityName, entity] of Object.entries(schema.entities)) { const dependencies: string[] = []; if (entity.relationships) { for (const relationship of Object.values(entity.relationships)) { // Skip self-references if nullable (allowed) if (relationship.references === entityName && relationship.nullable) { continue; } dependencies.push(relationship.references); } } graph.set(entityName, dependencies); } // Detect cycles using DFS const visited = new Set<string>(); const recursionStack = new Set<string>(); function dfs(node: string, path: string[]): boolean { visited.add(node); recursionStack.add(node); path.push(node); const neighbors = graph.get(node) || []; for (const neighbor of neighbors) { if (!visited.has(neighbor)) { if (dfs(neighbor, [...path])) { return true; } } else if (recursionStack.has(neighbor)) { // Found a cycle const cyclePath = [...path, neighbor]; circularPaths.push(cyclePath.join(' -> ')); return true; } } recursionStack.delete(node); return false; } for (const entityName of Object.keys(schema.entities)) { if (!visited.has(entityName)) { dfs(entityName, []); } } return circularPaths; } /** * Validates that an entity count is within allowed range (1-10000). * This limit ensures reasonable memory usage and performance. * * @param {number} count - The entity count to validate * @returns {{ valid: boolean; error?: string }} Validation result with error message if invalid * @example * ```typescript * validateEntityCount(100); // { valid: true } * validateEntityCount(0); // { valid: false, error: 'Count must be at least 1' } * validateEntityCount(15000); // { valid: false, error: 'Count must not exceed 10000' } * validateEntityCount(3.14); // { valid: false, error: 'Count must be an integer' } * ``` */ export function validateEntityCount(count: number): { valid: boolean; error?: string } { if (!Number.isInteger(count)) { return { valid: false, error: 'Count must be an integer' }; } if (count < 1) { return { valid: false, error: 'Count must be at least 1' }; } if (count > 10000) { return { valid: false, error: 'Count must not exceed 10000' }; } return { valid: true }; }

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/funsjanssen/faker-mcp'

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