39f6ae692b
Implements enhanced filtering and pagination for the `uwf thread list` command to support workflows with large numbers of threads. Changes: - Add --page, --page-size parameters for pagination (default: page 1, size 20) - Add --since, --until time filters supporting multiple formats (ISO8601, relative like "2h", "1d") - Add --workflow filter to show threads for specific workflow - Add --sort parameter (newest-first, oldest-first, alphabetical) - Add pagination metadata in JSON output (page, pageSize, totalThreads, totalPages, hasMore) - Implement parseRelativeTime() for human-friendly time expressions (1h, 30m, 2d, 1w) - Add comprehensive unit tests for filters, pagination, and time parsing - Update CLI help text with new parameters and examples Fixes #471
45 lines
1.3 KiB
TypeScript
45 lines
1.3 KiB
TypeScript
import { decodeCrockfordBase32Bits, encodeCrockfordBase32Bits } from "./base32.js";
|
|
|
|
const ULID_TIME_BITS = 48;
|
|
const ULID_RANDOM_BITS = 80;
|
|
|
|
function readRandomUint80(): bigint {
|
|
const bytes = new Uint8Array(10);
|
|
crypto.getRandomValues(bytes);
|
|
let x = 0n;
|
|
for (let i = 0; i < bytes.length; i++) {
|
|
x = (x << 8n) | BigInt(bytes[i]);
|
|
}
|
|
return x & ((1n << 80n) - 1n);
|
|
}
|
|
|
|
/**
|
|
* Generate a ULID using Crockford Base32: 10 timestamp chars + 16 random chars.
|
|
* Timestamp uses 48 bits of Unix time in milliseconds.
|
|
*/
|
|
export function generateUlid(nowMs: number): string {
|
|
if (!Number.isFinite(nowMs) || nowMs < 0 || nowMs >= 2 ** ULID_TIME_BITS) {
|
|
throw new Error("nowMs must be a finite number in [0, 2^48)");
|
|
}
|
|
const time = BigInt(Math.floor(nowMs));
|
|
const rand = readRandomUint80();
|
|
const payload = (time << BigInt(ULID_RANDOM_BITS)) | rand;
|
|
return encodeCrockfordBase32Bits(payload, ULID_TIME_BITS + ULID_RANDOM_BITS);
|
|
}
|
|
|
|
/**
|
|
* Extract the timestamp (in milliseconds) from a ULID string.
|
|
* Returns null if the ULID is invalid.
|
|
*/
|
|
export function extractUlidTimestamp(ulid: string): number | null {
|
|
if (ulid.length !== 26) {
|
|
return null;
|
|
}
|
|
const timestampPart = ulid.slice(0, 10);
|
|
const decoded = decodeCrockfordBase32Bits(timestampPart, ULID_TIME_BITS);
|
|
if (!decoded.ok) {
|
|
return null;
|
|
}
|
|
return Number(decoded.value);
|
|
}
|