UNPKG

@simpleapps-com/augur-api

Version:

TypeScript client library for Augur microservices API endpoints

153 lines 5.65 kB
import { z } from 'zod'; /** * Flexible Schema Utilities for Handling Inconsistent API Outputs * * During microservice API refactoring, many endpoints return inconsistent formats. * These utilities provide standardized patterns for handling common inconsistencies. */ /** * Creates a schema that accepts both array and object formats * Common pattern: server sometimes returns [] and sometimes {} */ export const flexibleArrayOrObject = (itemSchema) => z.union([ z.array(itemSchema), // Preferred format z.record(z.unknown()), // Legacy/inconsistent format z.record(z.never()).optional(), // Empty object fallback ]); /** * Creates a schema that accepts both array and record formats for collections * Handles cases where APIs return either array of items or object with key-value pairs */ export const flexibleCollection = (itemSchema) => z.union([ z.array(itemSchema), // Standard array format z.record(itemSchema), // Object with typed values z.array(z.unknown()), // Fallback array with unknown items z.record(z.unknown()), // Fallback object with unknown values ]); /** * Creates a schema for fields that might be string, number, or null * Common during API migrations when data types are being standardized */ export const flexibleStringOrNumber = () => z.union([z.string(), z.number(), z.null(), z.undefined()]).transform(val => { if (val === null || val === undefined) return null; return String(val); }); /** * Creates a schema for boolean fields that might come as string, number, or boolean * Handles legacy APIs that return "1"/"0", true/false, or "true"/"false" */ export const flexibleBoolean = () => z.union([z.boolean(), z.string(), z.number(), z.null(), z.undefined()]).transform(val => { if (typeof val === 'boolean') return val; if (typeof val === 'string') return val.toLowerCase() === 'true' || val === '1'; if (typeof val === 'number') return val === 1; return false; }); /** * Creates a schema for date fields that might be string, Date, or null * Handles various date format inconsistencies */ export const flexibleDate = () => z.union([z.string(), z.date(), z.null(), z.undefined()]).transform(val => { if (val === null || val === undefined) return null; if (val instanceof Date) return val.toISOString(); return String(val); }); /** * Creates a flexible schema for profile/metadata fields that vary between endpoints * This is the pattern we found with profileValues - sometimes array, sometimes object */ export const flexibleProfileData = () => z .union([ z.record(z.union([z.string(), z.array(z.string())])), // Object format with string or array values z.array(z.unknown()), // Array format (common case) z.null(), // No profile data z.undefined(), // Field missing ]) .optional(); /** * Creates a flexible schema that gracefully handles unexpected extra fields * Uses passthrough to allow additional properties during API evolution */ export const flexibleObject = (shape) => z.object(shape).passthrough(); /** * Creates a schema that handles nested data that might be normalized or denormalized * Common when APIs are being refactored from flat to nested structures */ export const flexibleNested = (simpleSchema, complexSchema) => z.union([ simpleSchema, // Simple/flat format complexSchema, // Complex/nested format ]); /** * Pre-built schema for common metadata fields that are inconsistent during refactoring * Handles count normalization and bidirectional sync between total and totalResults */ export const flexibleMetadataFields = z .object({ count: z.number().optional().default(0), total: z.number().optional().default(0), totalResults: z.number().optional().default(0), }) .transform(data => { // Apply the same normalization logic as BaseResponseSchema const count = data.count || 0; // Bidirectional sync between total and totalResults let total = data.total; let totalResults = data.totalResults || 0; // If total is 0 and totalResults > 0, set total to totalResults if (total === 0 && totalResults > 0) { total = totalResults; } // If totalResults is 0 and total > 0, set totalResults to total else if (totalResults === 0 && total > 0) { totalResults = total; } return { count, total, totalResults, }; }); /** * Utility for wrapping existing schemas to be more flexible during API refactoring * Adds .catch() to provide fallback values instead of throwing validation errors */ export const makeFlexible = (schema, fallbackValue) => schema.catch(fallbackValue); /** * Common patterns for user-related data that varies across services */ export const flexibleUserFields = { profileValues: flexibleProfileData(), customFields: flexibleArrayOrObject(z.unknown()), permissions: flexibleCollection(z.string()), groups: flexibleArrayOrObject(z .object({ id: z.number(), title: z.string(), }) .passthrough()), }; /** * Common patterns for product/item data that varies across services */ export const flexibleProductFields = { attributes: flexibleCollection(z .object({ attributeId: z.string(), value: z.string(), }) .passthrough()), categories: flexibleArrayOrObject(z .object({ categoryUid: z.number(), categoryDesc: z.string(), }) .passthrough()), metadata: flexibleProfileData(), customData: flexibleArrayOrObject(z.unknown()), }; //# sourceMappingURL=flexible-schemas.js.map