UNPKG

simple-task-master

Version:
264 lines 10.5 kB
"use strict"; /** * Schema validation for Simple Task Master */ Object.defineProperty(exports, "__esModule", { value: true }); exports.SchemaValidationError = exports.CURRENT_SCHEMA_VERSION = exports.isLockFile = exports.isConfig = exports.isTask = void 0; exports.validateTask = validateTask; exports.validateConfig = validateConfig; exports.validateLockFile = validateLockFile; exports.createDefaultConfig = createDefaultConfig; const types_1 = require("./types"); Object.defineProperty(exports, "isTask", { enumerable: true, get: function () { return types_1.isTask; } }); Object.defineProperty(exports, "isConfig", { enumerable: true, get: function () { return types_1.isConfig; } }); Object.defineProperty(exports, "isLockFile", { enumerable: true, get: function () { return types_1.isLockFile; } }); const constants_1 = require("./constants"); /** * Current schema version */ exports.CURRENT_SCHEMA_VERSION = 1; /** * Error thrown when schema validation fails */ class SchemaValidationError extends Error { field; constructor(message, field) { super(message); this.field = field; this.name = 'SchemaValidationError'; } } exports.SchemaValidationError = SchemaValidationError; /** * Core STM fields with their expected types for validation */ const STM_CORE_FIELDS = { id: 'number', title: 'string', status: 'string', schema: 'number', created: 'string', updated: 'string', tags: 'array', dependencies: 'array' }; /** * Required fields for Task schema */ const REQUIRED_TASK_FIELDS = [ 'schema', 'id', 'title', 'status', 'created', 'updated', 'tags', 'dependencies' ]; /** * Required fields for Config schema */ const REQUIRED_CONFIG_FIELDS = ['schema', 'lockTimeoutMs', 'maxTaskSizeBytes']; /** * Required fields for LockFile schema */ const REQUIRED_LOCK_FIELDS = ['pid', 'command', 'timestamp']; /** * Validates that an object has all required core fields and validates types for known core fields * Unknown fields are preserved without validation */ function validateFields(obj, requiredFields, coreFields, schemaName) { // Check for required core fields for (const field of requiredFields) { if (!(field in obj)) { throw new SchemaValidationError(`Missing required core field '${field}' in ${schemaName}`, field); } } // Validate types for known core fields (unknown fields are preserved) for (const [field, expectedType] of Object.entries(coreFields)) { if (field in obj) { const value = obj[field]; let isValidType = false; switch (expectedType) { case 'number': isValidType = typeof value === 'number'; break; case 'string': isValidType = typeof value === 'string'; break; case 'array': isValidType = Array.isArray(value); break; default: isValidType = true; // Unknown type, skip validation } if (!isValidType) { throw new SchemaValidationError(`Core field '${field}' in ${schemaName} must be of type ${expectedType}, got ${Array.isArray(value) ? 'array' : typeof value}`, field); } } } } /** * Validates a Task object against the schema */ function validateTask(obj) { if (typeof obj !== 'object' || obj === null) { throw new SchemaValidationError('Task must be an object'); } const task = obj; // Validate field count limit const fieldCount = Object.keys(task).length; if (fieldCount > constants_1.FILE_LIMITS.MAX_TOTAL_FIELDS) { throw new SchemaValidationError(`${constants_1.ERROR_MESSAGES.TOO_MANY_FIELDS} (found ${fieldCount} fields)`); } // Validate required core fields and validate types for known core fields validateFields(task, REQUIRED_TASK_FIELDS, STM_CORE_FIELDS, 'Task'); // Validate schema version if (task.schema !== exports.CURRENT_SCHEMA_VERSION) { throw new SchemaValidationError(`Unsupported schema version ${task.schema}. Expected version ${exports.CURRENT_SCHEMA_VERSION}`, 'schema'); } // Validate field types if (!(0, types_1.isTask)(task)) { // Provide specific error messages for type mismatches if (typeof task.id !== 'number') { throw new SchemaValidationError('Task id must be a number', 'id'); } if (typeof task.title !== 'string') { throw new SchemaValidationError('Task title must be a string', 'title'); } if (typeof task.status !== 'string' || !['pending', 'in-progress', 'done'].includes(task.status)) { throw new SchemaValidationError('Task status must be one of: pending, in-progress, done', 'status'); } if (typeof task.created !== 'string') { throw new SchemaValidationError('Task created must be an ISO 8601 string', 'created'); } if (typeof task.updated !== 'string') { throw new SchemaValidationError('Task updated must be an ISO 8601 string', 'updated'); } if (!Array.isArray(task.tags)) { throw new SchemaValidationError('Task tags must be an array', 'tags'); } if (!task.tags.every((tag) => typeof tag === 'string')) { throw new SchemaValidationError('All task tags must be strings', 'tags'); } if (!Array.isArray(task.dependencies)) { throw new SchemaValidationError('Task dependencies must be an array', 'dependencies'); } if (!task.dependencies.every((dep) => typeof dep === 'number')) { throw new SchemaValidationError('All task dependencies must be numbers', 'dependencies'); } // Generic fallback (shouldn't reach here) throw new SchemaValidationError('Invalid task object'); } // Validate ISO 8601 timestamps validateISO8601(task.created, 'created'); validateISO8601(task.updated, 'updated'); return task; } /** * Validates a Config object against the schema */ function validateConfig(obj) { if (typeof obj !== 'object' || obj === null) { throw new SchemaValidationError('Config must be an object'); } const config = obj; // Validate required fields (Config validation unchanged - still strict) validateFields(config, REQUIRED_CONFIG_FIELDS, {}, 'Config'); // Additional validation for unknown fields in Config (maintain strict validation) const allowedConfigFields = new Set(REQUIRED_CONFIG_FIELDS); for (const field of Object.keys(config)) { if (!allowedConfigFields.has(field)) { throw new SchemaValidationError(`Unknown field '${field}' in Config. Allowed fields: ${Array.from(allowedConfigFields).join(', ')}`, field); } } // Validate schema version if (config.schema !== exports.CURRENT_SCHEMA_VERSION) { throw new SchemaValidationError(`Unsupported schema version ${config.schema}. Expected version ${exports.CURRENT_SCHEMA_VERSION}`, 'schema'); } // Validate field types if (!(0, types_1.isConfig)(config)) { if (typeof config.lockTimeoutMs !== 'number') { throw new SchemaValidationError('Config lockTimeoutMs must be a number', 'lockTimeoutMs'); } if (typeof config.maxTaskSizeBytes !== 'number') { throw new SchemaValidationError('Config maxTaskSizeBytes must be a number', 'maxTaskSizeBytes'); } // Generic fallback throw new SchemaValidationError('Invalid config object'); } // Validate ranges if (config.lockTimeoutMs <= 0) { throw new SchemaValidationError('Config lockTimeoutMs must be positive', 'lockTimeoutMs'); } if (config.maxTaskSizeBytes <= 0) { throw new SchemaValidationError('Config maxTaskSizeBytes must be positive', 'maxTaskSizeBytes'); } return config; } /** * Validates a LockFile object against the schema */ function validateLockFile(obj) { if (typeof obj !== 'object' || obj === null) { throw new SchemaValidationError('LockFile must be an object'); } const lock = obj; // Validate required fields (LockFile validation unchanged - still strict) validateFields(lock, REQUIRED_LOCK_FIELDS, {}, 'LockFile'); // Additional validation for unknown fields in LockFile (maintain strict validation) const allowedLockFields = new Set(REQUIRED_LOCK_FIELDS); for (const field of Object.keys(lock)) { if (!allowedLockFields.has(field)) { throw new SchemaValidationError(`Unknown field '${field}' in LockFile. Allowed fields: ${Array.from(allowedLockFields).join(', ')}`, field); } } // Validate field types if (!(0, types_1.isLockFile)(lock)) { if (typeof lock.pid !== 'number') { throw new SchemaValidationError('LockFile pid must be a number', 'pid'); } if (typeof lock.command !== 'string') { throw new SchemaValidationError('LockFile command must be a string', 'command'); } if (typeof lock.timestamp !== 'number') { throw new SchemaValidationError('LockFile timestamp must be a number', 'timestamp'); } // Generic fallback throw new SchemaValidationError('Invalid lock file object'); } // Validate ranges if (lock.pid <= 0) { throw new SchemaValidationError('LockFile pid must be positive', 'pid'); } if (lock.timestamp <= 0) { throw new SchemaValidationError('LockFile timestamp must be positive', 'timestamp'); } return lock; } /** * Validates an ISO 8601 timestamp string */ function validateISO8601(timestamp, field) { const iso8601Regex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/; if (!iso8601Regex.test(timestamp)) { throw new SchemaValidationError(`Invalid ISO 8601 timestamp in field '${field}': ${timestamp}`, field); } // Verify it's a valid date const date = new Date(timestamp); if (isNaN(date.getTime())) { throw new SchemaValidationError(`Invalid date in field '${field}': ${timestamp}`, field); } } /** * Creates a default Config object */ function createDefaultConfig() { return { schema: exports.CURRENT_SCHEMA_VERSION, lockTimeoutMs: 30000, maxTaskSizeBytes: 1048576 }; } //# sourceMappingURL=schema.js.map