paramour 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/describe.d.ts +5 -0
- package/dist/describe.js +1 -0
- package/dist/path.d.ts +9 -0
- package/dist/path.js +14 -2
- package/dist/route.d.ts +25 -3
- package/dist/route.js +7 -1
- package/package.json +1 -1
package/dist/describe.d.ts
CHANGED
|
@@ -45,6 +45,11 @@ export interface RouteDescription {
|
|
|
45
45
|
readonly path: string;
|
|
46
46
|
readonly router: RouterKind;
|
|
47
47
|
readonly search: SearchDescription;
|
|
48
|
+
/**
|
|
49
|
+
* Present (and `true`) only for a route defined with `trailingSlash: true`,
|
|
50
|
+
* so descriptions of R6-default routes are unchanged.
|
|
51
|
+
*/
|
|
52
|
+
readonly trailingSlash?: true;
|
|
48
53
|
}
|
|
49
54
|
/**
|
|
50
55
|
* The `search:` slot's three shapes: absent config, a codec map, or the
|
package/dist/describe.js
CHANGED
package/dist/path.d.ts
CHANGED
|
@@ -61,6 +61,15 @@ export type PathSegment = {
|
|
|
61
61
|
* joined with `/`. R2's element joining falls out of the same join as
|
|
62
62
|
* everything else; a fully-elided path (an optional catch-all at the root)
|
|
63
63
|
* yields "/".
|
|
64
|
+
*
|
|
65
|
+
* R6's one exception lives here, not in href, so `buildPath` and `href`
|
|
66
|
+
* always agree on the path: a route defined with `trailingSlash: true` gets a
|
|
67
|
+
* "/" after its last segment. The slash goes on the BUILT path, after any
|
|
68
|
+
* elision, so an elided optional catch-all yields "/docs/" and the root stays
|
|
69
|
+
* "/" rather than "//". It is a route-definition fact, not a Next config read,
|
|
70
|
+
* because route modules also run where no Next config exists (a CLI printing
|
|
71
|
+
* absolute links); a static export with `trailingSlash: true` serves exactly
|
|
72
|
+
* these URLs without a redirect.
|
|
64
73
|
*/
|
|
65
74
|
export declare function buildPath<R extends AnyRoute>(route: R, params: InferParamsInput<R>): string;
|
|
66
75
|
/**
|
package/dist/path.js
CHANGED
|
@@ -13,9 +13,19 @@ const GROUP_SEGMENT = /^\(.*\)$/;
|
|
|
13
13
|
* joined with `/`. R2's element joining falls out of the same join as
|
|
14
14
|
* everything else; a fully-elided path (an optional catch-all at the root)
|
|
15
15
|
* yields "/".
|
|
16
|
+
*
|
|
17
|
+
* R6's one exception lives here, not in href, so `buildPath` and `href`
|
|
18
|
+
* always agree on the path: a route defined with `trailingSlash: true` gets a
|
|
19
|
+
* "/" after its last segment. The slash goes on the BUILT path, after any
|
|
20
|
+
* elision, so an elided optional catch-all yields "/docs/" and the root stays
|
|
21
|
+
* "/" rather than "//". It is a route-definition fact, not a Next config read,
|
|
22
|
+
* because route modules also run where no Next config exists (a CLI printing
|
|
23
|
+
* absolute links); a static export with `trailingSlash: true` serves exactly
|
|
24
|
+
* these URLs without a redirect.
|
|
16
25
|
*/
|
|
17
26
|
export function buildPath(route, params) {
|
|
18
|
-
|
|
27
|
+
const path = `/${encodeParams(route, params).join("/")}`;
|
|
28
|
+
return route["~trailingSlash"] && path !== "/" ? `${path}/` : path;
|
|
19
29
|
}
|
|
20
30
|
/**
|
|
21
31
|
* Decodes a params source against a route's codecs, the sync twin of
|
|
@@ -308,7 +318,9 @@ export function tokenizePath(path) {
|
|
|
308
318
|
throw new ParamourError(`route path must start with "/": "${path}"`);
|
|
309
319
|
}
|
|
310
320
|
if (path !== "/" && path.endsWith("/")) {
|
|
311
|
-
|
|
321
|
+
// The literal is the route's identity (registry key, Href brand), so it
|
|
322
|
+
// never carries the slash; the built URL can, via the define option.
|
|
323
|
+
throw new ParamourError(`route path must not end with "/": "${path}" (to build links with a trailing slash, pass trailingSlash: true in the route config)`);
|
|
312
324
|
}
|
|
313
325
|
if (path === "/")
|
|
314
326
|
return [];
|
package/dist/route.d.ts
CHANGED
|
@@ -226,17 +226,26 @@ export interface Route<Path extends string, PC extends ParamsConfig<Path>, SC ex
|
|
|
226
226
|
* registry — tree-shaking is untouched.
|
|
227
227
|
*/
|
|
228
228
|
readonly "~segments": readonly PathSegment[];
|
|
229
|
+
/**
|
|
230
|
+
* The define-time `trailingSlash` option (R6's exception): `buildPath`, and
|
|
231
|
+
* so `href`, append "/" to every non-root path when true. Required, not
|
|
232
|
+
* optional: an optional member here turns the wrong-router diagnostics
|
|
233
|
+
* (an AppRoute passed where AnyPagesRoute is expected) into
|
|
234
|
+
* exactOptionalPropertyTypes noise. Readers test it for truthiness, so a
|
|
235
|
+
* plain-JS hand-built route without it gets the R6 default.
|
|
236
|
+
*/
|
|
237
|
+
readonly "~trailingSlash": boolean;
|
|
229
238
|
}
|
|
230
239
|
/**
|
|
231
240
|
* Conditional on the path shape: dynamic paths REQUIRE `params` with exactly
|
|
232
241
|
* the extracted segment names; static paths REJECT it (`?: never` — may be
|
|
233
242
|
* absent, may never be present, which under exactOptionalPropertyTypes holds
|
|
234
|
-
* even for non-fresh objects).
|
|
243
|
+
* even for non-fresh objects). `trailingSlash` is the same on both branches.
|
|
235
244
|
*/
|
|
236
|
-
export type RouteConfig<Path extends string, PC extends ParamsConfig<Path>, SC extends SearchSlot> = [PathParamNames<Path>] extends [never] ? {
|
|
245
|
+
export type RouteConfig<Path extends string, PC extends ParamsConfig<Path>, SC extends SearchSlot> = [PathParamNames<Path>] extends [never] ? TrailingSlashOption & {
|
|
237
246
|
readonly params?: never;
|
|
238
247
|
readonly search?: SC;
|
|
239
|
-
} : {
|
|
248
|
+
} : TrailingSlashOption & {
|
|
240
249
|
readonly params: ConformParams<Path, PC>;
|
|
241
250
|
readonly search?: SC;
|
|
242
251
|
};
|
|
@@ -310,6 +319,19 @@ type PresentRegisteredPaths = (ParamourRegister extends {
|
|
|
310
319
|
* and decode expectations, so it is excluded here.
|
|
311
320
|
*/
|
|
312
321
|
type StaticPathsOf<P extends string> = P extends `${string}[${string}` ? never : P;
|
|
322
|
+
/**
|
|
323
|
+
* The route-config option behind R6's exception. Not exported on its own:
|
|
324
|
+
* it is part of {@link RouteConfig}, the type wrappers should name.
|
|
325
|
+
*/
|
|
326
|
+
interface TrailingSlashOption {
|
|
327
|
+
/**
|
|
328
|
+
* Build every non-root path with a trailing slash: `/asset/`,
|
|
329
|
+
* `/docs/a/b/`, `/asset/?q=1#top`; the root stays `/`. Set it to match
|
|
330
|
+
* `trailingSlash` in `next.config` (`paramour doctor` warns when they
|
|
331
|
+
* disagree). Defaults to `false`, wire-format rule R6.
|
|
332
|
+
*/
|
|
333
|
+
readonly trailingSlash?: boolean;
|
|
334
|
+
}
|
|
313
335
|
/**
|
|
314
336
|
* Defines an App Router route: the URL-shaped path literal plus its
|
|
315
337
|
* param/search codec configs. Validates the literal eagerly — fail-fast at
|
package/dist/route.js
CHANGED
|
@@ -161,13 +161,19 @@ async function rebrandRejection(promise) {
|
|
|
161
161
|
*/
|
|
162
162
|
function routeData(router, path, config) {
|
|
163
163
|
const segments = tokenizePath(path);
|
|
164
|
-
const { params, search } = config;
|
|
164
|
+
const { params, search, trailingSlash } = config;
|
|
165
|
+
// Plain-JS backstop, fail-fast like the literal checks: a truthy string
|
|
166
|
+
// ("false") read loosely would silently change every link the route builds.
|
|
167
|
+
if (trailingSlash !== undefined && typeof trailingSlash !== "boolean") {
|
|
168
|
+
throw new ParamourError(`route "${path}": trailingSlash must be a boolean, got ${describeType(trailingSlash)}`);
|
|
169
|
+
}
|
|
165
170
|
return {
|
|
166
171
|
path,
|
|
167
172
|
"~params": params ?? {},
|
|
168
173
|
"~router": router,
|
|
169
174
|
"~search": search ?? {},
|
|
170
175
|
"~segments": segments,
|
|
176
|
+
"~trailingSlash": trailingSlash ?? false,
|
|
171
177
|
};
|
|
172
178
|
}
|
|
173
179
|
/**
|