Files
claude-task-master/packages/tm-core/src/utils/run-id-generator.ts
Ralph Khreish ccb87a516a feat: implement tdd workflow (#1309)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-10-18 16:29:03 +02:00

130 lines
3.9 KiB
TypeScript

/**
* Run ID generation and validation utilities for the global storage system.
* Uses ISO 8601 timestamps with millisecond precision for unique, chronologically-ordered run IDs.
*
* @module run-id-generator
*/
// Collision detection state
let lastTimestamp = 0;
let counter = 0;
/**
* Generates a unique run ID using ISO 8601 timestamp format with millisecond precision.
* The ID is guaranteed to be chronologically sortable and URL-safe.
* Includes collision detection to ensure uniqueness even when called in rapid succession.
*
* @param {Date} [date=new Date()] - Optional date to use for the run ID. Defaults to current time.
* @returns {string} ISO 8601 formatted timestamp (e.g., '2024-01-15T10:30:45.123Z')
*
* @example
* generateRunId() // returns '2024-01-15T10:30:45.123Z'
* generateRunId(new Date('2024-01-15T10:00:00.000Z')) // returns '2024-01-15T10:00:00.000Z'
*/
export function generateRunId(date: Date = new Date()): string {
const timestamp = date.getTime();
// Collision detection: if same millisecond, wait for next millisecond
if (timestamp === lastTimestamp) {
counter++;
// Wait for next millisecond to ensure uniqueness
let newTimestamp = timestamp;
while (newTimestamp === timestamp) {
newTimestamp = Date.now();
}
date = new Date(newTimestamp);
lastTimestamp = newTimestamp;
counter = 0;
} else {
lastTimestamp = timestamp;
counter = 0;
}
return date.toISOString();
}
/**
* Validates whether a string is a valid run ID.
* A valid run ID must be:
* - In ISO 8601 format with milliseconds
* - In UTC timezone (ends with 'Z')
* - A valid date when parsed
*
* @param {any} runId - The value to validate
* @returns {boolean} True if the value is a valid run ID
*
* @example
* isValidRunId('2024-01-15T10:30:45.123Z') // returns true
* isValidRunId('invalid') // returns false
* isValidRunId('2024-01-15T10:30:45Z') // returns false (missing milliseconds)
*/
export function isValidRunId(runId: any): boolean {
if (!runId || typeof runId !== 'string') {
return false;
}
// Check format: YYYY-MM-DDTHH:mm:ss.sssZ
const isoFormatRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
if (!isoFormatRegex.test(runId)) {
return false;
}
// Validate it's a real date
const date = new Date(runId);
if (isNaN(date.getTime())) {
return false;
}
// Ensure the parsed date matches the input (catches invalid dates like 2024-13-01)
return date.toISOString() === runId;
}
/**
* Parses a run ID string into a Date object.
*
* @param {any} runId - The run ID to parse
* @returns {Date | null} Date object if valid, null if invalid
*
* @example
* parseRunId('2024-01-15T10:30:45.123Z') // returns Date object
* parseRunId('invalid') // returns null
*/
export function parseRunId(runId: any): Date | null {
if (!isValidRunId(runId)) {
return null;
}
return new Date(runId);
}
/**
* Compares two run IDs chronologically.
* Returns a negative number if id1 is earlier, positive if id1 is later, or 0 if equal.
* Can be used as a comparator function for Array.sort().
*
* @param {string} id1 - First run ID to compare
* @param {string} id2 - Second run ID to compare
* @returns {number} Negative if id1 < id2, positive if id1 > id2, zero if equal
* @throws {Error} If either run ID is invalid
*
* @example
* compareRunIds('2024-01-15T10:00:00.000Z', '2024-01-15T11:00:00.000Z') // returns negative number
* ['2024-01-15T14:00:00.000Z', '2024-01-15T10:00:00.000Z'].sort(compareRunIds)
* // returns ['2024-01-15T10:00:00.000Z', '2024-01-15T14:00:00.000Z']
*/
export function compareRunIds(id1: string, id2: string): number {
if (!isValidRunId(id1)) {
throw new Error(`Invalid run ID: ${id1}`);
}
if (!isValidRunId(id2)) {
throw new Error(`Invalid run ID: ${id2}`);
}
// String comparison works for ISO 8601 timestamps
// because they are lexicographically sortable
if (id1 < id2) return -1;
if (id1 > id2) return 1;
return 0;
}