Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
130 lines
3.9 KiB
TypeScript
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;
|
|
}
|