@rangojs/router 0.0.0-experimental.29 → 0.0.0-experimental.30
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/vite/index.js +1 -1
- package/package.json +1 -1
- package/src/router/handler-context.ts +26 -2
- package/src/router/middleware-types.ts +10 -14
- package/src/router/middleware.ts +20 -24
- package/src/router/prerender-match.ts +2 -0
- package/src/server/request-context.ts +9 -7
- package/src/types/handler-context.ts +12 -16
package/dist/vite/index.js
CHANGED
|
@@ -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.
|
|
1748
|
+
version: "0.0.0-experimental.30",
|
|
1749
1749
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
1750
1750
|
keywords: [
|
|
1751
1751
|
"react",
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@ import { _getRequestContext } from "../server/request-context.js";
|
|
|
9
9
|
import { getSearchSchema, isRouteRootScoped } from "../route-map-builder.js";
|
|
10
10
|
import { parseSearchParams, serializeSearchParams } from "../search-params.js";
|
|
11
11
|
import { contextGet, contextSet } from "../context-var.js";
|
|
12
|
-
import { NOCACHE_SYMBOL } from "../cache/taint.js";
|
|
12
|
+
import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
|
|
13
13
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
14
14
|
import { PRERENDER_PASSTHROUGH } from "../prerender.js";
|
|
15
15
|
|
|
@@ -213,6 +213,24 @@ export function createHandlerContext<TEnv>(
|
|
|
213
213
|
const stubResponse =
|
|
214
214
|
requestContext?.res ?? new Response(null, { status: 200 });
|
|
215
215
|
|
|
216
|
+
// Guard mutating Headers methods so they throw inside "use cache" functions.
|
|
217
|
+
const MUTATING_HEADERS_METHODS = new Set(["set", "append", "delete"]);
|
|
218
|
+
const guardedHeaders = new Proxy(stubResponse.headers, {
|
|
219
|
+
get(target, prop, receiver) {
|
|
220
|
+
const value = Reflect.get(target, prop, receiver);
|
|
221
|
+
if (typeof value === "function") {
|
|
222
|
+
if (MUTATING_HEADERS_METHODS.has(prop as string)) {
|
|
223
|
+
return (...args: any[]) => {
|
|
224
|
+
assertNotInsideCacheExec(requestContext, "headers");
|
|
225
|
+
return value.apply(target, args);
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return value.bind(target);
|
|
229
|
+
}
|
|
230
|
+
return value;
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
|
|
216
234
|
const ctx: InternalHandlerContext<any, TEnv> = {
|
|
217
235
|
params,
|
|
218
236
|
build: false,
|
|
@@ -221,6 +239,7 @@ export function createHandlerContext<TEnv>(
|
|
|
221
239
|
search: searchSchema ? resolvedSearchParams : {},
|
|
222
240
|
pathname,
|
|
223
241
|
url,
|
|
242
|
+
originalUrl: new URL(request.url),
|
|
224
243
|
env: bindings,
|
|
225
244
|
var: variables,
|
|
226
245
|
get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as HandlerContext<
|
|
@@ -228,10 +247,11 @@ export function createHandlerContext<TEnv>(
|
|
|
228
247
|
TEnv
|
|
229
248
|
>["get"],
|
|
230
249
|
set: ((keyOrVar: any, value: any) => {
|
|
250
|
+
assertNotInsideCacheExec(requestContext, "set");
|
|
231
251
|
contextSet(variables, keyOrVar, value);
|
|
232
252
|
}) as HandlerContext<any, TEnv>["set"],
|
|
233
253
|
res: stubResponse, // Stub response for setting headers
|
|
234
|
-
headers:
|
|
254
|
+
headers: guardedHeaders, // Guarded shorthand for res.headers
|
|
235
255
|
// Placeholder use() - will be replaced with actual implementation during request
|
|
236
256
|
use: () => {
|
|
237
257
|
throw new Error("ctx.use() called before loaders were initialized");
|
|
@@ -304,6 +324,7 @@ export function createPrerenderContext<TEnv>(
|
|
|
304
324
|
search: {},
|
|
305
325
|
pathname,
|
|
306
326
|
url: syntheticUrl,
|
|
327
|
+
originalUrl: syntheticUrl,
|
|
307
328
|
get env(): TEnv {
|
|
308
329
|
return throwUnavailable("env");
|
|
309
330
|
},
|
|
@@ -385,6 +406,9 @@ export function createStaticContext<TEnv>(
|
|
|
385
406
|
get url(): URL {
|
|
386
407
|
return throwUnavailable("url");
|
|
387
408
|
},
|
|
409
|
+
get originalUrl(): URL {
|
|
410
|
+
return throwUnavailable("originalUrl");
|
|
411
|
+
},
|
|
388
412
|
get env(): TEnv {
|
|
389
413
|
return throwUnavailable("env");
|
|
390
414
|
},
|
|
@@ -57,9 +57,15 @@ export interface MiddlewareContext<
|
|
|
57
57
|
/** Original request */
|
|
58
58
|
request: Request;
|
|
59
59
|
|
|
60
|
-
/** Parsed URL */
|
|
60
|
+
/** Parsed URL (with internal `_rsc*` params stripped) */
|
|
61
61
|
url: URL;
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* The original request URL with all parameters intact, including
|
|
65
|
+
* internal `_rsc*` transport params.
|
|
66
|
+
*/
|
|
67
|
+
originalUrl: URL;
|
|
68
|
+
|
|
63
69
|
/** URL pathname */
|
|
64
70
|
pathname: string;
|
|
65
71
|
|
|
@@ -73,16 +79,7 @@ export interface MiddlewareContext<
|
|
|
73
79
|
params: TParams;
|
|
74
80
|
|
|
75
81
|
/**
|
|
76
|
-
* Response
|
|
77
|
-
* where headers and cookies accumulate. After `next()`, returns the downstream response.
|
|
78
|
-
*
|
|
79
|
-
* Use `ctx.header()` to set response headers, or `cookies()` for cookie mutations.
|
|
80
|
-
* To replace the response entirely, return a new `Response` from the middleware.
|
|
81
|
-
*/
|
|
82
|
-
readonly res: Response;
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Shorthand for ctx.res.headers — response headers.
|
|
82
|
+
* Response headers.
|
|
86
83
|
* Before `next()`, returns headers from the shared response stub.
|
|
87
84
|
* After `next()`, returns headers from the downstream response.
|
|
88
85
|
*/
|
|
@@ -101,11 +98,10 @@ export interface MiddlewareContext<
|
|
|
101
98
|
var: DefaultVars;
|
|
102
99
|
|
|
103
100
|
/**
|
|
104
|
-
* Set a response header - can be called before or after `next()
|
|
101
|
+
* Set a response header - can be called before or after `next()`.
|
|
105
102
|
*
|
|
106
103
|
* When called before `next()`, headers are queued and merged into the final response.
|
|
107
104
|
* When called after `next()`, headers are set directly on the response.
|
|
108
|
-
* Shorthand for `ctx.res.headers.set()`.
|
|
109
105
|
*/
|
|
110
106
|
header(name: string, value: string): void;
|
|
111
107
|
|
|
@@ -202,7 +198,7 @@ export interface MiddlewareEntry<TEnv = any> {
|
|
|
202
198
|
}
|
|
203
199
|
|
|
204
200
|
/**
|
|
205
|
-
* Mutable response holder -
|
|
201
|
+
* Mutable response holder - tracks the current response through the middleware chain.
|
|
206
202
|
*/
|
|
207
203
|
export interface ResponseHolder {
|
|
208
204
|
response: Response | null;
|
package/src/router/middleware.ts
CHANGED
|
@@ -163,9 +163,25 @@ export function createMiddlewareContext<TEnv>(
|
|
|
163
163
|
// Cookie operations are handled by the standalone cookies() function which
|
|
164
164
|
// delegates to the shared RequestContext internally.
|
|
165
165
|
// The runtime implementation - types are enforced at call sites via MiddlewareContext<TEnv>
|
|
166
|
+
// Internal helper: resolve the current response (stub before next(), real after).
|
|
167
|
+
// Not exposed on the public MiddlewareContext type — use ctx.headers instead.
|
|
168
|
+
const getResponse = (): Response => {
|
|
169
|
+
if (isPreNext()) {
|
|
170
|
+
const reqCtx = _getRequestContext();
|
|
171
|
+
if (reqCtx) return reqCtx.res;
|
|
172
|
+
}
|
|
173
|
+
if (!responseHolder.response) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
"Response is not available - responseHolder was not initialized",
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
return responseHolder.response;
|
|
179
|
+
};
|
|
180
|
+
|
|
166
181
|
return {
|
|
167
182
|
request,
|
|
168
183
|
url,
|
|
184
|
+
originalUrl: new URL(request.url),
|
|
169
185
|
pathname: url.pathname,
|
|
170
186
|
searchParams: url.searchParams,
|
|
171
187
|
env: env as MiddlewareContext<TEnv>["env"],
|
|
@@ -180,28 +196,8 @@ export function createMiddlewareContext<TEnv>(
|
|
|
180
196
|
) as MiddlewareContext<TEnv>["routeName"];
|
|
181
197
|
},
|
|
182
198
|
|
|
183
|
-
get res(): Response {
|
|
184
|
-
// Before next(): return shared RequestContext stub so headers
|
|
185
|
-
// set via ctx.header() are visible on ctx.res.
|
|
186
|
-
if (isPreNext()) {
|
|
187
|
-
const reqCtx = _getRequestContext();
|
|
188
|
-
if (reqCtx) return reqCtx.res;
|
|
189
|
-
}
|
|
190
|
-
if (!responseHolder.response) {
|
|
191
|
-
throw new Error(
|
|
192
|
-
"ctx.res is not available - responseHolder was not initialized",
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
return responseHolder.response;
|
|
196
|
-
},
|
|
197
|
-
set res(_: Response) {
|
|
198
|
-
throw new Error(
|
|
199
|
-
"ctx.res is read-only. Use ctx.header() to set response headers, or cookies() for cookie mutations.",
|
|
200
|
-
);
|
|
201
|
-
},
|
|
202
|
-
|
|
203
199
|
get headers(): Headers {
|
|
204
|
-
return
|
|
200
|
+
return getResponse().headers;
|
|
205
201
|
},
|
|
206
202
|
|
|
207
203
|
get: ((keyOrVar: any) =>
|
|
@@ -302,9 +298,9 @@ export function matchMiddleware<TEnv>(
|
|
|
302
298
|
*
|
|
303
299
|
* Features:
|
|
304
300
|
* - `await next()` returns actual Response
|
|
305
|
-
* - `ctx.
|
|
306
|
-
* - `ctx.header()` shorthand for setting
|
|
307
|
-
* - Forgiving: if middleware doesn't return, uses
|
|
301
|
+
* - `ctx.headers` available before and after `await next()`
|
|
302
|
+
* - `ctx.header()` shorthand for setting a single header
|
|
303
|
+
* - Forgiving: if middleware doesn't return, uses the downstream response
|
|
308
304
|
* - Short-circuit: return Response to stop chain
|
|
309
305
|
* - Error catching: try/catch around `next()` works
|
|
310
306
|
*/
|
|
@@ -101,6 +101,7 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
101
101
|
env: {} as TEnv,
|
|
102
102
|
request: new Request("http://prerender" + pathname),
|
|
103
103
|
url: new URL("http://prerender" + pathname),
|
|
104
|
+
originalUrl: new URL("http://prerender" + pathname),
|
|
104
105
|
pathname,
|
|
105
106
|
searchParams: new URLSearchParams(),
|
|
106
107
|
var: variables,
|
|
@@ -331,6 +332,7 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
331
332
|
env: {} as TEnv,
|
|
332
333
|
request: syntheticRequest,
|
|
333
334
|
url: syntheticUrl,
|
|
335
|
+
originalUrl: syntheticUrl,
|
|
334
336
|
pathname: "/",
|
|
335
337
|
searchParams: syntheticUrl.searchParams,
|
|
336
338
|
var: {},
|
|
@@ -49,8 +49,13 @@ export interface RequestContext<
|
|
|
49
49
|
env: TEnv;
|
|
50
50
|
/** Original HTTP request */
|
|
51
51
|
request: Request;
|
|
52
|
-
/** Parsed URL (
|
|
52
|
+
/** Parsed URL (with internal `_rsc*` params stripped) */
|
|
53
53
|
url: URL;
|
|
54
|
+
/**
|
|
55
|
+
* The original request URL with all parameters intact, including
|
|
56
|
+
* internal `_rsc*` transport params.
|
|
57
|
+
*/
|
|
58
|
+
originalUrl: URL;
|
|
54
59
|
/** URL pathname */
|
|
55
60
|
pathname: string;
|
|
56
61
|
/** URL search params (system params like _rsc* are NOT filtered here) */
|
|
@@ -72,12 +77,7 @@ export interface RequestContext<
|
|
|
72
77
|
* Initially empty, then set to matched params
|
|
73
78
|
*/
|
|
74
79
|
params: TParams;
|
|
75
|
-
/**
|
|
76
|
-
* Stub response for setting headers/cookies (read-only).
|
|
77
|
-
* Headers set here are merged into the final response.
|
|
78
|
-
* Use header() or setStatus() to mutate response headers/status.
|
|
79
|
-
* Use cookies().set()/cookies().delete() for cookie mutations.
|
|
80
|
-
*/
|
|
80
|
+
/** @internal Stub response for collecting headers/cookies. Use ctx.headers or ctx.header() instead. */
|
|
81
81
|
readonly res: Response;
|
|
82
82
|
|
|
83
83
|
/** @internal Get a cookie value (effective: request + response mutations). Use cookies().get() instead. */
|
|
@@ -301,6 +301,7 @@ export type PublicRequestContext<
|
|
|
301
301
|
| "_reportBackgroundError"
|
|
302
302
|
| "_debugPerformance"
|
|
303
303
|
| "_metricsStore"
|
|
304
|
+
| "res"
|
|
304
305
|
>;
|
|
305
306
|
|
|
306
307
|
// AsyncLocalStorage instance for request context
|
|
@@ -556,6 +557,7 @@ export function createRequestContext<TEnv>(
|
|
|
556
557
|
env,
|
|
557
558
|
request,
|
|
558
559
|
url,
|
|
560
|
+
originalUrl: new URL(request.url),
|
|
559
561
|
pathname: url.pathname,
|
|
560
562
|
searchParams: url.searchParams,
|
|
561
563
|
var: variables,
|
|
@@ -228,9 +228,17 @@ export type HandlerContext<
|
|
|
228
228
|
*/
|
|
229
229
|
pathname: string;
|
|
230
230
|
/**
|
|
231
|
-
* The full URL object (with
|
|
231
|
+
* The full URL object (with internal `_rsc*` params stripped).
|
|
232
|
+
* Use this for application logic — routing, link generation, display.
|
|
232
233
|
*/
|
|
233
234
|
url: URL;
|
|
235
|
+
/**
|
|
236
|
+
* The original request URL with all parameters intact, including
|
|
237
|
+
* internal `_rsc*` transport params. Use `ctx.url` for application
|
|
238
|
+
* logic — this is only needed for advanced cases like debugging
|
|
239
|
+
* or custom cache keying.
|
|
240
|
+
*/
|
|
241
|
+
originalUrl: URL;
|
|
234
242
|
/**
|
|
235
243
|
* Platform bindings (DB, KV, secrets, etc.).
|
|
236
244
|
* Access resources like `ctx.env.DB`, `ctx.env.KV`.
|
|
@@ -267,21 +275,7 @@ export type HandlerContext<
|
|
|
267
275
|
<T>(contextVar: ContextVar<T>, value: T): void;
|
|
268
276
|
} & (<K extends keyof DefaultVars>(key: K, value: DefaultVars[K]) => void);
|
|
269
277
|
/**
|
|
270
|
-
*
|
|
271
|
-
* Headers set here are merged into the final response.
|
|
272
|
-
*
|
|
273
|
-
* @example
|
|
274
|
-
* ```typescript
|
|
275
|
-
* route("product", (ctx) => {
|
|
276
|
-
* ctx.res.headers.set("Cache-Control", "s-maxage=60");
|
|
277
|
-
* return <ProductPage />;
|
|
278
|
-
* });
|
|
279
|
-
* ```
|
|
280
|
-
*/
|
|
281
|
-
res: Response;
|
|
282
|
-
/**
|
|
283
|
-
* Shorthand for ctx.res.headers - response headers.
|
|
284
|
-
* Headers set here are merged into the final response.
|
|
278
|
+
* Response headers. Headers set here are merged into the final response.
|
|
285
279
|
*
|
|
286
280
|
* @example
|
|
287
281
|
* ```typescript
|
|
@@ -436,6 +430,8 @@ export type InternalHandlerContext<
|
|
|
436
430
|
TEnv = DefaultEnv,
|
|
437
431
|
TSearch extends SearchSchema = {},
|
|
438
432
|
> = HandlerContext<TParams, TEnv, TSearch> & {
|
|
433
|
+
/** @internal Stub response for collecting headers/cookies. */
|
|
434
|
+
res: Response;
|
|
439
435
|
/** Prerender-only control flow helper, attached when the runtime context supports it. */
|
|
440
436
|
passthrough?: () => unknown;
|
|
441
437
|
/** Current segment ID for handle data attribution. */
|