Skip to content

Commit ce74baa

Browse files
committed
fix: make optional route parameters optional in array type
Split KnownRouteParamsArray into RequiredParamsTuple and OptionalParamsTuple so that optional route parameters become optional tuple elements. This allows passing fewer array items when trailing parameters are optional, e.g. `route('posts.comments.show', [2])`. Fixes #736
1 parent 8a0b645 commit ce74baa

File tree

2 files changed

+27
-11
lines changed

2 files changed

+27
-11
lines changed

src/js/index.d.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,16 +114,28 @@ type RouteParamsObject<N extends RouteName> = N extends KnownRouteName
114114
type GenericRouteParamsArray = unknown[];
115115
/**
116116
* An array of parameters for a specific named route.
117+
*
118+
* Required parameters come first as required tuple elements, then optional
119+
* parameters become optional tuple elements, followed by arbitrary extras.
117120
*/
118121
type KnownRouteParamsArray<I extends readonly ParameterInfo[]> = [
119-
...{ [K in keyof I]: Routable<I[K]> },
120-
...unknown[],
122+
...RequiredParamsTuple<I>,
123+
...OptionalParamsTuple<I>,
121124
];
122-
// Because `K in keyof I` for a `readonly` array is always a number, even though this
123-
// looks like `{ 0: T, 1: U, 2: V }` TypeScript generates `[T, U, V]`. The nested
124-
// array destructing lets us type the first n items in the array, which are known
125-
// route parameters, and then allow arbitrary additional items.
126-
// See https://github.com/tighten/ziggy/pull/664#discussion_r1330002370.
125+
126+
type RequiredParamsTuple<I extends readonly ParameterInfo[]> =
127+
I extends readonly [infer F extends ParameterInfo, ...infer R extends ParameterInfo[]]
128+
? F extends { required: true }
129+
? [Routable<F>, ...RequiredParamsTuple<R>]
130+
: []
131+
: [];
132+
133+
type OptionalParamsTuple<I extends readonly ParameterInfo[]> =
134+
I extends readonly [infer F extends ParameterInfo, ...infer R extends ParameterInfo[]]
135+
? F extends { required: false }
136+
? [Routable<F>?, ...OptionalParamsTuple<R>]
137+
: OptionalParamsTuple<R>
138+
: [...unknown[]];
127139

128140
// Uncomment to test:
129141
// type B = KnownRouteParamsArray<[{ name: 'post'; required: true; binding: 'uuid' }]>;

tests/js/route.test-d.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,12 @@ assertType(route('posts.comments.show', 'foo'));
6565
assertType(route('posts.comments.show'));
6666

6767
// Simple array examples
68-
// assertType(route('posts.comments.show', [2])); // TODO shouldn't error, only one required param
68+
assertType(route('posts.comments.show', [2]));
6969
assertType(route('posts.comments.show', [2, 3]));
70-
// assertType(route('posts.comments.show', ['foo'])); // TODO shouldn't error, only one required param
70+
assertType(route('posts.comments.show', ['foo']));
7171
assertType(route('posts.comments.show', ['foo', 'bar']));
7272
// Allows mix of plain values and parameter objects
73-
// assertType(route('posts.comments.show', [{ id: 2 }])); // TODO shouldn't error, only one required param
73+
assertType(route('posts.comments.show', [{ id: 2 }]));
7474
assertType(route('posts.comments.show', [{ id: 2 }, 3]));
7575
assertType(route('posts.comments.show', ['2', { uuid: 3 }]));
7676
assertType(route('posts.comments.show', [{ id: 2 }, { uuid: '3' }]));
@@ -105,9 +105,13 @@ assertType(route().current('missing', { foo: 1 }));
105105
assertType(route().current('posts.comments.show', { comment: 2 }));
106106
assertType(route().current('posts.comments.show', { post: 2 }));
107107
assertType(route().current('posts.comments.show', 2));
108-
// assertType(route().current('posts.comments.show', [2])); // TODO shouldn't error, only one required param
108+
assertType(route().current('posts.comments.show', [2]));
109109
assertType(route().current('posts.comments.show', 'foo'));
110110

111+
// All-optional route with array params
112+
assertType(route('optional', []));
113+
assertType(route('optional', ['foo']));
114+
111115
// Test route function return types
112116
assertType<string>(route('optional', { maybe: 'foo' }));
113117
assertType<string>(route('optional', 'foo'));

0 commit comments

Comments
 (0)