diff --git a/src/common/spec.ts b/src/common/spec.ts index c35f5ba..8adbb24 100644 --- a/src/common/spec.ts +++ b/src/common/spec.ts @@ -23,6 +23,7 @@ export const Method = [ "head", ] as const; export type Method = (typeof Method)[number]; +export type CaseInsensitive = Uppercase | Lowercase; export type CaseInsensitiveMethod = Method | Uppercase; export const isMethod = (x: unknown): x is Method => Method.includes(x as Method); diff --git a/src/fetch/index.t-test.ts b/src/fetch/index.t-test.ts index 99936b5..6537893 100644 --- a/src/fetch/index.t-test.ts +++ b/src/fetch/index.t-test.ts @@ -14,9 +14,9 @@ const JSONT = JSON as JSONT; (async () => { const f = fetch as FetchT<"", Spec>; { - // TODO: 今はinitを省略する場合undefinedを明示的に渡す必要があるが、なんとかしたい + // TODO: 今はinitの省略ができないが、できるようにしたい // methodを省略した場合はgetとして扱う - const res = await f("/users", undefined); + const res = await f("/users", {}); (await res.json()).prop; } })(); @@ -110,7 +110,7 @@ const JSONT = JSON as JSONT; } { - // TODO: 今は定義していないメソッドを受け付けてしまうが、いつかなんとかしたい + // @ts-expect-error 定義されていないmethodは指定できない await f("/users", { method: "patch" }); } })(); @@ -151,7 +151,7 @@ const JSONT = JSON as JSONT; }>; (async () => { const f = fetch as FetchT<"", Spec>; - // TODO: getが定義されていない場合、methodを省略したらエラーになってほしいが今はならない + // @ts-expect-error getが定義されていない場合、methodは省略できない await f(`/users`, {}); })(); } @@ -208,6 +208,34 @@ const JSONT = JSON as JSONT; })(); } +{ + type Spec = DefineApiEndpoints<{ + "/packages/list": { + get: { + responses: { 200: { body: { prop: string } } }; + query: { state?: boolean }; + }; + }; + }>; + (async () => { + const basePath = "/api/projects/:projectName/workflow"; + const f = fetch as FetchT; + { + const res = await f( + `/api/projects/projectA/workflow/packages/list?state=true`, + {}, + ); + if (res.ok) { + (await res.json()).prop; + } + } + { + // query parameter can be omitted because it is optional + f(`/api/projects/projectA/workflow/packages/list`, {}); + } + })(); +} + { type Spec = DefineApiEndpoints<{ "/vectorize/indexes/:indexName": { diff --git a/src/fetch/index.ts b/src/fetch/index.ts index c54e2b5..42ab75e 100644 --- a/src/fetch/index.ts +++ b/src/fetch/index.ts @@ -1,6 +1,5 @@ import { ApiEndpoints, - ApiHasP, ApiP, AnyApiResponses, CaseInsensitiveMethod, @@ -13,6 +12,7 @@ import { Replace, StatusCode, IsAllOptional, + CaseInsensitive, } from "../common"; import { UrlPrefixPattern, ToUrlParamPattern } from "../common"; import { TypedString } from "../json"; @@ -22,10 +22,12 @@ export type RequestInitT< // eslint-disable-next-line @typescript-eslint/no-explicit-any Body extends Record | undefined, HeadersObj extends Record | undefined, -> = Omit & { - method?: InputMethod; +> = Omit & + (InputMethod extends "get" | "GET" + ? { method?: InputMethod } + : { method: InputMethod }) & // eslint-disable-next-line @typescript-eslint/no-explicit-any -} & (Body extends Record + (Body extends Record ? IsAllOptional extends true ? { body?: Body | TypedString } : { body: TypedString } @@ -42,17 +44,24 @@ export type RequestInitT< * FetchT is a type for window.fetch like function but more strict type information */ type FetchT = < + UrlPattern extends ToUrlParamPattern<`${UrlPrefix}${keyof E & string}`>, Input extends Query extends undefined - ? ToUrlParamPattern<`${UrlPrefix}${keyof E & string}`> - : `${ToUrlParamPattern<`${UrlPrefix}${keyof E & string}`>}?${string}`, + ? UrlPattern + : IsAllOptional extends true + ? UrlPattern | `${UrlPattern}?${string}` + : `${UrlPattern}?${string}`, InputPath extends PathToUrlParamPattern< NormalizePath< ParseURL, "">>["path"] > >, CandidatePaths extends string = MatchedPatterns, - InputMethod extends CaseInsensitiveMethod = "get", - M extends Method = Lowercase, + InputMethod extends CaseInsensitive & + CaseInsensitiveMethod = CaseInsensitive & + CaseInsensitiveMethod, + M extends Method = CaseInsensitive<"get"> extends InputMethod + ? "get" + : Lowercase, Query extends ApiP = ApiP< E, CandidatePaths, @@ -76,19 +85,11 @@ type FetchT = < : Record, >( input: Input, - init: ApiHasP extends true - ? RequestInitT< - InputMethod, - ApiP, - ApiP - > - : - | RequestInitT< - InputMethod, - ApiP, - ApiP - > - | undefined, + init: RequestInitT< + InputMethod, + ApiP, + ApiP + >, ) => Promise; export default FetchT;