Skip to content

Commit

Permalink
test(dredge-types): test type and fix client type issue (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
dhrjarun authored Oct 7, 2024
1 parent 3e805bc commit 878dea2
Show file tree
Hide file tree
Showing 13 changed files with 795 additions and 177 deletions.
5 changes: 5 additions & 0 deletions .changeset/silly-turkeys-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"dredge-types": patch
---

Fix client type issue due to path type
2 changes: 1 addition & 1 deletion packages/types/source/client/dredge-client-option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export type inferDredgeClientOption<
{
method: Method;
dataType?: keyof RouteOptions["dataTypes"];
responseDataTypes?: keyof RouteOptions["dataTypes"];
responseDataType?: keyof RouteOptions["dataTypes"];
dataTypes?: {
[key in keyof RouteOptions["dataTypes"]]?: string;
};
Expand Down
73 changes: 72 additions & 1 deletion packages/types/source/route/route-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,36 @@ import { Parser, inferParserType } from "../parser";
import { IsNever } from "../utils";
import { Route } from "./dredge-route.js";

type _inferPathArray<T> = T extends `${infer P}/${infer Rest}`
? [P, ..._inferPathArray<Rest>]
: T extends `/${infer P}`
? [P]
: [T];

type TrimSlashes<T> = T extends `/${infer U}/`
? U
: T extends `/${infer U}`
? U
: T extends `${infer U}/`
? U
: T;

export type inferPathArray<T> = _inferPathArray<TrimSlashes<T>>;

type SupportedTypeInTemplateLiteral =
| string
| number
| bigint
| boolean
| null
| undefined;

type inferParamParserType<P> = IsNever<P> extends true
? string
: P extends Parser
? inferParserType<P>
? inferParserType<P> extends SupportedTypeInTemplateLiteral
? inferParserType<P>
: string
: string;

type _inferSimplePathString<
Expand All @@ -26,6 +52,36 @@ export type inferSimplePathString<
? _inferSimplePathString<Paths, Params>
: never;

export type inferRouteGenericPath<R> = R extends Route<
any,
any,
any,
any,
infer PathArray extends string[],
any,
any,
any,
any,
any
>
? inferGenericPath<PathArray>
: never;

type _inferGenericPath<Paths> = Paths extends [
infer First extends string,
...infer Tail extends string[],
]
? `/${First extends `:${infer N}`
? string | number | bigint
: First}${_inferGenericPath<Tail>}`
: "";

export type inferGenericPath<Paths> = Paths extends []
? never
: Paths extends string[]
? _inferGenericPath<Paths>
: never;

type _inferParamPathString<Paths> = Paths extends [
infer First extends string,
...infer Tail extends string[],
Expand Down Expand Up @@ -111,6 +167,21 @@ export type inferRouteSimplePath<R> = R extends Route<
? inferSimplePathString<PathArray, Params>
: never;

export type inferRouteParamPath<R> = R extends Route<
any,
any,
any,
any,
infer PathArray,
any,
any,
any,
any,
any
>
? inferParamPathString<PathArray>
: never;

export type inferRouteSignature<R> = R extends Route<
any,
any,
Expand Down
13 changes: 5 additions & 8 deletions packages/types/source/router.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {
AnyRoute,
ExcludeRoute,
HasRouteParamPath,
Route,
inferRouteGenericPath,
inferRouteSimplePath,
} from "./route";
import { IsNever, Merge } from "./utils";
Expand Down Expand Up @@ -35,13 +35,10 @@ export type ModifyRoutes<
? HasRouteParamPath<First> extends false
? ModifyRoutes<Rest, All, [...U, First]>
: IsNever<
Extract<
inferRouteSimplePath<ExcludeRoute<All[number], First>>,
inferRouteSimplePath<First>
>
> extends true
? ModifyRoutes<Rest, All, [...U, First]>
: ModifyRoutes<Rest, All, [...U, MakeDynamicRoute<First>]>
Extract<inferRouteGenericPath<First>, inferRouteSimplePath<First>>
> extends false
? ModifyRoutes<Rest, All, [...U, MakeDynamicRoute<First>]>
: ModifyRoutes<Rest, All, [...U, First]>
: U;

type MakeDynamicRoute<T> = T extends Route<
Expand Down
7 changes: 7 additions & 0 deletions packages/types/source/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@ export type RequiredKeys<Type> = Type extends unknown
? Exclude<keyof Type, OptionalKeys<Type>>
: never;

export type DistributiveIndex<T, K extends PropertyKey> = T extends Record<
K,
infer V
>
? V
: never;

// https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360
export type IsAny<Type> = 0 extends 1 & Type ? true : false;

Expand Down
217 changes: 217 additions & 0 deletions packages/types/test/client-options.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import { expectTypeOf, test } from "vitest";
import { dredgeRoute } from "./helpers/dredge-route";
import { dredgeRouter } from "./helpers/dredge-router";
import { inferDredgeClientOption } from "../source/client/dredge-client-option";
import { DistributiveIndex, inferRouterRoutes } from "../source";
import { z } from "zod";

test("options.method", () => {
const getPostPutRouter = dredgeRouter([
dredgeRoute().path("/a").get().build(),
dredgeRoute().path("/b").post().build(),
dredgeRoute().path("/c").put().build(),
]);

type GetPostPutRouter = inferRouterRoutes<typeof getPostPutRouter>;
type GetPostPutOptions = inferDredgeClientOption<GetPostPutRouter[number]>;

expectTypeOf<GetPostPutOptions["method"]>().toEqualTypeOf<
"get" | "post" | "put"
>();

const deletePatchHeadRouter = dredgeRouter([
dredgeRoute().path("/d").delete().build(),
dredgeRoute().path("/e").patch().build(),
dredgeRoute().path("/f").head().build(),
]);

type DeletePatchHeadRouter = inferRouterRoutes<typeof deletePatchHeadRouter>;
type DeletePatchHeadOptions = inferDredgeClientOption<
DeletePatchHeadRouter[number]
>;

expectTypeOf<DeletePatchHeadOptions["method"]>().toEqualTypeOf<
"delete" | "patch" | "head"
>();
});

test("options.dataTypes", () => {
const r = dredgeRoute().options({
dataTypes: {
json: "application/json",
form: "application/x-www-form-urlencoded",
text: "text/plain",
},
});

const router = dredgeRouter([
r.path("/a").get().build(),
r.path("/b").post(z.string()).build(),
]);

type Router = inferRouterRoutes<typeof router>;
type DataTypes = inferDredgeClientOption<Router[number]>["dataTypes"];

expectTypeOf<DataTypes>().toEqualTypeOf<
| {
readonly json?: string | undefined;
readonly form?: string | undefined;
readonly text?: string | undefined;
}
| undefined
>();
});

test("options.dataType and options.responseDataType", () => {
const r = dredgeRoute().options({
dataTypes: {
json: "application/json",
form: "application/x-www-form-urlencoded",
text: "text/plain",
},
});

const router = dredgeRouter([
r.path("/a").get().build(),
r.path("/b").get().build(),
r.path("/c").get().build(),
]);

type Router = inferRouterRoutes<typeof router>;
type Options = inferDredgeClientOption<Router[number]>;

expectTypeOf<Options["dataType"]>().toEqualTypeOf<
"json" | "form" | "text" | undefined
>();
expectTypeOf<Options["responseDataType"]>().toEqualTypeOf<
"json" | "form" | "text" | undefined
>();
});

test.todo(
"options.params should not exist if there are none defined in the route",
);

test("options.params", () => {
const r = dredgeRoute();

const router = dredgeRouter([
r.path("/a/:b").get().build(),
r
.path("/c/:d/:e")
.params({
d: z.number(),
e: z.enum(["a", "b"]),
})
.get()
.build(),
r
.path("/f/g/:h/:i/:j")
.params({
h: z.string(),
i: z.boolean(),
j: z.date(),
})
.get()
.build(),
// r.path('/k').get().build(), // TODO: fix this, it causes some unneccary type in params type.
]);

type Router = inferRouterRoutes<typeof router>;
type Params = inferDredgeClientOption<Router[number]>["params"];

expectTypeOf<Params>().toEqualTypeOf<
| {
b: string;
}
| { readonly d: number; readonly e: "a" | "b" }
| { readonly h: string; readonly i: boolean; readonly j: Date }
>();
});

test("options.searchParams", () => {
const r = dredgeRoute();

const router = dredgeRouter([
r.path("/a").get().build(),
r
.path("/c")
.searchParams({
d: z.number(),
e: z.enum(["a", "b"]),
})
.get()
.build(),
r
.path("/f")
.searchParams({
h: z.string(),
i: z.boolean(),
j: z.date(),
})
.get()
.build(),
r
.path("/k")
.get()
.build(), // TODO: fix this
]);

type Router = inferRouterRoutes<typeof router>;
type SearchParams = inferDredgeClientOption<Router[number]>["searchParams"];

expectTypeOf<SearchParams>().toEqualTypeOf<
| Record<string, any>
| {
// TODO: see what to do with this, and where Record and undefined is coming from
readonly d: number | number[];
readonly e: "a" | "b" | ("a" | "b")[];
}
| {
readonly h: string | string[];
readonly i: boolean | boolean[];
readonly j: Date | Date[];
}
| undefined
>();
});
test.todo("options.data should not exist if method does not suport it");
test("options.data", () => {
const r = dredgeRoute().options({
dataTypes: {
json: "application/json",
form: "application/x-www-form-urlencoded",
text: "text/plain",
},
});

const router = dredgeRouter([
r.path("/a").get().build(),
r.path("/c").put(z.number()).build(),
r.path("/b").post(z.string()).build(),
r
.path("/c")
.patch(z.object({ a: z.string() }))
.build(),
]);

type Routes = inferRouterRoutes<typeof router>;
type Options = inferDredgeClientOption<Routes[number]>;

type ExpectedData = string | number | { a: string };
expectTypeOf<
DistributiveIndex<Options, "data">
>().toEqualTypeOf<ExpectedData>();

expectTypeOf<
DistributiveIndex<Options, "json">
>().toEqualTypeOf<ExpectedData>();

expectTypeOf<
DistributiveIndex<Options, "form">
>().toEqualTypeOf<ExpectedData>();

expectTypeOf<
DistributiveIndex<Options, "text">
>().toEqualTypeOf<ExpectedData>();
});
Loading

0 comments on commit 878dea2

Please sign in to comment.