Skip to content

Commit

Permalink
add search parameter support (#18)
Browse files Browse the repository at this point in the history
fixes #17
  • Loading branch information
tatethurston authored Jul 5, 2022
1 parent 0e00165 commit e882157
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 59 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 0.0.13

- Support search parameters. See [#17](https://github.com/tatethurston/nextjs-routes/issues/17) for more context.

## 0.0.12

- Removed reexports of `next/link` and `next/router`.
Expand Down
20 changes: 11 additions & 9 deletions examples/src-pages/nextjs-routes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@
// Run `yarn nextjs-routes` to regenerate this file.

type Route =
| { pathname: "/api/hello" }
| { pathname: "/bars/[bar]"; query: { bar: string } }
| { pathname: "/foos/[foo]"; query: { foo: string } }
| { pathname: "/" };
| { pathname: "/api/hello"; query?: Query | undefined }
| { pathname: "/bars/[bar]"; query: Query<{ bar: string }> }
| { pathname: "/foos/[foo]"; query: Query<{ foo: string }> }
| { pathname: "/"; query?: Query | undefined };

type Pathname = Route["pathname"];

type Query = {
[K in Route as K["pathname"]]: K["query"] extends Record<string, string>
? K["query"]
: never;
type Query<Params = {}> = Params & {
[key: string]: string;
};

type QueryForPathname = {
[K in Route as K["pathname"]]: K["query"];
};

declare module "next/link" {
Expand Down Expand Up @@ -48,7 +50,7 @@ declare module "next/router" {
extends Omit<Router, "push" | "replace"> {
pathname: P;
route: P;
query: Query[P];
query: QueryForPathname[P];
push(
url: Route,
as?: string,
Expand Down
20 changes: 11 additions & 9 deletions examples/typescript-example/nextjs-routes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@
// Run `yarn nextjs-routes` to regenerate this file.

type Route =
| { pathname: "/api/hello" }
| { pathname: "/bars/[bar]"; query: { bar: string } }
| { pathname: "/foos/[foo]"; query: { foo: string } }
| { pathname: "/" };
| { pathname: "/api/hello"; query?: Query | undefined }
| { pathname: "/bars/[bar]"; query: Query<{ bar: string }> }
| { pathname: "/foos/[foo]"; query: Query<{ foo: string }> }
| { pathname: "/"; query?: Query | undefined };

type Pathname = Route["pathname"];

type Query = {
[K in Route as K["pathname"]]: K["query"] extends Record<string, string>
? K["query"]
: never;
type Query<Params = {}> = Params & {
[key: string]: string;
};

type QueryForPathname = {
[K in Route as K["pathname"]]: K["query"];
};

declare module "next/link" {
Expand Down Expand Up @@ -48,7 +50,7 @@ declare module "next/router" {
extends Omit<Router, "push" | "replace"> {
pathname: P;
route: P;
query: Query[P];
query: QueryForPathname[P];
push(
url: Route,
as?: string,
Expand Down
2 changes: 1 addition & 1 deletion public.package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nextjs-routes",
"version": "0.0.12",
"version": "0.0.13",
"description": "Type safe routing for Next.js",
"license": "MIT",
"author": "Tate <[email protected]>",
Expand Down
48 changes: 25 additions & 23 deletions src/__snapshots__/test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,33 @@ exports[`route generation typescript 1`] = `
// Run \`yarn nextjs-routes\` to regenerate this file.
type Route =
| { pathname: '/404' }
| { pathname: '/[foo]', query: { foo: string; } }
| { pathname: '/[foo]/[bar]/[baz]', query: { foo: string; bar: string; baz: string; } }
| { pathname: '/[foo]/bar/[baz]', query: { foo: string; baz: string; } }
| { pathname: '/[foo]/bar/[baz]/foo/[bar]', query: { foo: string; baz: string; bar: string; } }
| { pathname: '/[foo]/baz', query: { foo: string; } }
| { pathname: '/_debug/health-check' }
| { pathname: '/_error' }
| { pathname: '/api/[[...segments]]', query: { segments?: string[]; } }
| { pathname: '/api/[...segments]', query: { segments: string[]; } }
| { pathname: '/api/bar' }
| { pathname: '/foo/[slug]', query: { slug: string; } }
| { pathname: '/' }
| { pathname: '/not-found' }
| { pathname: '/settings/bars/[bar]', query: { bar: string; } }
| { pathname: '/settings/bars/[bar]/baz', query: { bar: string; } }
| { pathname: '/settings/foo' }
| { pathname: '/settings' }
| { pathname: '/404'; query?: Query | undefined }
| { pathname: '/[foo]'; query: Query<{ foo: string; }> }
| { pathname: '/[foo]/[bar]/[baz]'; query: Query<{ foo: string; bar: string; baz: string; }> }
| { pathname: '/[foo]/bar/[baz]'; query: Query<{ foo: string; baz: string; }> }
| { pathname: '/[foo]/bar/[baz]/foo/[bar]'; query: Query<{ foo: string; baz: string; bar: string; }> }
| { pathname: '/[foo]/baz'; query: Query<{ foo: string; }> }
| { pathname: '/_debug/health-check'; query?: Query | undefined }
| { pathname: '/_error'; query?: Query | undefined }
| { pathname: '/api/[[...segments]]'; query?: Query | undefined }
| { pathname: '/api/[...segments]'; query: Query<{ segments: string[]; }> }
| { pathname: '/api/bar'; query?: Query | undefined }
| { pathname: '/foo/[slug]'; query: Query<{ slug: string; }> }
| { pathname: '/'; query?: Query | undefined }
| { pathname: '/not-found'; query?: Query | undefined }
| { pathname: '/settings/bars/[bar]'; query: Query<{ bar: string; }> }
| { pathname: '/settings/bars/[bar]/baz'; query: Query<{ bar: string; }> }
| { pathname: '/settings/foo'; query?: Query | undefined }
| { pathname: '/settings'; query?: Query | undefined }
type Pathname = Route[\\"pathname\\"];
type Query = {
[K in Route as K[\\"pathname\\"]]: K[\\"query\\"] extends Record<string, string>
? K[\\"query\\"]
: never;
type Query<Params = {}> = Params & {
[key: string]: string;
}
type QueryForPathname = {
[K in Route as K[\\"pathname\\"]]: K[\\"query\\"]
};
declare module \\"next/link\\" {
Expand Down Expand Up @@ -64,7 +66,7 @@ declare module \\"next/router\\" {
export interface NextRouter<P extends Pathname = Pathname> extends Omit<Router, \\"push\\" | \\"replace\\"> {
pathname: P;
route: P;
query: Query[P]
query: QueryForPathname[P]
push(url: Route, as?: string, options?: TransitionOptions): Promise<boolean>;
replace(
url: Route,
Expand Down
38 changes: 21 additions & 17 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,26 @@ export function nextRoutes(files: string[]): Route[] {
});
}

function getQueryInterface(query: Route["query"]): string {
let res = "";
function getQueryInterface(
query: Route["query"]
): [query: string, requiredKeys: number] {
let res = "{ ";
let requiredKeys = 0;
Object.entries(query).forEach(([key, value]) => {
res += key;
switch (value) {
case "dynamic": {
requiredKeys += 1;
res += ": string";
break;
}
case "catch-all": {
requiredKeys += 1;
res += ": string[]";
break;
}
case "optional-catch-all": {
res += "?: string[]";
res += "?: string[] | undefined";
break;
}
// istanbul ignore next
Expand All @@ -100,11 +105,8 @@ function getQueryInterface(query: Route["query"]): string {
}
res += "; ";
});

if (res) {
return `{ ${res}}`;
}
return res;
res += " }";
return [res, requiredKeys];
}

export function generate(routes: Route[]): string {
Expand All @@ -115,21 +117,23 @@ export function generate(routes: Route[]): string {
type Route =
| ${routes
.map((route) => {
const query = getQueryInterface(route.query);
if (query) {
return `{ pathname: '${route.pathname}', query: ${query} }`;
const [query, requiredKeys] = getQueryInterface(route.query);
if (requiredKeys > 0) {
return `{ pathname: '${route.pathname}'; query: Query<${query}> }`;
} else {
return `{ pathname: '${route.pathname}' }`;
return `{ pathname: '${route.pathname}'; query?: Query | undefined }`;
}
})
.join("\n | ")}
type Pathname = Route["pathname"];
type Query = {
[K in Route as K["pathname"]]: K["query"] extends Record<string, string>
? K["query"]
: never;
type Query<Params = {}> = Params & {
[key: string]: string;
}
type QueryForPathname = {
[K in Route as K["pathname"]]: K["query"]
};
declare module "next/link" {
Expand Down Expand Up @@ -164,7 +168,7 @@ declare module "next/router" {
export interface NextRouter<P extends Pathname = Pathname> extends Omit<Router, "push" | "replace"> {
pathname: P;
route: P;
query: Query[P]
query: QueryForPathname[P]
push(url: Route, as?: string, options?: TransitionOptions): Promise<boolean>;
replace(
url: Route,
Expand Down

0 comments on commit e882157

Please sign in to comment.