Skip to content

Commit 31d794e

Browse files
authored
feat: Add support for experimental.typedRoutes (lukemorales#15)
* literal routes * update dependencies * lint files * integrate with Next `typedRoutes` * update docs * generate changeset * rename type * update lockfile * update ci actions * remove initial cache from ci script
1 parent 5e7e23e commit 31d794e

File tree

12 files changed

+108
-69
lines changed

12 files changed

+108
-69
lines changed

.changeset/fuzzy-parrots-glow.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"next-safe-navigation": minor
3+
---
4+
5+
Add support for `experimental.typedRoutes`
6+
7+
You may now enable `experimental.typedRoutes` in `next.config.js` to have a better and safer experience with autocomplete when defining your routes

.github/workflows/ci.yml

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,11 @@ jobs:
2525

2626
- name: ♻️ Cache node_modules
2727
uses: actions/cache@v4
28-
id: bun-cache
28+
id: cache
2929
with:
3030
path: "**/node_modules"
3131
key: ${{ runner.os }}-node-${{ hashFiles('**/bun.lockb') }}
32-
restore-keys: |
33-
${{ runner.os }}-node-
3432

35-
if: steps.bun-cache.outputs.cache-hit != 'true'
3633
- run: bun install --frozen-lockfile
3734

3835
lint-package:
@@ -62,7 +59,7 @@ jobs:
6259
key: ${{ matrix.os }}-eslint-${{ hashFiles('**/*.ts', 'package.json', 'tsconfig.json') }}
6360

6461
- name: 📦 Install dependencies
65-
if: steps.bun-cache.outputs.cache-hit != 'true'
62+
if: steps.cache.outputs.cache-hit != 'true'
6663
run: bun install --frozen-lockfile
6764

6865
- name: 🚨 Lint files
@@ -88,7 +85,7 @@ jobs:
8885
key: ${{ runner.os }}-node-${{ hashFiles('**/bun.lockb') }}
8986

9087
- name: 📦 Install dependencies
91-
if: steps.bun-cache.outputs.cache-hit != 'true'
88+
if: steps.cache.outputs.cache-hit != 'true'
9289
run: bun install --frozen-lockfile
9390

9491
- name: 🧪 Run tests
@@ -119,7 +116,7 @@ jobs:
119116
key: ${{ runner.os }}-node-${{ hashFiles('**/bun.lockb') }}
120117

121118
- name: 📦 Install dependencies
122-
if: steps.bun-cache.outputs.cache-hit != 'true'
119+
if: steps.cache.outputs.cache-hit != 'true'
123120
run: bun install --frozen-lockfile
124121

125122
- name: 🏗️ Build package

.github/workflows/release.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ jobs:
3030

3131
- name: ♻️ Cache node_modules
3232
uses: actions/cache@v4
33-
id: bun-cache
33+
id: cache
3434
with:
3535
path: "**/node_modules"
3636
key: ${{ runner.os }}-node-${{ hashFiles('**/bun.lockb') }}
3737
restore-keys: |
3838
${{ runner.os }}-node-
3939
40-
if: steps.bun-cache.outputs.cache-hit != 'true'
40+
if: steps.cache.outputs.cache-hit != 'true'
4141
- run: bun install --frozen-lockfile
4242

4343
build:
@@ -54,7 +54,7 @@ jobs:
5454
bun-version: latest
5555

5656
- name: 📦 Install dependencies
57-
if: steps.bun-cache.outputs.cache-hit != 'true'
57+
if: steps.cache.outputs.cache-hit != 'true'
5858
run: bun install --frozen-lockfile
5959

6060
- name: 🚨 Check for errors
@@ -159,7 +159,7 @@ jobs:
159159
registry-url: 'https://npm.pkg.github.com'
160160

161161
- name: 📦 Install dependencies
162-
if: steps.bun-cache.outputs.cache-hit != 'true'
162+
if: steps.cache.outputs.cache-hit != 'true'
163163
run: bun install --frozen-lockfile
164164

165165
- name: 🏷️ Overwrite package name with user scope

.github/workflows/tests.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ jobs:
2020

2121
- name: ♻️ Cache node_modules
2222
uses: actions/cache@v4
23-
id: bun-cache
23+
id: cache
2424
with:
2525
path: "**/node_modules"
2626
key: ${{ runner.os }}-node-${{ hashFiles('**/bun.lockb') }}
2727
restore-keys: |
2828
${{ runner.os }}-node-
2929
30-
if: steps.bun-cache.outputs.cache-hit != 'true'
30+
if: steps.cache.outputs.cache-hit != 'true'
3131
- run: bun install --frozen-lockfile
3232

3333
tests:
@@ -50,7 +50,7 @@ jobs:
5050
key: ${{ runner.os }}-node-${{ hashFiles('**/bun.lockb') }}
5151

5252
- name: 📦 Install dependencies
53-
if: steps.bun-cache.outputs.cache-hit != 'true'
53+
if: steps.cache.outputs.cache-hit != 'true'
5454
run: bun install --frozen-lockfile
5555

5656
- name: 🧪 Run tests
@@ -81,7 +81,7 @@ jobs:
8181
key: ${{ runner.os }}-node-${{ hashFiles('**/bun.lockb') }}
8282

8383
- name: 📦 Install dependencies
84-
if: steps.bun-cache.outputs.cache-hit != 'true'
84+
if: steps.cache.outputs.cache-hit != 'true'
8585
run: bun install --frozen-lockfile
8686

8787
- name: 🏗️ Build package

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ npm install next-safe-navigation
3333

3434
## ⚡ Quick start
3535

36-
> [!WARNING]
37-
> Ensure `experimental.typedRoutes` is disabled in `next.config.js`
36+
> [!TIP]
37+
> Enable `experimental.typedRoutes` in `next.config.js` for a better and safer experience with autocomplete when defining your routes
3838
3939
### Declare your application routes and parameters in a single place
4040
```ts

bun.lockb

13.6 KB
Binary file not shown.

package.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,19 +59,19 @@
5959
"@changesets/changelog-github": "^0.5.0",
6060
"@changesets/cli": "^2.27.1",
6161
"@lukemorales/prettier-config": "^1.1.0",
62-
"@testing-library/react": "^14.2.0",
62+
"@testing-library/react": "^14.2.2",
6363
"@types/bun": "latest",
6464
"@vitejs/plugin-react": "^4.2.1",
65-
"@vitest/coverage-v8": "^1.2.2",
66-
"@vitest/ui": "^1.2.2",
67-
"eslint-config-lukemorales": "^0.3.0",
65+
"@vitest/coverage-v8": "^1.4.0",
66+
"@vitest/ui": "^1.4.0",
67+
"eslint-config-lukemorales": "^0.4.1",
6868
"jsdom": "^24.0.0",
69-
"next": "^14.1.0",
69+
"next": "^14.1.4",
7070
"npm-run-all": "^4.1.5",
71-
"prettier": "^3.2.4",
72-
"tsup": "^8.0.1",
71+
"prettier": "^3.2.5",
72+
"tsup": "^8.0.2",
7373
"typescript": "^4.8.2",
74-
"vitest": "^1.2.2",
74+
"vitest": "^1.4.0",
7575
"zod": "^3.22.4"
7676
},
7777
"peerDependencies": {

src/convert-url-search-params-to-object.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export function convertURLSearchParamsToObject(
1212
const values = params.getAll(key);
1313

1414
acc[key] = values.length > 1 ? values : value;
15+
1516
return acc;
1617
},
1718
{},

src/create-navigation-config.ts

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,67 +10,77 @@ import { makeRouteBuilder, type RouteBuilder } from './make-route-builder';
1010
import type { Prettify } from './types';
1111

1212
type AnyRouteBuilder =
13-
| RouteBuilder<any, any>
14-
| RouteBuilder<any, never>
15-
| RouteBuilder<never, any>
16-
| RouteBuilder<never, never>;
13+
| RouteBuilder<string, any, any>
14+
| RouteBuilder<string, any, never>
15+
| RouteBuilder<string, never, any>
16+
| RouteBuilder<string, never, never>;
1717

1818
type NavigationConfig = Record<string, AnyRouteBuilder>;
1919

20-
type SafeRootRoute = () => string;
20+
type SafeRootRoute<Path extends string> = () => Path;
2121

22-
type SafeRouteWithParams<Params extends z.ZodSchema> = {
23-
(options: z.input<Params>): string;
22+
type SafeRouteWithParams<Path extends string, Params extends z.ZodSchema> = {
23+
(options: z.input<Params>): Path;
2424
$parseParams: (params: unknown) => z.output<Params>;
2525
};
2626

27-
type SafeRouteWithSearch<Search extends z.ZodSchema> = {
28-
(options?: { search?: z.input<Search> }): string;
27+
type SafeRouteWithSearch<Path extends string, Search extends z.ZodSchema> = {
28+
(options?: { search?: z.input<Search> }): Path;
2929
$parseSearchParams: (searchParams: unknown) => z.output<Search>;
3030
};
3131

32-
type SafeRouteWithRequiredSearch<Search extends z.ZodSchema> = {
33-
(options: { search: z.input<Search> }): string;
32+
type SafeRouteWithRequiredSearch<
33+
Path extends string,
34+
Search extends z.ZodSchema,
35+
> = {
36+
(options: { search: z.input<Search> }): Path;
3437
$parseSearchParams: (searchParams: unknown) => z.output<Search>;
3538
};
3639

3740
type SafeRouteWithParamsAndSearch<
41+
Path extends string,
3842
Params extends z.ZodSchema,
3943
Search extends z.ZodSchema,
4044
Options = z.input<Params> & { search?: z.input<Search> },
4145
> = {
42-
(options: Prettify<Options>): string;
46+
(options: Prettify<Options>): Path;
4347
$parseParams: (params: unknown) => z.output<Params>;
4448
$parseSearchParams: (searchParams: unknown) => z.output<Search>;
4549
};
4650

4751
type SafeRouteWithParamsAndRequiredSearch<
52+
Path extends string,
4853
Params extends z.ZodSchema,
4954
Search extends z.ZodSchema,
5055
Options = z.input<Params> & { search: z.input<Search> },
5156
> = {
52-
(options: Prettify<Options>): string;
57+
(options: Prettify<Options>): Path;
5358
$parseParams: (params: unknown) => z.output<Params>;
5459
$parseSearchParams: (searchParams: unknown) => z.output<Search>;
5560
};
5661

57-
type SafeRoute<Params extends z.ZodSchema, Search extends z.ZodSchema> =
58-
[Params, Search] extends [never, never] ? SafeRootRoute
59-
: [Params, Search] extends [z.ZodSchema, never] ? SafeRouteWithParams<Params>
62+
type SafeRoute<
63+
Path extends string,
64+
Params extends z.ZodSchema,
65+
Search extends z.ZodSchema,
66+
> =
67+
[Params, Search] extends [never, never] ? SafeRootRoute<Path>
68+
: [Params, Search] extends [z.ZodSchema, never] ?
69+
SafeRouteWithParams<Path, Params>
6070
: [Params, Search] extends [never, z.ZodSchema] ?
6171
undefined extends z.input<Search> ?
62-
SafeRouteWithSearch<Search>
63-
: SafeRouteWithRequiredSearch<Search>
72+
SafeRouteWithSearch<Path, Search>
73+
: SafeRouteWithRequiredSearch<Path, Search>
6474
: [Params, Search] extends [z.ZodSchema, z.ZodSchema] ?
6575
undefined extends z.input<Search> ?
66-
SafeRouteWithParamsAndSearch<Params, Search>
67-
: SafeRouteWithParamsAndRequiredSearch<Params, Search>
76+
SafeRouteWithParamsAndSearch<Path, Params, Search>
77+
: SafeRouteWithParamsAndRequiredSearch<Path, Params, Search>
6878
: never;
6979

7080
type RouteWithParams<Config extends NavigationConfig> = {
7181
[Route in keyof Config & string]: Config[Route] extends (
72-
| RouteBuilder<infer Params extends z.ZodSchema, never>
73-
| RouteBuilder<infer Params extends z.ZodSchema, any>
82+
| RouteBuilder<string, infer Params extends z.ZodSchema, never>
83+
| RouteBuilder<string, infer Params extends z.ZodSchema, any>
7484
) ?
7585
Params extends z.ZodSchema ?
7686
Route
@@ -80,8 +90,8 @@ type RouteWithParams<Config extends NavigationConfig> = {
8090

8191
type RouteWithSearchParams<Config extends NavigationConfig> = {
8292
[Route in keyof Config & string]: Config[Route] extends (
83-
| RouteBuilder<never, infer Search extends z.ZodSchema>
84-
| RouteBuilder<any, infer Search extends z.ZodSchema>
93+
| RouteBuilder<string, never, infer Search extends z.ZodSchema>
94+
| RouteBuilder<string, any, infer Search extends z.ZodSchema>
8595
) ?
8696
Search extends z.ZodSchema ?
8797
Route
@@ -92,11 +102,12 @@ type RouteWithSearchParams<Config extends NavigationConfig> = {
92102
type SafeNavigation<Config extends NavigationConfig> = {
93103
[Route in keyof Config]: Config[Route] extends (
94104
RouteBuilder<
105+
infer Path extends string,
95106
infer Params extends z.ZodSchema,
96107
infer Search extends z.ZodSchema
97108
>
98109
) ?
99-
SafeRoute<Params, Search>
110+
SafeRoute<Path, Params, Search>
100111
: never;
101112
};
102113

@@ -108,8 +119,8 @@ type ValidatedRouteParams<
108119
> =
109120
Route extends keyof Pick<Router, AcceptableRoute & keyof Router> ?
110121
Router[Route] extends (
111-
| SafeRoute<infer Params extends z.ZodSchema, any>
112-
| SafeRoute<infer Params extends z.ZodSchema, never>
122+
| SafeRoute<string, infer Params extends z.ZodSchema, any>
123+
| SafeRoute<string, infer Params extends z.ZodSchema, never>
113124
) ?
114125
z.output<Params>
115126
: never
@@ -123,8 +134,8 @@ type ValidatedRouteSearchParams<
123134
> =
124135
Route extends keyof Pick<Router, AcceptableRoute & keyof Router> ?
125136
Router[Route] extends (
126-
| SafeRoute<any, infer Search extends z.ZodSchema>
127-
| SafeRoute<never, infer Search extends z.ZodSchema>
137+
| SafeRoute<string, any, infer Search extends z.ZodSchema>
138+
| SafeRoute<string, never, infer Search extends z.ZodSchema>
128139
) ?
129140
z.output<Search>
130141
: never

src/make-route-builder.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable no-secrets/no-secrets */
21
import { z } from 'zod';
32

43
import { makeRouteBuilder } from './make-route-builder';

0 commit comments

Comments
 (0)