|
| 1 | +import { z } from "zod"; |
1 | 2 | import parseDuration from "parse-duration"; |
2 | 3 |
|
3 | | -export type TimeGranularityBracket = { |
4 | | - max: string; |
5 | | - granularity: string; |
6 | | -}; |
| 4 | +const DurationString = z |
| 5 | + .string() |
| 6 | + .refine( |
| 7 | + (val) => parseDuration(val) !== null, |
| 8 | + (val) => ({ message: `Invalid duration string: "${val}"` }) |
| 9 | + ); |
| 10 | + |
| 11 | +const BracketSchema = z.object({ |
| 12 | + max: z.union([z.literal("Infinity"), DurationString]), |
| 13 | + granularity: DurationString, |
| 14 | +}); |
| 15 | + |
| 16 | +const BracketsSchema = z |
| 17 | + .array(BracketSchema) |
| 18 | + .min(1, "TimeGranularity requires at least one bracket"); |
| 19 | + |
| 20 | +export type TimeGranularityBracket = z.input<typeof BracketSchema>; |
7 | 21 |
|
8 | 22 | type ParsedBracket = { |
9 | 23 | maxMs: number; |
10 | 24 | granularityMs: number; |
11 | 25 | }; |
12 | 26 |
|
| 27 | +function requireParsedDuration(input: string): number { |
| 28 | + const ms = parseDuration(input); |
| 29 | + if (ms === null) { |
| 30 | + throw new Error(`Failed to parse duration string: "${input}"`); |
| 31 | + } |
| 32 | + return ms; |
| 33 | +} |
| 34 | + |
13 | 35 | export class TimeGranularity { |
14 | 36 | private readonly parsed: ParsedBracket[]; |
15 | 37 |
|
16 | 38 | constructor(brackets: TimeGranularityBracket[]) { |
17 | | - if (brackets.length === 0) { |
18 | | - throw new Error("TimeGranularity requires at least one bracket"); |
19 | | - } |
| 39 | + const validated = BracketsSchema.parse(brackets); |
20 | 40 |
|
21 | | - this.parsed = brackets.map((b) => ({ |
22 | | - maxMs: parseDuration(b.max) ?? Infinity, |
23 | | - granularityMs: parseDuration(b.granularity)!, |
| 41 | + this.parsed = validated.map((b) => ({ |
| 42 | + maxMs: b.max === "Infinity" ? Infinity : requireParsedDuration(b.max), |
| 43 | + granularityMs: requireParsedDuration(b.granularity), |
24 | 44 | })); |
25 | 45 | } |
26 | 46 |
|
27 | 47 | getTimeGranularityMs(from: Date, to: Date): number { |
| 48 | + if (from.getTime() > to.getTime()) { |
| 49 | + return this.parsed[this.parsed.length - 1].granularityMs; |
| 50 | + } |
| 51 | + |
28 | 52 | const rangeMs = to.getTime() - from.getTime(); |
29 | 53 | for (const bracket of this.parsed) { |
30 | 54 | if (rangeMs <= bracket.maxMs) { |
|
0 commit comments