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>
This commit is contained in:
129
packages/tm-core/src/utils/run-id-generator.ts
Normal file
129
packages/tm-core/src/utils/run-id-generator.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user