@rangojs/router 0.0.0-experimental.59 → 0.0.0-experimental.60

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.
@@ -1745,7 +1745,7 @@ import { resolve } from "node:path";
1745
1745
  // package.json
1746
1746
  var package_default = {
1747
1747
  name: "@rangojs/router",
1748
- version: "0.0.0-experimental.59",
1748
+ version: "0.0.0-experimental.60",
1749
1749
  description: "Django-inspired RSC router with composable URL patterns",
1750
1750
  keywords: [
1751
1751
  "react",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rangojs/router",
3
- "version": "0.0.0-experimental.59",
3
+ "version": "0.0.0-experimental.60",
4
4
  "description": "Django-inspired RSC router with composable URL patterns",
5
5
  "keywords": [
6
6
  "react",
package/src/reverse.ts CHANGED
@@ -305,8 +305,22 @@ export function createReverse<TRoutes extends Record<string, string>>(
305
305
  if (params) {
306
306
  // Replace :param placeholders with actual values
307
307
  // Strip constraint syntax: :param(a|b) -> use "param" as key
308
+ // Optional params (:param?) are omitted when not provided
309
+ let hadOmittedOptional = false;
308
310
  result = result.replace(
309
- /:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\??/g,
311
+ /:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?(\?)/g,
312
+ (_, key, _constraint, optional) => {
313
+ const value = params[key];
314
+ if (value === undefined) {
315
+ hadOmittedOptional = true;
316
+ return "";
317
+ }
318
+ return encodeURIComponent(value);
319
+ },
320
+ );
321
+ // Second pass: required params (no trailing ?)
322
+ result = result.replace(
323
+ /:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?(?!\?)/g,
310
324
  (_, key) => {
311
325
  const value = params[key];
312
326
  if (value === undefined) {
@@ -315,6 +329,11 @@ export function createReverse<TRoutes extends Record<string, string>>(
315
329
  return encodeURIComponent(value);
316
330
  },
317
331
  );
332
+ // Clean up slashes only when an optional param was actually omitted,
333
+ // so intentional trailing-slash patterns like "/blog/" are preserved.
334
+ if (hadOmittedOptional) {
335
+ result = result.replace(/\/\/+/g, "/").replace(/\/+$/, "") || "/";
336
+ }
318
337
  }
319
338
 
320
339
  // Append search params as query string
@@ -166,9 +166,24 @@ export function createReverseFunction(
166
166
  : hrefParams;
167
167
 
168
168
  // Substitute params (strip constraint and optional syntax: :param(a|b)? -> value)
169
+ // Optional params (:param?) are omitted when not provided
169
170
  if (effectiveParams) {
171
+ let hadOmittedOptional = false;
172
+ // First pass: optional params (trailing ?)
170
173
  result = result.replace(
171
- /:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\??/g,
174
+ /:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?(\?)/g,
175
+ (_, key) => {
176
+ const value = effectiveParams[key];
177
+ if (value === undefined) {
178
+ hadOmittedOptional = true;
179
+ return "";
180
+ }
181
+ return encodeURIComponent(value);
182
+ },
183
+ );
184
+ // Second pass: required params (no trailing ?)
185
+ result = result.replace(
186
+ /:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?(?!\?)/g,
172
187
  (_, key) => {
173
188
  const value = effectiveParams[key];
174
189
  if (value === undefined) {
@@ -177,6 +192,11 @@ export function createReverseFunction(
177
192
  return encodeURIComponent(value);
178
193
  },
179
194
  );
195
+ // Clean up slashes only when an optional param was actually omitted,
196
+ // so intentional trailing-slash patterns like "/blog/" are preserved.
197
+ if (hadOmittedOptional) {
198
+ result = result.replace(/\/\/+/g, "/").replace(/\/+$/, "") || "/";
199
+ }
180
200
  }
181
201
 
182
202
  // Append search params as query string