UNPKG

@gati-framework/runtime

Version:

Gati runtime execution engine for running handler-based applications

243 lines 7.67 kB
/** * Version resolver for extracting and resolving versions from requests */ export class VersionResolver { registry; cache = new Map(); maxCacheSize = 1000; constructor(registry) { this.registry = registry; } /** * Extract version from query parameters */ extractFromQuery(query) { const version = query.version || query.v; if (!version) { return null; } const extracted = Array.isArray(version) ? version[0] : version; // Return null for empty strings return extracted && extracted.trim() !== '' ? extracted : null; } /** * Extract version from headers */ extractFromHeaders(headers) { const version = headers['x-gati-version'] || headers['x-api-version']; if (!version) { return null; } const extracted = Array.isArray(version) ? version[0] : version; // Return null for empty strings return extracted && extracted.trim() !== '' ? extracted : null; } /** * Parse timestamp from various formats * Returns timestamp in SECONDS (to match TSV format) */ parseTimestamp(value) { // ISO 8601 format: 2025-11-21T10:30:00Z if (value.includes('T')) { const date = new Date(value); return isNaN(date.getTime()) ? null : Math.floor(date.getTime() / 1000); } // Unix timestamp (seconds) if (/^\d{10}$/.test(value)) { return parseInt(value, 10); } // Unix timestamp (milliseconds) - convert to seconds if (/^\d{13}$/.test(value)) { return Math.floor(parseInt(value, 10) / 1000); } return null; } /** * Resolve version for a handler */ resolveVersion(handlerPath, query, headers) { // Try cache first const cacheKey = this.makeCacheKey(handlerPath, query, headers); const cached = this.cache.get(cacheKey); if (cached) { return { ...cached, cached: true }; } // 1. Try query parameter const queryVersion = this.extractFromQuery(query); if (queryVersion) { const result = this.resolveVersionString(handlerPath, queryVersion, 'query'); if ('code' in result) { return result; } this.cacheResult(cacheKey, result); return result; } // 2. Try header const headerVersion = this.extractFromHeaders(headers); if (headerVersion) { const result = this.resolveVersionString(handlerPath, headerVersion, 'header'); if ('code' in result) { return result; } this.cacheResult(cacheKey, result); return result; } // 3. Default to latest const latest = this.registry.getLatestVersion(handlerPath); if (!latest) { return { code: 'VERSION_NOT_FOUND', message: `No versions found for handler: ${handlerPath}`, }; } const result = { version: latest, source: 'latest', cached: false, }; this.cacheResult(cacheKey, result); return result; } /** * Resolve version string to TSV */ resolveVersionString(handlerPath, versionString, source) { // Handle empty string if (!versionString || versionString.trim() === '') { return { code: 'INVALID_FORMAT', message: 'Version string cannot be empty', details: { versionString }, }; } // Try as semantic version tag if (!versionString.includes('T') && !versionString.startsWith('tsv:')) { const tsv = this.registry.getVersionByTag(handlerPath, versionString); if (tsv) { return { version: tsv, source: 'tag', cached: false, }; } } // Try as timestamp if (versionString.includes('T') || /^\d{10,13}$/.test(versionString)) { const timestamp = this.parseTimestamp(versionString); if (!timestamp) { return { code: 'INVALID_TIMESTAMP', message: `Invalid timestamp format: ${versionString}`, details: { versionString }, }; } const tsv = this.registry.getVersionAt(handlerPath, timestamp); if (!tsv) { return { code: 'VERSION_NOT_FOUND', message: `No version found at timestamp: ${versionString}`, details: { timestamp, handlerPath }, }; } return { version: tsv, source: 'timestamp', cached: false, }; } // Try as direct TSV if (versionString.startsWith('tsv:')) { const info = this.registry.getVersionInfo(versionString); if (!info) { return { code: 'VERSION_NOT_FOUND', message: `Version not found: ${versionString}`, details: { versionString }, }; } return { version: versionString, source, cached: false, }; } // Invalid format return { code: 'INVALID_FORMAT', message: `Invalid version format: ${versionString}`, details: { versionString }, }; } /** * Validate version format */ validateVersionFormat(version) { // TSV format if (version.startsWith('tsv:')) { return /^tsv:\d+-[a-zA-Z0-9_-]+-\d+$/.test(version); } // Timestamp format if (version.includes('T') || /^\d{10,13}$/.test(version)) { return this.parseTimestamp(version) !== null; } // Semantic version or tag (any non-empty string) return version.length > 0; } /** * Clear version cache */ clearCache() { this.cache.clear(); } /** * Get cache statistics */ getCacheStats() { return { size: this.cache.size, maxSize: this.maxCacheSize, }; } /** * Create cache key */ makeCacheKey(handlerPath, query, headers) { const queryVersion = this.extractFromQuery(query); const headerVersion = this.extractFromHeaders(headers); return `${handlerPath}:${queryVersion || ''}:${headerVersion || ''}`; } /** * Cache resolution result */ cacheResult(key, result) { if (this.cache.size >= this.maxCacheSize) { const firstKey = this.cache.keys().next().value; if (firstKey) { this.cache.delete(firstKey); } } this.cache.set(key, result); } } /** * Legacy resolver for backward compatibility */ export class ExecutionContextResolver { registry; constructor(registry) { this.registry = registry; } /** * Resolves the complete version state for a given context. */ resolve(_context) { return this.registry.getAll(); } /** * Resolves a specific artifact version for the current context. */ resolveArtifact(type, id, _context) { return this.registry.get(type, id); } } //# sourceMappingURL=resolver.js.map