Skip to content

Commit 2f3bfc3

Browse files
committed
chore: add utility types
1 parent f9279f7 commit 2f3bfc3

10 files changed

Lines changed: 95 additions & 1 deletion

File tree

src/features/products/types/IProduct.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
import type { Branded } from "@/types/Branded";
2+
13
import { Category } from "./Category";
24
import type { IRating } from "./IRating";
35

6+
export type ProductId = Branded<number, "ProductId">;
7+
48
export interface IProduct {
5-
id: number;
9+
id: ProductId;
610
title: string;
711
description: string;
812
category: Category;

src/types/AllOrNothing.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/* eslint-disable @typescript-eslint/no-empty-function */
2+
import type { ReactNode } from "react";
3+
import { describe, it, expectTypeOf } from "vitest";
4+
5+
import type { AllOrNothing } from "./AllOrNothing";
6+
7+
type ChangeFn = () => void;
8+
9+
type ControllableValue<TValue> = AllOrNothing<{
10+
value: TValue;
11+
onValueChange: ChangeFn;
12+
}>;
13+
14+
type Props = ControllableValue<string> & { children: ReactNode };
15+
16+
describe("AllOrNothing", () => {
17+
it("matches the type when only required properties defined", () => {
18+
expectTypeOf({
19+
children: "child",
20+
}).toMatchTypeOf<Props>();
21+
});
22+
23+
it(`doesn't match the type when only value defined`, () => {
24+
expectTypeOf({
25+
children: "child",
26+
value: "value",
27+
}).not.toMatchTypeOf<Props>();
28+
});
29+
30+
it(`doesn't match the type when only onValueChange defined`, () => {
31+
expectTypeOf({
32+
children: "child",
33+
onValueChange: () => {},
34+
}).not.toMatchTypeOf<Props>();
35+
});
36+
37+
it("matches the type when all properties defined", () => {
38+
expectTypeOf({
39+
children: "child",
40+
value: "value",
41+
onValueChange: () => {},
42+
}).toMatchTypeOf<Props>();
43+
});
44+
});

src/types/AllOrNothing.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export type AllOrNothing<T extends AnyObject> = T | ToUndefinedObject<T>;
2+
3+
type ToUndefinedObject<T extends AnyObject> = Partial<
4+
Record<keyof T, undefined>
5+
>;
6+
7+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
8+
type AnyObject = Record<string, any>;

src/types/Autocomplete.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type Autocomplete<T extends string> = T & (string & {});

src/types/Branded.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
declare const __brand: unique symbol;
2+
3+
interface Brand<B> {
4+
[__brand]: B;
5+
}
6+
export type Branded<T, B> = T & Brand<B>;

src/types/DeepPartial.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type DeepPartial<T> = {
2+
[P in keyof T]?: DeepPartial<T[P]>;
3+
};

src/types/NonEmptyArray.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type NonEmptyArray<T> = [T, ...T[]];

src/types/NonNullableProps.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type NonNullableProps<T> = {
2+
[P in keyof T]-?: NonNullable<T[P]>;
3+
};

src/types/OneOfUnion.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { describe, it, expectTypeOf } from "vitest";
2+
3+
import type { OneOfUnion } from "./OneOfUnion";
4+
5+
type MyUnion =
6+
| { type: "a"; value: number }
7+
| { type: "b"; value: string }
8+
| { type: "c"; value: boolean };
9+
10+
describe("OneOfUnion", () => {
11+
it("extracts correct type", () => {
12+
type ExtractedA = OneOfUnion<MyUnion, "a">;
13+
type ExtractedB = OneOfUnion<MyUnion, "b">;
14+
type ExtractedC = OneOfUnion<MyUnion, "c">;
15+
16+
expectTypeOf<ExtractedA>().toEqualTypeOf<{ type: "a"; value: number }>();
17+
expectTypeOf<ExtractedB>().toEqualTypeOf<{ type: "b"; value: string }>();
18+
expectTypeOf<ExtractedC>().toEqualTypeOf<{ type: "c"; value: boolean }>();
19+
});
20+
});

src/types/OneOfUnion.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export type OneOfUnion<
2+
TUnion extends { type: string },
3+
TType extends TUnion["type"],
4+
> = Extract<TUnion, { type: TType }>;

0 commit comments

Comments
 (0)