Skip to main content
Glama
base-schemas.ts11.9 kB
/** * Base Schema Definitions - Reusable Zod field patterns * * This module provides centralized, reusable schema definitions for common * patterns across the RPG-MCP schema system. Using these reduces duplication, * ensures consistency, and makes it easier to update validation rules globally. * * USAGE: * ```typescript * import { IdField, TimestampFields, GridCoordinates } from './base-schemas.js'; * * const MySchema = z.object({ * id: IdField, * ...TimestampFields, * position: GridCoordinates, * }); * ``` * * @module schema/base-schemas */ import { z } from 'zod'; // ============================================================================ // IDENTIFIERS // ============================================================================ /** * Standard string ID field (for entities that use string IDs) */ export const IdField = z.string().min(1, 'ID cannot be empty'); /** * UUID field (for entities that use UUIDs) */ export const UuidField = z.string().uuid('Must be a valid UUID'); /** * World ID reference field */ export const WorldIdField = z.string().describe('World this entity belongs to'); /** * Region ID reference field (optional for entities that may not be region-linked) */ export const RegionIdField = z.string().optional().describe('Region containing this entity'); /** * Character ID reference field */ export const CharacterIdField = z.string().describe('Character ID reference'); // ============================================================================ // TIMESTAMPS // ============================================================================ /** * ISO 8601 datetime field */ export const DateTimeField = z.string().datetime(); /** * Spread into object schemas that need createdAt/updatedAt * * @example * const MySchema = z.object({ * id: IdField, * name: z.string(), * ...TimestampFields * }); */ export const TimestampFields = { createdAt: DateTimeField, updatedAt: DateTimeField, } as const; /** * Optional lastVisitedAt timestamp for trackable entities */ export const LastVisitedField = z.string().datetime().optional(); // ============================================================================ // COORDINATES & SPATIAL // ============================================================================ /** * World grid X coordinate (0 to world width) */ export const GridXField = z.number().int().min(0).describe('World grid X coordinate'); /** * World grid Y coordinate (0 to world height) */ export const GridYField = z.number().int().min(0).describe('World grid Y coordinate'); /** * Standard 2D grid coordinates */ export const GridCoordinates = z.object({ x: GridXField, y: GridYField, }); /** * Combat/tactical position (allows floating point for precise positioning) */ export const TacticalPosition = z.object({ x: z.number(), y: z.number(), z: z.number().optional(), }); export type TacticalPositionType = z.infer<typeof TacticalPosition>; /** * Bounding box for spatial queries */ export const BoundingBox = z.object({ minX: z.number().int().min(0), maxX: z.number().int().min(0), minY: z.number().int().min(0), maxY: z.number().int().min(0), }); export type BoundingBoxType = z.infer<typeof BoundingBox>; // ============================================================================ // NAMES & TEXT FIELDS // ============================================================================ /** * Entity name field with validation */ export const NameField = z.string() .min(1, 'Name cannot be empty') .max(100, 'Name cannot exceed 100 characters') .refine(s => s.trim().length > 0, 'Name cannot be whitespace only'); /** * Short description field (for tooltips, summaries) */ export const ShortDescriptionField = z.string() .max(500, 'Description too long') .optional(); /** * Long description field (for detailed content) */ export const LongDescriptionField = z.string() .min(10, 'Description must be detailed') .max(2000, 'Description too long') .refine(s => s.trim().length >= 10, 'Description must have at least 10 non-whitespace characters'); // ============================================================================ // NUMERIC FIELDS // ============================================================================ /** * Non-negative integer (0+) */ export const NonNegativeInt = z.number().int().min(0); /** * Positive integer (1+) */ export const PositiveInt = z.number().int().min(1); /** * Population field for settlements */ export const PopulationField = z.number().int().min(0).describe('Population count'); /** * Character level (1-20 for standard D&D) */ export const LevelField = z.number().int().min(1).max(20); /** * Difficulty Class field (5-30 standard range) */ export const DCField = z.number().int().min(5).max(30); /** * HP field (0+) */ export const HpField = z.number().int().min(0); /** * AC field (0+) */ export const AcField = z.number().int().min(0); /** * Percentage field (0-1 as decimal) */ export const PercentageField = z.number().min(0).max(1); /** * Percentage field (0-100 as integer) */ export const PercentageInt = z.number().int().min(0).max(100); // ============================================================================ // D&D ABILITY SCORES // ============================================================================ /** * Single ability score (typically 1-30) */ export const AbilityScoreField = z.number().int().min(0).max(30); /** * Standard D&D ability score object */ export const AbilityScores = z.object({ str: AbilityScoreField, dex: AbilityScoreField, con: AbilityScoreField, int: AbilityScoreField, wis: AbilityScoreField, cha: AbilityScoreField, }); export type AbilityScoresType = z.infer<typeof AbilityScores>; /** * Save proficiency enum */ export const SaveProficiencyEnum = z.enum(['str', 'dex', 'con', 'int', 'wis', 'cha']); /** * Skill proficiency enum */ export const SkillProficiencyEnum = z.enum([ 'acrobatics', 'animal_handling', 'arcana', 'athletics', 'deception', 'history', 'insight', 'intimidation', 'investigation', 'medicine', 'nature', 'perception', 'performance', 'persuasion', 'religion', 'sleight_of_hand', 'stealth', 'survival' ]); // ============================================================================ // SIZE CATEGORIES // ============================================================================ /** * D&D 5e size categories * Note: Named BaseSizeCategory to avoid conflict with encounter.ts SizeCategory */ export const BaseSizeCategory = z.enum([ 'tiny', // 2.5ft, shares space 'small', // 5ft, 1 square 'medium', // 5ft, 1 square 'large', // 10ft, 2x2 squares 'huge', // 15ft, 3x3 squares 'gargantuan' // 20ft+, 4x4+ squares ]); export type BaseSizeCategoryType = z.infer<typeof BaseSizeCategory>; // ============================================================================ // DAMAGE & EFFECTS // ============================================================================ /** * Standard damage types in D&D 5e * Note: Named BaseDamageType to avoid conflict with spell.ts DamageType */ export const BaseDamageTypeEnum = z.enum([ 'slashing', 'piercing', 'bludgeoning', 'fire', 'cold', 'lightning', 'thunder', 'acid', 'poison', 'necrotic', 'radiant', 'psychic', 'force' ]); export type BaseDamageType = z.infer<typeof BaseDamageTypeEnum>; /** * Array of damage types (for resistances, immunities, vulnerabilities) */ export const BaseDamageTypeArray = z.array(BaseDamageTypeEnum).default([]); /** * Condition type enum */ export const ConditionTypeEnum = z.enum([ 'blinded', 'charmed', 'deafened', 'frightened', 'grappled', 'incapacitated', 'invisible', 'paralyzed', 'petrified', 'poisoned', 'prone', 'restrained', 'stunned', 'unconscious', 'exhaustion' ]); export type ConditionType = z.infer<typeof ConditionTypeEnum>; // ============================================================================ // CURRENCY // ============================================================================ /** * Standard D&D currency object */ export const CurrencyFields = z.object({ gold: z.number().int().min(0).default(0), silver: z.number().int().min(0).default(0), copper: z.number().int().min(0).default(0), }).default({}); export type CurrencyType = z.infer<typeof CurrencyFields>; // ============================================================================ // COMBAT & ENCOUNTER // ============================================================================ /** * Encounter status enum */ export const EncounterStatusEnum = z.enum(['active', 'completed', 'paused']); /** * Movement speed field (in feet, default 30) */ export const MovementSpeedField = z.number().int().min(0).default(30); /** * Initiative bonus field */ export const InitiativeBonusField = z.number().int(); // ============================================================================ // DISCOVERY & VISIBILITY // ============================================================================ /** * Discovery state for POIs and secrets */ export const DiscoveryStateEnum = z.enum([ 'unknown', // Not yet discovered 'rumored', // Heard about but not visited 'discovered', // Visited at least once 'explored', // Fully explored 'mapped' // Detailed notes created ]); export type DiscoveryState = z.infer<typeof DiscoveryStateEnum>; // ============================================================================ // EXIT & CONNECTION TYPES // ============================================================================ /** * Cardinal and vertical directions */ export const DirectionEnum = z.enum([ 'north', 'south', 'east', 'west', 'up', 'down', 'northeast', 'northwest', 'southeast', 'southwest' ]); export type Direction = z.infer<typeof DirectionEnum>; /** * Exit/door types */ export const ExitTypeEnum = z.enum(['OPEN', 'LOCKED', 'HIDDEN']); /** * Cover types for combat props */ export const CoverTypeEnum = z.enum(['none', 'half', 'three_quarter', 'full']); // ============================================================================ // HELPER FUNCTIONS // ============================================================================ /** * Create a standard entity schema with ID and timestamps * @param fields - Additional fields for the entity * @returns Combined Zod object schema * * @example * const MyEntitySchema = createEntitySchema({ * name: NameField, * value: NonNegativeInt, * }); */ export function createEntitySchema<T extends z.ZodRawShape>(fields: T) { return z.object({ id: IdField, ...fields, ...TimestampFields, }); } /** * Create a standard entity schema with UUID and timestamps * @param fields - Additional fields for the entity * @returns Combined Zod object schema */ export function createUuidEntitySchema<T extends z.ZodRawShape>(fields: T) { return z.object({ id: UuidField, ...fields, ...TimestampFields, }); } /** * Create a world-linked entity schema with worldId, optional regionId, coordinates * @param fields - Additional fields for the entity * @returns Combined Zod object schema * * @example * const StructureSchema = createWorldEntitySchema({ * name: NameField, * type: z.enum(['city', 'town', 'village']), * population: PopulationField, * }); */ export function createWorldEntitySchema<T extends z.ZodRawShape>(fields: T) { return z.object({ id: IdField, worldId: WorldIdField, regionId: RegionIdField, x: GridXField, y: GridYField, ...fields, ...TimestampFields, }); }

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/Mnehmos/rpg-mcp'

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