Request: Allow paths that are not patterns (no parameters)
Closed this issue · 5 comments
I have a number of paths in my application that are not patterns. For example:
import {path} from 'static-path';
export const featureRoot = '/feature';
export const featureSubRoute = path(`${featureRoot}/thing/:thingId`);
This leads to mixed usage of pathName
vs pathName.pattern
, pathName()
, and pathName.path()
.
Desired usage:
import {path} from 'static-path';
export const featureRoot = path('/feature');
export const featureSubRoute = featureRoot.path('/thing/:thingId');
Would love to hear your thoughts.
Your desired code works as expected:
export const featureRoot = path('/feature');
export const featureSubRoute = featureRoot.path('/thing/:thingId');
console.log(featureRoot({}))
console.log(featureSubRoute({thingId: "1"}))
// Prints:
// /feature
// /feature/thing/1
For featureRoot({})
, featureRoot()
is not allowed. Would it be possible to not require the empty object?
A Path
is a hybrid type parameterized on the pattern's literal string type:
/* A Path is parameterized only on its pattern. If we ever need the param
* types, we can use helper types to create them from the pattern type. */
export type Path<Pattern extends string> = {
(params: Params<Pattern>): string;
pattern: NormalizePattern<Pattern>;
parts: Part<Pattern>[];
path: <Subpattern extends string>(subpattern: Subpattern) => Path<`${Pattern}/${Subpattern}`>;
};
The signature part of that ((params: Params<Pattern>): string
) is where it accepts the params object and generates a concrete path string. Two possibilities come to mind:
- Overload
Path
's hybrid type to require an object argument all of the time, except when the params type is exactly{}
. I don't think that's possible in TS. - Have the main
path
constructor function return a different type for paths that have no params. This could probably work, but I think it would add a lot of complexity. Probably too much complexity to justify itself. But I could be wrong!
I may be missing other possibilities. Feel free to give it a try and submit a PR if you want feedback on a potential solution.
Here's what I've come up with by amending Params
:
type Params<Pattern extends string> = string extends Pattern
? never
: PathParamNames<Pattern> extends never
? void
: { [K in PathParamNames<Pattern>]: string; }
}
const courses = path('/course');
courses({});
// Argument of type '{}' is not assignable to parameter of type 'void'.
courses();
// Ok
I additionally tried adding support for optional parameters (despite React Router v6 dropping support):
type RequiredParam<ParamName> = ParamName extends `${infer Name}?` ? never : ParamName;
type OptionalParam<ParamName> = ParamName extends `${infer Name}?` ? Name : never;
type Params<Pattern extends string> = string extends Pattern
? never
: PathParamNames<Pattern> extends never
? void
: { [K in RequiredParam<PathParamNames<Pattern>>]: string; } & { [K in OptionalParam<PathParamNames<Pattern>>]?: string; };
const courses = path('/course');
const course = courses.path(':courseId/:optional?');
course();
// Expected 1 arguments, but got 0.
course({});
// Property 'courseId' is missing in type '{}' but required in type '{ courseId: string; }'.
course({optional: '1'})
// Property 'courseId' is missing in type '{ optional: string; }' but required in type '{ courseId: string; }'.
course({courseId: '1'});
// Ok
course({courseId: '1', optional: '1'})
// Ok
I'm not as well versed in TypeScript as I'd like, so I'm not entirely sure if PathParamNames<Pattern> extends never
is valid ("is never?"). I've also seen [T] extends [never]
here, not sure the difference.
Closing as I've now done the proper thing by checking out the repo and better understanding the limitations here. If I can come up with something that isn't too complex for what it's worth, I'll open a PR. I may still open a PR for optional params support.