| 1 | import * 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 | */ |
| 14 | export 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 | |
| 32 | const 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 | */ |
| 44 | export const toWireSeconds = wire(Duration.toSeconds); |
| 45 | |
| 46 | /** Convert a {@link Duration.Input} to whole milliseconds for a wire/API field. */ |
| 47 | export const toWireMillis = wire(Duration.toMillis); |
| 48 | |
| 49 | /** Convert a {@link Duration.Input} to whole minutes for a wire/API field. */ |
| 50 | export const toWireMinutes = wire(Duration.toMinutes); |
| 51 | |
| 52 | /** Convert a {@link Duration.Input} to whole hours for a wire/API field. */ |
| 53 | export const toWireHours = wire(Duration.toHours); |
| 54 | |
| 55 | /** Convert a {@link Duration.Input} to whole days for a wire/API field. */ |
| 56 | export 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 | */ |
| 64 | export 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 | */ |
| 73 | export const toSeconds = (input: Duration.Input | undefined) => |
| 74 | input === undefined |
| 75 | ? undefined |
| 76 | : Math.max(0, Math.ceil(Duration.toSeconds(normalizeDurationInput(input)))); |