1import * as Duration from "effect/Duration";
2
3/**
4 * Re-hydrate a {@link Duration.Input} that may have been persisted to (and
5 * read back from) state as a structural `Duration` JSON.
6 *
7 * When a `Duration.Duration` round-trips through JSON state it is serialized
8 * to `{ _id: "Duration", _tag: "Millis" | "Nanos" | "Infinity", ... }`, which
9 * is NOT a valid {@link Duration.Input} on the way back in. This normalizes
10 * such a value back to a plain input (`number` millis, `bigint` nanos, or the
11 * `"Infinity"` literal). Any already-valid input (a number, a `"20 seconds"`
12 * string, or a live `Duration`) is returned unchanged.
13 */
14export const normalizeDurationInput = (
15 input: Duration.Input,
16): Duration.Input => {
17 const json = input as {
18 _id?: unknown;
19 _tag?: "Millis" | "Nanos" | "Infinity" | "NegativeInfinity";
20 millis?: number;
21 nanos?: string;
22 };
23 return json._id === "Duration"
24 ? json._tag === "Millis"
25 ? json.millis!
26 : json._tag === "Nanos"
27 ? BigInt(json.nanos!)
28 : "Infinity"
29 : input;
30};
31
32const wire =
33 (to: (input: Duration.Input) => number) =>
34 (input: Duration.Input | undefined): number | undefined =>
35 input === undefined
36 ? undefined
37 : Math.round(to(normalizeDurationInput(input)));
38
39/**
40 * Convert a {@link Duration.Input} to whole seconds for a wire/API field,
41 * normalizing any persisted-state `Duration` JSON first. Returns `undefined`
42 * when `input` is `undefined` so call sites can map optional fields directly.
43 */
44export const toWireSeconds = wire(Duration.toSeconds);
45
46/** Convert a {@link Duration.Input} to whole milliseconds for a wire/API field. */
47export const toWireMillis = wire(Duration.toMillis);
48
49/** Convert a {@link Duration.Input} to whole minutes for a wire/API field. */
50export const toWireMinutes = wire(Duration.toMinutes);
51
52/** Convert a {@link Duration.Input} to whole hours for a wire/API field. */
53export const toWireHours = wire(Duration.toHours);
54
55/** Convert a {@link Duration.Input} to whole days for a wire/API field. */
56export const toWireDays = wire(Duration.toDays);
57
58/**
59 * Convert a {@link Duration.Input} to whole, non-negative
60 * milliseconds. Returns `undefined` when `input` is `undefined`,
61 * so call sites can map through optional duration fields without
62 * a branch.
63 */
64export const toMillis = (input: Duration.Input | undefined) =>
65 input === undefined
66 ? undefined
67 : Math.max(0, Math.ceil(Duration.toMillis(normalizeDurationInput(input))));
68
69/**
70 * Convert a {@link Duration.Input} to whole, non-negative seconds.
71 * Returns `undefined` when `input` is `undefined`.
72 */
73export const toSeconds = (input: Duration.Input | undefined) =>
74 input === undefined
75 ? undefined
76 : Math.max(0, Math.ceil(Duration.toSeconds(normalizeDurationInput(input))));