From 4bc1edcb8895d5259dc2fdc5a5c2a842dbbcec2e Mon Sep 17 00:00:00 2001 From: Aidan Bleser Date: Tue, 3 Jun 2025 10:25:38 -0500 Subject: [PATCH 1/4] features --- .changeset/fresh-maps-rush.md | 6 ++++ .changeset/ninety-queens-hide.md | 6 ++++ .gitignore | 4 ++- src/ts/time.ts | 49 +++++++++++++++++++++++--------- src/ts/types.ts | 11 +++++++ 5 files changed, 61 insertions(+), 15 deletions(-) create mode 100644 .changeset/fresh-maps-rush.md create mode 100644 .changeset/ninety-queens-hide.md diff --git a/.changeset/fresh-maps-rush.md b/.changeset/fresh-maps-rush.md new file mode 100644 index 0000000..7eda966 --- /dev/null +++ b/.changeset/fresh-maps-rush.md @@ -0,0 +1,6 @@ +--- +"std": minor +--- + +feat(ts/time): Use Branded types for format duration and units. + \ No newline at end of file diff --git a/.changeset/ninety-queens-hide.md b/.changeset/ninety-queens-hide.md new file mode 100644 index 0000000..f255a81 --- /dev/null +++ b/.changeset/ninety-queens-hide.md @@ -0,0 +1,6 @@ +--- +"std": minor +--- + +feat(ts/types): Add `Brand` utility type + \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3091757..b1fa891 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules -coverage \ No newline at end of file +coverage + +.DS_Store diff --git a/src/ts/time.ts b/src/ts/time.ts index 6eef28c..15c4ace 100644 --- a/src/ts/time.ts +++ b/src/ts/time.ts @@ -1,15 +1,36 @@ -/** Milliseconds in a second */ -export const SECOND = 1000; -/** Milliseconds in a minute */ -export const MINUTE = SECOND * 60; -/** Milliseconds in an hour */ -export const HOUR = MINUTE * 60; -/** Milliseconds in a day */ -export const DAY = HOUR * 24; +import type { Brand } from "./types"; + +export type Milliseconds = Brand; + +export const milliseconds = { + /** Milliseconds in a second */ + SECOND: 1000 as Milliseconds, + /** Milliseconds in a minute */ + MINUTE: (1000 * 60) as Milliseconds, + /** Milliseconds in a hour */ + HOUR: (1000 * 60 * 60) as Milliseconds, + /** Milliseconds in a day */ + DAY: (1000 * 60 * 60 * 24) as Milliseconds, + /** Milliseconds in a year */ + YEAR: (1000 * 60 * 60 * 24 * 365) as Milliseconds, +} as const; + +export type Seconds = Brand; + +export const seconds = { + /** Seconds in a minute */ + MINUTE: 60 as Seconds, + /** Seconds in a hour */ + HOUR: (60 * 60) as Seconds, + /** Seconds in a day */ + DAY: (60 * 60 * 24) as Seconds, + /** Seconds in a year */ + YEAR: (60 * 60 * 24 * 365) as Seconds, +} as const; /** Formats a time given in milliseconds with units. * - * @param durationMs Time to be formatted in milliseconds + * @param duration Time to be formatted in milliseconds * @param transform Runs before the num is formatted perfect place to put a `.toFixed()` * @returns * @@ -22,14 +43,14 @@ export const DAY = HOUR * 24; * ``` */ export function formatDuration( - durationMs: number, + duration: Milliseconds, transform: (num: number) => string = (num) => num.toString() ): string { - if (durationMs < SECOND) return `${transform(durationMs)}ms`; + if (duration < milliseconds.SECOND) return `${transform(duration)}ms`; - if (durationMs < MINUTE) return `${transform(durationMs / SECOND)}s`; + if (duration < milliseconds.MINUTE) return `${transform(duration / milliseconds.SECOND)}s`; - if (durationMs < HOUR) return `${transform(durationMs / MINUTE)}min`; + if (duration < milliseconds.HOUR) return `${transform(duration / milliseconds.MINUTE)}min`; - return `${durationMs / HOUR}h`; + return `${duration / milliseconds.HOUR}h`; } diff --git a/src/ts/types.ts b/src/ts/types.ts index bfbf908..06fcc84 100644 --- a/src/ts/types.ts +++ b/src/ts/types.ts @@ -17,3 +17,14 @@ export type LooseAutocomplete = T | (string & {}); export type Prettify = { [K in keyof T]: T[K]; } & {}; + +declare const brand: unique symbol; + +/** Allows you to create a branded type. + * + * ## Usage + * ```ts + * type Milliseconds = Brand; + * ``` + */ +export type Brand = T & { [brand]: Brand }; From 838092cde53055e5bf0f754b6c7e6aeca2246d0b Mon Sep 17 00:00:00 2001 From: Aidan Bleser Date: Tue, 3 Jun 2025 10:28:18 -0500 Subject: [PATCH 2/4] tests --- src/ts/time.test.ts | 28 ++++++++++++++++++---------- src/ts/time.ts | 6 +++--- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/ts/time.test.ts b/src/ts/time.test.ts index 9250e93..68fea2b 100644 --- a/src/ts/time.test.ts +++ b/src/ts/time.test.ts @@ -1,19 +1,27 @@ import { describe, expect, it } from 'vitest'; -import { DAY, HOUR, MINUTE, SECOND, formatDuration } from './time'; +import { milliseconds, formatDuration, type Milliseconds } from './time'; describe('formatDuration', () => { it('correctly formats durations', () => { - expect(formatDuration(500)).toBe('500ms'); - expect(formatDuration(SECOND)).toBe('1s'); - expect(formatDuration(MINUTE)).toBe('1min'); - expect(formatDuration(HOUR)).toBe('1h'); - expect(formatDuration(DAY)).toBe('24h'); + expect(formatDuration(500 as Milliseconds)).toBe('500ms'); + expect(formatDuration(milliseconds.SECOND)).toBe('1s'); + expect(formatDuration(milliseconds.MINUTE)).toBe('1min'); + expect(formatDuration(milliseconds.HOUR)).toBe('1h'); + expect(formatDuration(milliseconds.DAY)).toBe('24h'); }); it('correctly handles transform function', () => { - expect(formatDuration(SECOND / 55, (num) => num.toFixed(2))).toBe('18.18ms'); - expect(formatDuration(MINUTE / 55, (num) => num.toFixed(2))).toBe('1.09s'); - expect(formatDuration(HOUR / 55, (num) => num.toFixed(2))).toBe('1.09min'); - expect(formatDuration(DAY / 55, (num) => num.toFixed(2))).toBe('26.18min'); + expect( + formatDuration((milliseconds.SECOND / 55) as Milliseconds, (num) => num.toFixed(2)) + ).toBe('18.18ms'); + expect( + formatDuration((milliseconds.MINUTE / 55) as Milliseconds, (num) => num.toFixed(2)) + ).toBe('1.09s'); + expect( + formatDuration((milliseconds.HOUR / 55) as Milliseconds, (num) => num.toFixed(2)) + ).toBe('1.09min'); + expect( + formatDuration((milliseconds.DAY / 55) as Milliseconds, (num) => num.toFixed(2)) + ).toBe('26.18min'); }); }); diff --git a/src/ts/time.ts b/src/ts/time.ts index 15c4ace..aaaf2be 100644 --- a/src/ts/time.ts +++ b/src/ts/time.ts @@ -1,6 +1,6 @@ -import type { Brand } from "./types"; +import type { Brand } from './types'; -export type Milliseconds = Brand; +export type Milliseconds = Brand; export const milliseconds = { /** Milliseconds in a second */ @@ -15,7 +15,7 @@ export const milliseconds = { YEAR: (1000 * 60 * 60 * 24 * 365) as Milliseconds, } as const; -export type Seconds = Brand; +export type Seconds = Brand; export const seconds = { /** Seconds in a minute */ From 1eb406ad3c49335810c641d8e65cb1c4268ad243 Mon Sep 17 00:00:00 2001 From: Aidan Bleser Date: Tue, 3 Jun 2025 10:29:07 -0500 Subject: [PATCH 3/4] Delete .DS_Store --- .DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index efe15e497003440494752d0d02a71266930d1940..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHLu}&L75Pch4&xAvV=jE=WEUY?XATn^hen_u%kn#0^WSw3U3Jj&yP zcfw~49ULL07RMO*Z1%p)`&}-*%@P?sU|zq@6pn=T@CLN z@A6*%R|xM4MpUxvsi@xnoOxVM=gr&C(&o7g%3gOrR3267W6c(e>IOp}6bK3g1#T6P z_e0Dkm{=?f>Q@IleFPv@IjxOjIZH@RY%#G|7^H`$Oe)c&Dtp9GCY|%KjY}*R22DDY zJ$xv;v$7`?rFZB0u?~ky42C`^5ESqgsF=$QssH=S&;Nc>xC#mc1)h`ws@gtiw|Gmo zww7*AYHiH+kxfkE3WGX@oh`?DAf;ITSI|aZECXUt<8 From 47235e2d461ff1323c1b80e5f5152c5bd1d0d676 Mon Sep 17 00:00:00 2001 From: Aidan Bleser Date: Tue, 3 Jun 2025 10:29:56 -0500 Subject: [PATCH 4/4] Update time.test.ts --- src/ts/time.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ts/time.test.ts b/src/ts/time.test.ts index 68fea2b..bd378dd 100644 --- a/src/ts/time.test.ts +++ b/src/ts/time.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { milliseconds, formatDuration, type Milliseconds } from './time'; +import { type Milliseconds, formatDuration, milliseconds } from './time'; describe('formatDuration', () => { it('correctly formats durations', () => {