UNPKG

@pawel-up/jexl

Version:

Javascript Expression Language: Powerful context-based expression parser and evaluator

317 lines (276 loc) 8.77 kB
/** * Utility functions for working with Jexl function schemas * These helpers can be used across all function libraries */ import type { JSONSchema7, JSONSchema7Definition } from 'json-schema' import { type FunctionSchema, type FunctionParameter, type LibrarySchema, type LibraryConfig, JSON_SCHEMA_VERSION, DEFAULT_BASE_URL, } from './types.js' /** * Creates a complete library schema from function schemas and config */ export function createLibrarySchema( functionSchemas: Record<string, FunctionSchema>, config: LibraryConfig ): LibrarySchema { const baseUrl = config.baseUrl || DEFAULT_BASE_URL return { $schema: JSON_SCHEMA_VERSION, $id: `${baseUrl}/${config.category}.schema.json`, title: config.title, description: config.description, version: config.version, functions: functionSchemas, } } /** * Creates a function parameter definition with JSON Schema */ export function createParameter( name: string, schema: JSONSchema7, required = true, options: { variadic?: boolean } = {} ): FunctionParameter { return { name, schema, required, ...options, } } /** * Creates a function schema with JSON Schema for returns */ export function createFunctionSchema( name: string, description: string, category: string, parameters: FunctionParameter[], returns: JSONSchema7, options: { examples?: string[] since?: string deprecated?: boolean | string tags?: string[] } = {} ): FunctionSchema { return { name, description, category, parameters, returns, ...options, } } /** * Gets a specific function schema by name */ export function getFunctionSchema(library: LibrarySchema, functionName: string): FunctionSchema | undefined { return library.functions[functionName] } /** * Gets all function names from a library */ export function getAllFunctionNames(library: LibrarySchema): string[] { return Object.keys(library.functions) } /** * Gets functions by category */ export function getFunctionsByCategory(library: LibrarySchema, category: string): Record<string, FunctionSchema> { return Object.fromEntries(Object.entries(library.functions).filter(([, schema]) => schema.category === category)) } /** * Gets functions by tag */ export function getFunctionsByTag(library: LibrarySchema, tag: string): Record<string, FunctionSchema> { return Object.fromEntries(Object.entries(library.functions).filter(([, schema]) => schema.tags?.includes(tag))) } /** * Searches functions by name or description */ export function searchFunctions(library: LibrarySchema, query: string): Record<string, FunctionSchema> { const lowerQuery = query.toLowerCase() return Object.fromEntries( Object.entries(library.functions).filter( ([name, schema]) => name.toLowerCase().includes(lowerQuery) || schema.description.toLowerCase().includes(lowerQuery) ) ) } /** * Converts library schema to JSON string */ export function toJSON(library: LibrarySchema, pretty = true): string { return JSON.stringify(library, null, pretty ? 2 : 0) } /** * Merges multiple library schemas into one */ export function mergeLibrarySchemas(libraries: LibrarySchema[], config: LibraryConfig): LibrarySchema { const mergedFunctions = libraries.reduce((acc, lib) => ({ ...acc, ...lib.functions }), {}) return createLibrarySchema(mergedFunctions, config) } /** * Validates that a function schema has all required fields */ export function validateFunctionSchema(schema: FunctionSchema): string[] { const errors: string[] = [] if (!schema.name) errors.push('Function name is required') if (!schema.description) errors.push('Function description is required') if (!schema.category) errors.push('Function category is required') if (!schema.parameters) errors.push('Function parameters array is required') if (!schema.returns) errors.push('Function returns definition is required') // Validate parameters schema.parameters?.forEach((param, index) => { if (!param.name) errors.push(`Parameter ${index}: name is required`) if (!param.schema) { errors.push(`Parameter ${index} (${param.name}): schema is required`) } else { if (getTypeStringFromSchema(param.schema) === 'unknown') { errors.push( `Parameter ${index} (${param.name}): schema is missing a valid type definition (e.g., type, enum, anyOf)` ) } if (!param.schema.description) { errors.push(`Parameter ${index}: schema.description is required`) } } if (param.required === undefined) errors.push(`Parameter ${index}: required field is required`) }) // Validate return if (schema.returns && getTypeStringFromSchema(schema.returns) === 'unknown') { errors.push('Return schema is missing a valid type definition (e.g., type, enum, anyOf)') } return errors } /** * Extracts a readable type string from a JSON Schema */ export function getTypeStringFromSchema(schema: JSONSchema7Definition): string { if (typeof schema === 'boolean') { return schema ? 'any' : 'never' } if (schema.type) { if (Array.isArray(schema.type)) { return schema.type.join(' | ') } return schema.type } if (schema.enum) { return schema.enum.map((v) => (typeof v === 'string' ? `"${v}"` : String(v))).join(' | ') } if (schema.anyOf) { return schema.anyOf .map((s) => (typeof s === 'object' && s !== null && !Array.isArray(s) ? getTypeStringFromSchema(s) : 'unknown')) .join(' | ') } if (schema.oneOf) { return schema.oneOf .map((s) => (typeof s === 'object' && s !== null && !Array.isArray(s) ? getTypeStringFromSchema(s) : 'unknown')) .join(' | ') } return 'unknown' } /** * Generates a function signature string for display */ export function generateSignature(schema: FunctionSchema): string { const params = schema.parameters .map((p) => { const optional = p.required ? '' : '?' const variadic = p.variadic ? '...' : '' const typeStr = getTypeStringFromSchema(p.schema) return `${variadic}${p.name}${optional}: ${typeStr}` }) .join(', ') const returnType = getTypeStringFromSchema(schema.returns) return `${schema.name}(${params}) → ${returnType}` } /** * Generates markdown documentation for a function */ export function generateFunctionMarkdown(schema: FunctionSchema): string { const lines: string[] = [] lines.push(`### ${schema.name}`) lines.push('') lines.push(schema.description) lines.push('') if (schema.deprecated) { lines.push('> ⚠️ **Deprecated** - This function is deprecated and may be removed in future versions.') lines.push('') } lines.push('**Signature:**') lines.push(`\`${generateSignature(schema)}\``) lines.push('') if (schema.parameters.length > 0) { lines.push('**Parameters:**') lines.push('') schema.parameters.forEach((param) => { const required = param.required ? '' : ' (optional)' const variadic = param.variadic ? ' (variadic)' : '' const typeStr = getTypeStringFromSchema(param.schema) lines.push( `- \`${param.name}\` (${typeStr}${required}${variadic}) - ${param.schema?.description || 'No description provided'}` ) }) lines.push('') } const returnType = getTypeStringFromSchema(schema.returns) const returnDescription = schema.returns.description || 'Return value' lines.push(`**Returns:** ${returnType} - ${returnDescription}`) lines.push('') if (schema.examples && schema.examples.length > 0) { lines.push('**Examples:**') lines.push('') schema.examples.forEach((example) => { lines.push(`\`\`\`typescript`) lines.push(example) lines.push(`\`\`\``) lines.push('') }) } if (schema.since) { lines.push(`*Since: ${schema.since}*`) lines.push('') } return lines.join('\n') } /** * Generates complete markdown documentation for a library */ export function generateLibraryMarkdown(library: LibrarySchema): string { const functions = Object.values(library.functions) const lines: string[] = [] lines.push(`# ${library.title}`) lines.push('') lines.push(library.description) lines.push('') lines.push(`**Version:** ${library.version}`) lines.push('') lines.push(`**Functions:** ${functions.length}`) lines.push('') // Table of contents lines.push('## Table of Contents') lines.push('') functions.forEach((func) => { lines.push(`- [${func.name}](#${func.name.toLowerCase()})`) }) lines.push('') // Function documentation lines.push('## Functions') lines.push('') functions.forEach((func) => { lines.push(generateFunctionMarkdown(func)) }) return lines.join('\n') }