@rangojs/router 0.0.0-experimental.79 → 0.0.0-experimental.7d061845
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/README.md +120 -25
- package/dist/bin/rango.js +147 -57
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +2138 -841
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +68 -21
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +220 -30
- package/skills/caching/SKILL.md +116 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +3 -1
- package/skills/hooks/SKILL.md +229 -20
- package/skills/host-router/SKILL.md +45 -20
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +26 -4
- package/skills/layout/SKILL.md +6 -7
- package/skills/links/SKILL.md +247 -17
- package/skills/loader/SKILL.md +219 -9
- package/skills/middleware/SKILL.md +15 -9
- package/skills/migrate-nextjs/SKILL.md +4 -2
- package/skills/migrate-react-router/SKILL.md +5 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +12 -6
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +242 -24
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +66 -9
- package/skills/route/SKILL.md +33 -4
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +751 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/testing/SKILL.md +816 -0
- package/skills/typesafety/SKILL.md +319 -27
- package/skills/use-cache/SKILL.md +34 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +117 -0
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/event-controller.ts +86 -70
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/navigation-bridge.ts +65 -9
- package/src/browser/navigation-client.ts +45 -25
- package/src/browser/navigation-store.ts +32 -9
- package/src/browser/navigation-transaction.ts +10 -28
- package/src/browser/partial-update.ts +52 -26
- package/src/browser/prefetch/cache.ts +124 -26
- package/src/browser/prefetch/fetch.ts +114 -38
- package/src/browser/prefetch/queue.ts +36 -5
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +18 -13
- package/src/browser/react/NavigationProvider.tsx +72 -31
- package/src/browser/react/filter-segment-order.ts +51 -7
- package/src/browser/react/index.ts +3 -0
- package/src/browser/react/location-state-shared.ts +175 -4
- package/src/browser/react/location-state.ts +39 -13
- package/src/browser/react/use-handle.ts +17 -9
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +20 -8
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +22 -2
- package/src/browser/react/use-segments.ts +11 -8
- package/src/browser/response-adapter.ts +25 -0
- package/src/browser/rsc-router.tsx +64 -22
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +23 -30
- package/src/browser/types.ts +21 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +2 -0
- package/src/build/index.ts +2 -0
- package/src/build/route-trie.ts +2 -1
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +55 -14
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-scope.ts +28 -42
- package/src/cache/cf/cf-cache-store.ts +54 -13
- package/src/client.rsc.tsx +3 -0
- package/src/client.tsx +10 -8
- package/src/context-var.ts +5 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -1
- package/src/handle.ts +26 -13
- package/src/host/index.ts +2 -2
- package/src/host/router.ts +129 -57
- package/src/host/types.ts +31 -2
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +140 -20
- package/src/index.rsc.ts +9 -4
- package/src/index.ts +16 -6
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +21 -6
- package/src/loader.ts +3 -10
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/prerender.ts +4 -4
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -39
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +253 -265
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +43 -15
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -2
- package/src/router/error-handling.ts +1 -1
- package/src/router/handler-context.ts +21 -41
- package/src/router/intercept-resolution.ts +4 -18
- package/src/router/lazy-includes.ts +3 -3
- package/src/router/loader-resolution.ts +19 -2
- package/src/router/match-api.ts +4 -3
- package/src/router/match-handlers.ts +63 -20
- package/src/router/match-middleware/cache-lookup.ts +44 -91
- package/src/router/match-middleware/cache-store.ts +3 -2
- package/src/router/match-result.ts +53 -32
- package/src/router/metrics.ts +1 -1
- package/src/router/middleware-types.ts +15 -26
- package/src/router/middleware.ts +99 -84
- package/src/router/pattern-matching.ts +101 -17
- package/src/router/prerender-match.ts +1 -1
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +4 -28
- package/src/router/revalidation.ts +58 -2
- package/src/router/router-interfaces.ts +45 -28
- package/src/router/router-options.ts +40 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +27 -6
- package/src/router/segment-resolution/revalidation.ts +147 -106
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry.ts +99 -0
- package/src/router/trie-matching.ts +18 -13
- package/src/router/types.ts +8 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +38 -23
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +28 -69
- package/src/rsc/helpers.ts +91 -43
- package/src/rsc/index.ts +1 -1
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/progressive-enhancement.ts +4 -0
- package/src/rsc/response-route-handler.ts +46 -53
- package/src/rsc/rsc-rendering.ts +35 -51
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +17 -37
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +8 -2
- package/src/search-params.ts +4 -4
- package/src/segment-system.tsx +122 -56
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +118 -51
- package/src/server/cookie-store.ts +28 -4
- package/src/server/request-context.ts +20 -42
- package/src/ssr/index.tsx +5 -1
- package/src/static-handler.ts +1 -1
- package/src/testing/cache-status.ts +166 -0
- package/src/testing/collect-handle.ts +63 -0
- package/src/testing/dispatch.ts +440 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +154 -0
- package/src/testing/e2e/index.ts +149 -0
- package/src/testing/e2e/matchers.ts +51 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +306 -0
- package/src/testing/e2e/server.ts +183 -0
- package/src/testing/flight-matchers.ts +104 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +332 -0
- package/src/testing/flight.entry.ts +46 -0
- package/src/testing/flight.ts +224 -0
- package/src/testing/generated-routes.ts +223 -0
- package/src/testing/index.ts +106 -0
- package/src/testing/internal/context.ts +304 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +42 -0
- package/src/testing/render-handler.ts +267 -0
- package/src/testing/render-route.tsx +565 -0
- package/src/testing/run-loader.ts +341 -0
- package/src/testing/run-middleware.ts +188 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +270 -0
- package/src/types/global-namespace.ts +39 -26
- package/src/types/handler-context.ts +68 -50
- package/src/types/index.ts +1 -0
- package/src/types/loader-types.ts +5 -6
- package/src/types/request-scope.ts +126 -0
- package/src/types/segments.ts +35 -1
- package/src/urls/include-helper.ts +10 -53
- package/src/urls/index.ts +0 -3
- package/src/urls/path-helper-types.ts +11 -3
- package/src/urls/path-helper.ts +17 -52
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +26 -116
- package/src/urls/urls-function.ts +1 -5
- package/src/use-loader.tsx +413 -42
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +6 -6
- package/src/vite/discovery/discover-routers.ts +101 -51
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +67 -26
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +33 -0
- package/src/vite/discovery/virtual-module-codegen.ts +13 -23
- package/src/vite/index.ts +2 -0
- package/src/vite/plugin-types.ts +67 -0
- package/src/vite/plugins/cjs-to-esm.ts +8 -7
- package/src/vite/plugins/client-ref-dedup.ts +16 -0
- package/src/vite/plugins/client-ref-hashing.ts +28 -5
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/plugins/expose-action-id.ts +54 -30
- package/src/vite/plugins/expose-id-utils.ts +12 -8
- package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
- package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
- package/src/vite/plugins/expose-internal-ids.ts +496 -486
- package/src/vite/plugins/performance-tracks.ts +29 -25
- package/src/vite/plugins/use-cache-transform.ts +65 -50
- package/src/vite/plugins/version-injector.ts +39 -23
- package/src/vite/plugins/version-plugin.ts +59 -2
- package/src/vite/plugins/virtual-entries.ts +2 -2
- package/src/vite/rango.ts +116 -29
- package/src/vite/router-discovery.ts +750 -100
- package/src/vite/utils/ast-handler-extract.ts +15 -15
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +4 -2
- package/src/vite/utils/client-chunks.ts +190 -0
- package/src/vite/utils/forward-user-plugins.ts +193 -0
- package/src/vite/utils/manifest-utils.ts +21 -5
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +5 -4
- package/src/vite/utils/shared-utils.ts +107 -26
- package/src/browser/action-response-classifier.ts +0 -99
package/src/cache/cache-scope.ts
CHANGED
|
@@ -187,6 +187,32 @@ export class CacheScope {
|
|
|
187
187
|
return resolveCacheKey(keyFn, this.getStore(), defaultKey, "CacheScope");
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Evaluate the cache `condition` predicate. Returns false (skip the cache
|
|
192
|
+
* operation) when the predicate returns false or throws; returns true when
|
|
193
|
+
* there is no condition or no request context to evaluate it against.
|
|
194
|
+
*/
|
|
195
|
+
private conditionAllows(op: "read" | "write"): boolean {
|
|
196
|
+
if (this.config === false || !this.config.condition) return true;
|
|
197
|
+
const requestCtx = getRequestContext();
|
|
198
|
+
if (!requestCtx) return true;
|
|
199
|
+
try {
|
|
200
|
+
if (!this.config.condition(requestCtx)) {
|
|
201
|
+
debugCacheLog(
|
|
202
|
+
`[CacheScope] condition returned false, skipping cache ${op}`,
|
|
203
|
+
);
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
return true;
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error(
|
|
209
|
+
`[CacheScope] condition function threw, skipping cache ${op}:`,
|
|
210
|
+
error,
|
|
211
|
+
);
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
190
216
|
/**
|
|
191
217
|
* Lookup cached segments for a route (single cache entry per request).
|
|
192
218
|
* Returns { segments, shouldRevalidate } or null if cache miss.
|
|
@@ -204,27 +230,7 @@ export class CacheScope {
|
|
|
204
230
|
shouldRevalidate: boolean;
|
|
205
231
|
} | null> {
|
|
206
232
|
if (!this.enabled) return null;
|
|
207
|
-
|
|
208
|
-
// Evaluate condition — skip cache read when condition returns false
|
|
209
|
-
if (this.config !== false && this.config.condition) {
|
|
210
|
-
const requestCtx = getRequestContext();
|
|
211
|
-
if (requestCtx) {
|
|
212
|
-
try {
|
|
213
|
-
if (!this.config.condition(requestCtx)) {
|
|
214
|
-
debugCacheLog(
|
|
215
|
-
`[CacheScope] condition returned false, skipping cache read`,
|
|
216
|
-
);
|
|
217
|
-
return null;
|
|
218
|
-
}
|
|
219
|
-
} catch (error) {
|
|
220
|
-
console.error(
|
|
221
|
-
`[CacheScope] condition function threw, skipping cache read:`,
|
|
222
|
-
error,
|
|
223
|
-
);
|
|
224
|
-
return null;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
233
|
+
if (!this.conditionAllows("read")) return null;
|
|
228
234
|
|
|
229
235
|
const store = this.getStore();
|
|
230
236
|
if (!store) return null;
|
|
@@ -284,27 +290,7 @@ export class CacheScope {
|
|
|
284
290
|
isIntercept?: boolean,
|
|
285
291
|
): Promise<void> {
|
|
286
292
|
if (!this.enabled || segments.length === 0) return;
|
|
287
|
-
|
|
288
|
-
// Evaluate condition — skip cache write when condition returns false
|
|
289
|
-
if (this.config !== false && this.config.condition) {
|
|
290
|
-
const conditionCtx = getRequestContext();
|
|
291
|
-
if (conditionCtx) {
|
|
292
|
-
try {
|
|
293
|
-
if (!this.config.condition(conditionCtx)) {
|
|
294
|
-
debugCacheLog(
|
|
295
|
-
`[CacheScope] condition returned false, skipping cache write`,
|
|
296
|
-
);
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
} catch (error) {
|
|
300
|
-
console.error(
|
|
301
|
-
`[CacheScope] condition function threw, skipping cache write:`,
|
|
302
|
-
error,
|
|
303
|
-
);
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
293
|
+
if (!this.conditionAllows("write")) return;
|
|
308
294
|
|
|
309
295
|
const store = this.getStore();
|
|
310
296
|
if (!store) return;
|
|
@@ -56,6 +56,15 @@ export const CACHE_STALE_AT_HEADER = "x-edge-cache-stale-at";
|
|
|
56
56
|
/** Header storing cache status: HIT | REVALIDATING */
|
|
57
57
|
export const CACHE_STATUS_HEADER = "x-edge-cache-status";
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Header stashing the route author's original Cache-Control on L1 document
|
|
61
|
+
* entries. putResponse/promoteResponseToL1 overwrite Cache-Control with a long
|
|
62
|
+
* `max-age` so the CF Cache API retains the entry across the whole SWR window;
|
|
63
|
+
* getResponse restores this original value before serving so the client and any
|
|
64
|
+
* upstream CDN see the author's intended directive, not the internal edge TTL.
|
|
65
|
+
*/
|
|
66
|
+
const CACHE_ORIG_CC_HEADER = "x-edge-cache-orig-cc";
|
|
67
|
+
|
|
59
68
|
/**
|
|
60
69
|
* Maximum age in seconds for REVALIDATING status before allowing new revalidation.
|
|
61
70
|
* After this period, a stale entry in REVALIDATING status will trigger revalidation again.
|
|
@@ -67,13 +76,11 @@ export const MAX_REVALIDATION_INTERVAL = 30;
|
|
|
67
76
|
// Types
|
|
68
77
|
// ============================================================================
|
|
69
78
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
export
|
|
74
|
-
|
|
75
|
-
passThroughOnException(): void;
|
|
76
|
-
}
|
|
79
|
+
// Re-exported from the canonical home so cf-cache-store consumers keep
|
|
80
|
+
// importing `ExecutionContext` from this module without a second interface
|
|
81
|
+
// drifting over time.
|
|
82
|
+
export type { ExecutionContext } from "../../types/request-scope.js";
|
|
83
|
+
import type { ExecutionContext } from "../../types/request-scope.js";
|
|
77
84
|
|
|
78
85
|
/**
|
|
79
86
|
* Minimal Cloudflare KV Namespace interface.
|
|
@@ -184,7 +191,7 @@ export interface CFCacheStoreOptions<TEnv = unknown> {
|
|
|
184
191
|
* Cache version string override. When this changes, all cached entries are
|
|
185
192
|
* effectively invalidated (new keys won't match old entries).
|
|
186
193
|
*
|
|
187
|
-
* Defaults to the auto-generated VERSION from
|
|
194
|
+
* Defaults to the auto-generated VERSION from the `@rangojs/router:version` virtual module.
|
|
188
195
|
* Only set this if you need a custom versioning strategy.
|
|
189
196
|
*/
|
|
190
197
|
version?: string;
|
|
@@ -421,7 +428,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
421
428
|
}
|
|
422
429
|
|
|
423
430
|
// L2: persist to KV
|
|
424
|
-
this.kvSetSegment(key, data, staleAt, totalTtl);
|
|
431
|
+
this.kvSetSegment(key, data, staleAt, totalTtl, swrWindow);
|
|
425
432
|
} catch (error) {
|
|
426
433
|
console.error("[CFCacheStore] set failed:", error);
|
|
427
434
|
}
|
|
@@ -480,7 +487,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
480
487
|
const isStale = staleAt > 0 && Date.now() > staleAt;
|
|
481
488
|
|
|
482
489
|
return {
|
|
483
|
-
response,
|
|
490
|
+
response: this.toClientResponse(response),
|
|
484
491
|
shouldRevalidate: isStale,
|
|
485
492
|
};
|
|
486
493
|
} catch (error) {
|
|
@@ -489,6 +496,30 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
489
496
|
}
|
|
490
497
|
}
|
|
491
498
|
|
|
499
|
+
/**
|
|
500
|
+
* Strip internal edge headers and restore the author's Cache-Control before a
|
|
501
|
+
* cached document Response is served to a client. L1 entries carry the
|
|
502
|
+
* internal staleness/status headers and a rewritten Cache-Control; none of
|
|
503
|
+
* those should reach the browser or an upstream CDN.
|
|
504
|
+
*/
|
|
505
|
+
private toClientResponse(response: Response): Response {
|
|
506
|
+
const headers = new Headers(response.headers);
|
|
507
|
+
const originalCacheControl = headers.get(CACHE_ORIG_CC_HEADER);
|
|
508
|
+
if (originalCacheControl !== null) {
|
|
509
|
+
headers.set("Cache-Control", originalCacheControl);
|
|
510
|
+
} else {
|
|
511
|
+
headers.delete("Cache-Control");
|
|
512
|
+
}
|
|
513
|
+
headers.delete(CACHE_ORIG_CC_HEADER);
|
|
514
|
+
headers.delete(CACHE_STALE_AT_HEADER);
|
|
515
|
+
headers.delete(CACHE_STATUS_HEADER);
|
|
516
|
+
return new Response(response.body, {
|
|
517
|
+
status: response.status,
|
|
518
|
+
statusText: response.statusText,
|
|
519
|
+
headers,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
492
523
|
/**
|
|
493
524
|
* Store a Response with TTL and optional SWR window (for document-level caching).
|
|
494
525
|
* When KV is configured, also persists to L2.
|
|
@@ -515,8 +546,14 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
515
546
|
: [null, null]
|
|
516
547
|
: [response.body, null];
|
|
517
548
|
|
|
518
|
-
// Clone and add cache headers
|
|
549
|
+
// Clone and add cache headers. The author's Cache-Control is stashed and
|
|
550
|
+
// replaced with a long max-age so the CF Cache API holds the entry across
|
|
551
|
+
// the SWR window; getResponse restores the original before serving.
|
|
519
552
|
const headers = new Headers(response.headers);
|
|
553
|
+
const originalCacheControl = response.headers.get("Cache-Control");
|
|
554
|
+
if (originalCacheControl !== null) {
|
|
555
|
+
headers.set(CACHE_ORIG_CC_HEADER, originalCacheControl);
|
|
556
|
+
}
|
|
520
557
|
headers.set("Cache-Control", `public, max-age=${totalTtl}`);
|
|
521
558
|
headers.set(CACHE_STALE_AT_HEADER, String(staleAt));
|
|
522
559
|
|
|
@@ -766,13 +803,13 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
766
803
|
data: CachedEntryData,
|
|
767
804
|
staleAt: number,
|
|
768
805
|
totalTtl: number,
|
|
806
|
+
swrWindow: number,
|
|
769
807
|
): void {
|
|
770
808
|
// KV requires expirationTtl >= 60s. Skip write for short-lived entries.
|
|
771
809
|
if (!this.kv || !this.waitUntil || totalTtl < 60) return;
|
|
772
810
|
|
|
773
811
|
const kvKey = this.toKVKey(key);
|
|
774
|
-
const
|
|
775
|
-
const expiresAt = staleAt + swrWindow;
|
|
812
|
+
const expiresAt = staleAt + swrWindow * 1000;
|
|
776
813
|
|
|
777
814
|
this.waitUntil(async () => {
|
|
778
815
|
try {
|
|
@@ -939,6 +976,10 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
939
976
|
const request = this.keyToRequest(`doc:${key}`);
|
|
940
977
|
|
|
941
978
|
const headers = new Headers(envelope.hd);
|
|
979
|
+
const originalCacheControl = headers.get("Cache-Control");
|
|
980
|
+
if (originalCacheControl !== null) {
|
|
981
|
+
headers.set(CACHE_ORIG_CC_HEADER, originalCacheControl);
|
|
982
|
+
}
|
|
942
983
|
headers.set("Cache-Control", `public, max-age=${remainingTtl}`);
|
|
943
984
|
headers.set(CACHE_STALE_AT_HEADER, String(envelope.s));
|
|
944
985
|
|
package/src/client.rsc.tsx
CHANGED
|
@@ -78,6 +78,9 @@ export {
|
|
|
78
78
|
// Re-export useHref - it's a "use client" hook
|
|
79
79
|
export { useHref } from "./browser/react/use-href.js";
|
|
80
80
|
|
|
81
|
+
// Re-export useReverse - it's a "use client" hook
|
|
82
|
+
export { useReverse } from "./browser/react/use-reverse.js";
|
|
83
|
+
|
|
81
84
|
// Re-export useHandle - it's a "use client" hook
|
|
82
85
|
export { useHandle } from "./browser/react/use-handle.js";
|
|
83
86
|
|
package/src/client.tsx
CHANGED
|
@@ -214,6 +214,7 @@ export function useOutlet(): ReactNode {
|
|
|
214
214
|
export {
|
|
215
215
|
useLoader,
|
|
216
216
|
useFetchLoader,
|
|
217
|
+
useRefreshLoaders,
|
|
217
218
|
type LoadFunction,
|
|
218
219
|
type UseLoaderResult,
|
|
219
220
|
type UseFetchLoaderResult,
|
|
@@ -409,13 +410,10 @@ export {
|
|
|
409
410
|
type LocationStateOptions,
|
|
410
411
|
} from "./browser/react/location-state.js";
|
|
411
412
|
|
|
412
|
-
// Type-safe href for client-side path validation
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
type PatternToPath,
|
|
417
|
-
type PathResponse,
|
|
418
|
-
} from "./href-client.js";
|
|
413
|
+
// Type-safe href for client-side path validation. The path and response types
|
|
414
|
+
// are ambient as `Rango.Path` / `Rango.PathResponse` (declared in
|
|
415
|
+
// href-client.ts) — no import needed.
|
|
416
|
+
export { href, type PatternToPath } from "./href-client.js";
|
|
419
417
|
|
|
420
418
|
// Response envelope types for consuming JSON response routes
|
|
421
419
|
export type { ResponseEnvelope, ResponseError } from "./urls.js";
|
|
@@ -448,8 +446,12 @@ export { MountContext } from "./browser/react/mount-context.js";
|
|
|
448
446
|
// Mount-aware href hook - auto-prefixes paths with include() mount
|
|
449
447
|
export { useHref } from "./browser/react/use-href.js";
|
|
450
448
|
|
|
449
|
+
// Mount-aware reverse hook - resolves dot-prefixed names against an imported
|
|
450
|
+
// generated routes map (from a urls() module's .gen.ts).
|
|
451
|
+
export { useReverse } from "./browser/react/use-reverse.js";
|
|
452
|
+
|
|
451
453
|
// Type-safe scoped reverse function for scopedReverse<typeof patterns>()
|
|
452
|
-
export type { ScopedReverseFunction } from "./reverse.js";
|
|
454
|
+
export type { ScopedReverseFunction, LocalReverseFunction } from "./reverse.js";
|
|
453
455
|
|
|
454
456
|
// Loader definition type - for typing loader props in client components
|
|
455
457
|
export type { LoaderDefinition } from "./types.js";
|
package/src/context-var.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* interface PaginationData { current: number; total: number }
|
|
13
13
|
* export const Pagination = createVar<PaginationData>();
|
|
14
14
|
*
|
|
15
|
-
* // Non-cacheable var — throws
|
|
15
|
+
* // Non-cacheable var — ctx.get(User) throws inside a cache() boundary
|
|
16
16
|
* export const User = createVar<UserData>({ cache: false });
|
|
17
17
|
*
|
|
18
18
|
* // handler
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
export interface ContextVar<T> {
|
|
27
27
|
readonly __brand: "context-var";
|
|
28
28
|
readonly key: symbol;
|
|
29
|
-
/** When false,
|
|
29
|
+
/** When false, ctx.get(var) throws inside a cache() boundary. */
|
|
30
30
|
readonly cache: boolean;
|
|
31
31
|
/** Phantom field to carry the type parameter. Never set at runtime. */
|
|
32
32
|
readonly __type?: T;
|
|
@@ -35,9 +35,9 @@ export interface ContextVar<T> {
|
|
|
35
35
|
export interface ContextVarOptions {
|
|
36
36
|
/**
|
|
37
37
|
* When false, marks this variable as non-cacheable.
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
38
|
+
* Reading this var with ctx.get() inside a cache() boundary throws. Use for
|
|
39
|
+
* inherently request-specific data (user sessions, auth tokens, etc.) that
|
|
40
|
+
* must never be baked into cached segments.
|
|
41
41
|
*
|
|
42
42
|
* @default true
|
|
43
43
|
*/
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { isLoaderDataResult } from "./types.js";
|
|
3
|
+
|
|
4
|
+
// Shared by segment-system (server) and LoaderResolver (client) so the
|
|
5
|
+
// legacy/ok/error-fallback/throw decode of resolved loader values lives once.
|
|
6
|
+
// Last failing loader wins errorFallback; an error without a fallback throws.
|
|
7
|
+
export function decodeLoaderResults(
|
|
8
|
+
resolvedData: any[],
|
|
9
|
+
loaderIds: string[],
|
|
10
|
+
): { loaderData: Record<string, any>; errorFallback: ReactNode } {
|
|
11
|
+
const loaderData: Record<string, any> = {};
|
|
12
|
+
let errorFallback: ReactNode = null;
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < loaderIds.length; i++) {
|
|
15
|
+
const id = loaderIds[i];
|
|
16
|
+
const result = resolvedData[i];
|
|
17
|
+
|
|
18
|
+
if (!isLoaderDataResult(result)) {
|
|
19
|
+
loaderData[id] = result;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (result.ok) {
|
|
24
|
+
loaderData[id] = result.data;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (result.fallback) {
|
|
29
|
+
errorFallback = result.fallback;
|
|
30
|
+
} else {
|
|
31
|
+
throw new Error(result.error.message);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { loaderData, errorFallback };
|
|
36
|
+
}
|
package/src/errors.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Custom error classes for
|
|
2
|
+
* Custom error classes for Rango
|
|
3
3
|
*
|
|
4
4
|
* All errors include:
|
|
5
5
|
* - Descriptive names for easy identification
|
|
@@ -27,6 +27,17 @@ export class RouteNotFoundError extends Error {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
// name fallback covers cross-realm errors (Vite dev dupes, RSC serialization)
|
|
31
|
+
// where instanceof fails.
|
|
32
|
+
export function isRouteNotFoundError(
|
|
33
|
+
error: unknown,
|
|
34
|
+
): error is RouteNotFoundError {
|
|
35
|
+
return (
|
|
36
|
+
error instanceof RouteNotFoundError ||
|
|
37
|
+
(error instanceof Error && error.name === "RouteNotFoundError")
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
30
41
|
/**
|
|
31
42
|
* Thrown when data is not found (e.g., product with ID doesn't exist)
|
|
32
43
|
* Use this in handlers/loaders to trigger the nearest notFoundBoundary
|
|
@@ -109,6 +120,24 @@ export class BuildError extends Error {
|
|
|
109
120
|
}
|
|
110
121
|
}
|
|
111
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Thrown when a route-definition DSL helper (route/layout/loader/cache/…) is
|
|
125
|
+
* called outside an active urls()/map() builder, so there is no
|
|
126
|
+
* AsyncLocalStorage build context to attach to. The message names the specific
|
|
127
|
+
* helper and how to fix it; the `cause` records the mechanical reason so the
|
|
128
|
+
* failure mode is identifiable (not conflated with an unrelated throw).
|
|
129
|
+
*/
|
|
130
|
+
export class DslContextError extends Error {
|
|
131
|
+
name = "DslContextError" as const;
|
|
132
|
+
cause?: unknown;
|
|
133
|
+
|
|
134
|
+
constructor(message: string, options?: ErrorOptions) {
|
|
135
|
+
super(message);
|
|
136
|
+
Object.setPrototypeOf(this, DslContextError.prototype);
|
|
137
|
+
this.cause = options?.cause;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
112
141
|
/**
|
|
113
142
|
* Thrown when a network request fails (server unreachable, no internet, etc.)
|
|
114
143
|
* This error triggers the root error boundary with retry capability.
|
package/src/handle.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { missingInjectedIdError } from "./missing-id-error.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Handle definition for accumulating data across route segments.
|
|
3
5
|
*
|
|
@@ -43,6 +45,11 @@ function defaultCollect<T>(segments: T[][]): T[] {
|
|
|
43
45
|
// Used by useHandle() to recover collect when handle is deserialized from RSC prop.
|
|
44
46
|
const collectRegistry = new Map<string, (segments: unknown[][]) => unknown>();
|
|
45
47
|
|
|
48
|
+
// Monotonic counter for runtime fallback ids (see createHandle). Module-scoped
|
|
49
|
+
// and deterministic, so each createHandle() call gets a stable, unique id within
|
|
50
|
+
// the process. Only used when no build id was injected (a bare unit test).
|
|
51
|
+
let runtimeHandleIdCounter = 0;
|
|
52
|
+
|
|
46
53
|
/**
|
|
47
54
|
* Look up a collect function from the registry by handle $$id.
|
|
48
55
|
* Returns undefined if not registered (falls back to defaultCollect in useHandle).
|
|
@@ -93,14 +100,22 @@ export function createHandle<TData, TAccumulated = TData[]>(
|
|
|
93
100
|
collect?: (segments: TData[][]) => TAccumulated,
|
|
94
101
|
__injectedId?: string,
|
|
95
102
|
): Handle<TData, TAccumulated> {
|
|
96
|
-
|
|
103
|
+
let handleId = __injectedId ?? "";
|
|
97
104
|
|
|
98
105
|
if (!handleId && process.env.NODE_ENV === "development") {
|
|
99
|
-
throw
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
throw missingInjectedIdError("Handle", "createHandle");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// No build-injected id. This only happens in a bare unit test — every real
|
|
110
|
+
// build runs the rango Vite plugin, which always injects a stable id (and the
|
|
111
|
+
// line above throws for a genuinely non-exported handle in dev). Assign a
|
|
112
|
+
// process-stable runtime id so the collect registers below and the handle is
|
|
113
|
+
// fully exercisable in tests (useHandle, collectHandle, renderRoute's `handles`
|
|
114
|
+
// seeding run the REAL collect). Provably inert in production: the fallback
|
|
115
|
+
// never triggers when the plugin injects the id, so server/client id
|
|
116
|
+
// consistency (required for RSC recovery) is unaffected.
|
|
117
|
+
if (!handleId) {
|
|
118
|
+
handleId = `__rango_runtime_handle_${runtimeHandleIdCounter++}`;
|
|
104
119
|
}
|
|
105
120
|
|
|
106
121
|
const collectFn =
|
|
@@ -109,12 +124,10 @@ export function createHandle<TData, TAccumulated = TData[]>(
|
|
|
109
124
|
|
|
110
125
|
// Register collect in module-level registry so useHandle() can recover it
|
|
111
126
|
// when the handle is deserialized from RSC props (toJSON strips collect).
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
);
|
|
117
|
-
}
|
|
127
|
+
collectRegistry.set(
|
|
128
|
+
handleId,
|
|
129
|
+
collectFn as (segments: unknown[][]) => unknown,
|
|
130
|
+
);
|
|
118
131
|
|
|
119
132
|
return {
|
|
120
133
|
__brand: "handle" as const,
|
|
@@ -151,7 +164,7 @@ export function collectHandleData<TData, TAccumulated>(
|
|
|
151
164
|
const collectFn = getCollectFn(handle.$$id);
|
|
152
165
|
if (!collectFn && process.env.NODE_ENV !== "production") {
|
|
153
166
|
console.warn(
|
|
154
|
-
`[
|
|
167
|
+
`[rango] Handle "${handle.$$id}" has no registered collect function. ` +
|
|
155
168
|
`Falling back to flat array. Ensure the handle module is imported so ` +
|
|
156
169
|
`createHandle() runs and registers the collect function.`,
|
|
157
170
|
);
|
package/src/host/index.ts
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
*
|
|
12
12
|
* const router = createHostRouter();
|
|
13
13
|
*
|
|
14
|
-
* router.host(['.']).
|
|
15
|
-
* router.host(['admin.*']).
|
|
14
|
+
* router.host(['.']).lazy(() => import('./apps/main'));
|
|
15
|
+
* router.host(['admin.*']).lazy(() => import('./apps/admin'));
|
|
16
16
|
*
|
|
17
17
|
* export default {
|
|
18
18
|
* fetch(request) {
|