UNPKG

fortify-schema

Version:

A modern TypeScript validation library designed around familiar interface syntax and powerful conditional validation. Experience schema validation that feels natural to TypeScript developers while unlocking advanced runtime validation capabilities.

225 lines (193 loc) 5.79 kB
/** * Smart Schema Inference - TypeScript type-to-schema conversion * * This module provides automatic schema generation from TypeScript types, * making schema definition even more seamless. */ import { SchemaInterface } from "../mode/interfaces/Interface"; /** * Smart inference utilities for automatic schema generation */ export const Smart = { /** * Infer schema from TypeScript interface using runtime reflection * * @example * ```typescript * interface User { * id: number; * email: string; * name?: string; * } * * // Use with sample data that matches your interface * const UserSchema = Smart.fromType<User>({ * id: 1, * email: "user@example.com", * name: "John Doe" * }); * // Generates: Interface({ id: "positive", email: "email", name: "string?" }) * ``` */ fromType<T>(sampleData: T): SchemaInterface { if (!sampleData || typeof sampleData !== "object") { throw new Error( "Smart.fromType() requires sample data that matches your TypeScript interface" ); } return Smart.fromSample(sampleData); }, /** * Infer schema from sample data with intelligent type detection * * @example * ```typescript * const sampleUser = { * id: 1, * email: "user@example.com", * name: "John Doe", * tags: ["developer", "typescript"] * }; * * const UserSchema = Smart.fromSample(sampleUser); * // Generates: Interface({ id: "positive", email: "email", name: "string", tags: "string[]" }) * ``` */ fromSample(sample: any): SchemaInterface { const schema: any = {}; for (const [key, value] of Object.entries(sample)) { schema[key] = Smart.inferFieldType(value); } return schema; }, /** * Infer field type from value with smart detection */ inferFieldType(value: any): string { if (value === null || value === undefined) { return "any?"; } if (typeof value === "string") { // Smart email detection if (Smart.isEmail(value)) return "email"; // Smart URL detection if (Smart.isUrl(value)) return "url"; // Smart UUID detection if (Smart.isUuid(value)) return "uuid"; // Smart phone detection if (Smart.isPhone(value)) return "phone"; return "string"; } if (typeof value === "number") { // Smart positive number detection if (value > 0 && Number.isInteger(value)) return "positive"; if (Number.isInteger(value)) return "int"; return "number"; } if (typeof value === "boolean") { return "boolean"; } if (value instanceof Date) { return "date"; } if (Array.isArray(value)) { if (value.length === 0) return "any[]"; // Detect array element type from first element const elementType = Smart.inferFieldType(value[0]); const baseType = elementType.replace("?", ""); // Remove optional marker return `${baseType}[]`; } if (typeof value === "object") { // Nested object - recursively infer const nestedSchema: any = {}; for (const [nestedKey, nestedValue] of Object.entries(value)) { nestedSchema[nestedKey] = Smart.inferFieldType(nestedValue); } return nestedSchema; } return "any"; }, /** * Smart format detection utilities */ isEmail(str: string): boolean { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str); }, isUrl(str: string): boolean { try { new URL(str); return true; } catch { return false; } }, isUuid(str: string): boolean { return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test( str ); }, isPhone(str: string): boolean { return /^\+?[1-9]\d{1,14}$/.test(str.replace(/[\s\-\(\)]/g, "")); }, /** * Generate schema from JSON Schema (migration helper) * * @example * ```typescript * const jsonSchema = { * type: "object", * properties: { * id: { type: "number" }, * email: { type: "string", format: "email" } * } * }; * * const schema = Smart.fromJsonSchema(jsonSchema); * ``` */ fromJsonSchema(jsonSchema: any): SchemaInterface { if (jsonSchema.type === "object" && jsonSchema.properties) { const schema: any = {}; for (const [key, prop] of Object.entries(jsonSchema.properties as any)) { schema[key] = Smart.convertJsonSchemaProperty(prop); } return schema; } throw new Error("Unsupported JSON Schema format"); }, convertJsonSchemaProperty(prop: any): string { const isOptional = !prop.required; const suffix = isOptional ? "?" : ""; switch (prop.type) { case "string": if (prop.format === "email") return `email${suffix}`; if (prop.format === "uri") return `url${suffix}`; if (prop.format === "uuid") return `uuid${suffix}`; if (prop.minLength && prop.maxLength) { return `string(${prop.minLength},${prop.maxLength})${suffix}`; } return `string${suffix}`; case "number": case "integer": if (prop.minimum && prop.maximum) { return `number(${prop.minimum},${prop.maximum})${suffix}`; } if (prop.minimum > 0) return `positive${suffix}`; return prop.type === "integer" ? `int${suffix}` : `number${suffix}`; case "boolean": return `boolean${suffix}`; case "array": const itemType = Smart.convertJsonSchemaProperty( prop.items || { type: "any" } ); const baseType = itemType.replace("?", ""); return `${baseType}[]${suffix}`; default: return `any${suffix}`; } }, }; /** * Export for easy access */ export default Smart;