Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/common/type.t-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
CountChar,
ExtractByPrefix,
FilterNever,
IsAllOptional,
IsEqualNumber,
Replace,
ReplaceAll,
Expand Down Expand Up @@ -108,3 +109,13 @@ type SameSlashNumTestCases = [
Expect<Equal<SameSlashNum<`/${string}`, "/a">, true>>,
Expect<Equal<SameSlashNum<`/${string}`, "/a/b">, false>>,
];

// eslint-disable-next-line @typescript-eslint/no-unused-vars
type IsAllOptionalTestCases = [
// eslint-disable-next-line @typescript-eslint/ban-types
Expect<Equal<IsAllOptional<{}>, true>>,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using {} as a type.

The static analysis tool suggests not using {} as a type and explicitly defining the object shape instead. Using {} as a type could lead to unexpected behavior and make the code harder to understand and maintain.

Consider replacing {} with a more explicit type, such as Record<string, never>, to represent an empty object type.

Tools
Biome

[error] 116-116: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

Expect<Equal<IsAllOptional<{ a?: string }>, true>>,
Expect<Equal<IsAllOptional<{ a: string }>, false>>,
Expect<Equal<IsAllOptional<{ a?: string; b: string }>, false>>,
Expect<Equal<IsAllOptional<{ a?: string; b?: string }>, true>>,
];
3 changes: 3 additions & 0 deletions src/common/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,6 @@ export type SameSlashNum<P1 extends string, P2 extends string> = IsEqualNumber<
CountChar<P1, "/">,
CountChar<P2, "/">
>;

// eslint-disable-next-line @typescript-eslint/ban-types
export type IsAllOptional<T> = {} extends T ? true : false;
17 changes: 17 additions & 0 deletions src/fetch/index.t-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,23 @@ const JSONT = JSON as JSONT;
})();
}

{
type Spec = DefineApiEndpoints<{
"/users": {
post: {
headers: { "Content-Type"?: "application/json" };
body: { userName?: string };
responses: { 200: { body: { prop: string } } };
};
};
}>;
(async () => {
const f = fetch as FetchT<"", Spec>;
// headers and body can be omitted because they are optional
await f(`/users`, {});
})();
}

{
type Spec = DefineApiEndpoints<{
"/packages/list": {
Expand Down
11 changes: 9 additions & 2 deletions src/fetch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
PathToUrlParamPattern,
Replace,
StatusCode,
IsAllOptional,
} from "../common";
import { UrlPrefixPattern, ToUrlParamPattern } from "../common";
import { TypedString } from "../json";
Expand All @@ -26,9 +27,15 @@ export type RequestInitT<
method?: InputMethod;
} & FilterNever<{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
body: Body extends Record<string, any> ? TypedString<Body> : never;
body: Body extends Record<string, any>
? IsAllOptional<Body> extends true
? Body | TypedString<Body> | undefined
: TypedString<Body>
: never;
headers: HeadersObj extends Record<string, string>
? HeadersObj | Headers
? IsAllOptional<HeadersObj> extends true
? HeadersObj | Headers | undefined
: HeadersObj | Headers
: never;
}>;

Expand Down