@rangojs/router 0.0.0-experimental.d7eeaa75 → 0.0.0-experimental.d98a8e9d
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 +2154 -861
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +57 -11
- package/skills/api-client/SKILL.md +211 -0
- 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 +364 -0
- 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 +46 -4
- package/skills/layout/SKILL.md +28 -7
- package/skills/links/SKILL.md +247 -17
- package/skills/loader/SKILL.md +219 -9
- package/skills/middleware/SKILL.md +47 -12
- package/skills/migrate-nextjs/SKILL.md +562 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +71 -6
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +243 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +57 -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 +128 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +98 -0
- package/skills/testing/client-components.md +121 -0
- package/skills/testing/e2e-parity.md +124 -0
- package/skills/testing/flight.md +89 -0
- package/skills/testing/handles.md +127 -0
- package/skills/testing/loader.md +108 -0
- package/skills/testing/middleware.md +97 -0
- package/skills/testing/render-handler.md +102 -0
- package/skills/testing/response-routes.md +94 -0
- package/skills/testing/reverse-and-types.md +83 -0
- package/skills/testing/server-actions.md +89 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -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 +116 -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 +84 -11
- package/src/browser/navigation-client.ts +104 -68
- package/src/browser/navigation-store.ts +32 -9
- package/src/browser/navigation-transaction.ts +10 -28
- package/src/browser/partial-update.ts +64 -26
- package/src/browser/prefetch/cache.ts +183 -44
- package/src/browser/prefetch/fetch.ts +228 -37
- package/src/browser/prefetch/queue.ts +36 -5
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +30 -2
- 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 +32 -1
- package/src/browser/rsc-router.tsx +69 -22
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-reconciler.ts +36 -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 +8 -1
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +95 -25
- 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 +96 -205
- package/src/context-var.ts +5 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -4
- package/src/handle.ts +32 -14
- 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 -21
- package/src/index.rsc.ts +10 -6
- package/src/index.ts +54 -17
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +25 -7
- package/src/loader.ts +16 -9
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/prerender.ts +27 -6
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -36
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +384 -257
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +100 -28
- 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 +26 -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/find-match.ts +54 -6
- package/src/router/handler-context.ts +21 -38
- package/src/router/intercept-resolution.ts +4 -18
- package/src/router/lazy-includes.ts +41 -22
- package/src/router/loader-resolution.ts +82 -36
- package/src/router/manifest.ts +41 -19
- 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 +116 -19
- 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 +40 -16
- package/src/router/types.ts +8 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +52 -30
- 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/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/progressive-enhancement.ts +4 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +57 -61
- 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/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -4
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +132 -116
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +175 -53
- package/src/server/cookie-store.ts +28 -4
- package/src/server/request-context.ts +67 -51
- package/src/ssr/index.tsx +5 -1
- package/src/static-handler.ts +25 -3
- package/src/testing/cache-status.ts +166 -0
- package/src/testing/collect-handle.ts +63 -0
- package/src/testing/dispatch.ts +581 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -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 +326 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +110 -0
- package/src/testing/flight-normalize.ts +38 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +51 -0
- package/src/testing/flight.ts +234 -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 +323 -0
- package/src/testing/render-route.tsx +590 -0
- package/src/testing/run-loader.ts +363 -0
- package/src/testing/run-middleware.ts +205 -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 +285 -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 +11 -9
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +11 -0
- package/src/types/segments.ts +35 -2
- package/src/urls/include-helper.ts +34 -67
- package/src/urls/index.ts +1 -5
- package/src/urls/path-helper-types.ts +41 -7
- 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 +58 -139
- 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 +106 -75
- 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 +8 -59
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +21 -6
- package/src/vite/utils/shared-utils.ts +107 -26
- package/src/browser/action-response-classifier.ts +0 -99
package/src/loader.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* @rangojs/router/loader (client version)
|
|
3
3
|
*
|
|
4
4
|
* Client-only stub for createLoader. Returns a minimal loader definition
|
|
5
5
|
* ({ __brand, $$id }) that can be passed to hooks like useLoader.
|
|
@@ -18,6 +18,8 @@ import type {
|
|
|
18
18
|
LoaderDefinition,
|
|
19
19
|
LoaderFn,
|
|
20
20
|
} from "./types.js";
|
|
21
|
+
import { missingInjectedIdError } from "./missing-id-error.js";
|
|
22
|
+
import { isUnderTestRunner } from "./runtime-env.js";
|
|
21
23
|
|
|
22
24
|
// Overload 1: With function only (not fetchable)
|
|
23
25
|
export function createLoader<T>(
|
|
@@ -38,10 +40,6 @@ export function createLoader<T>(
|
|
|
38
40
|
|
|
39
41
|
// Implementation - client stub that just returns the loader definition
|
|
40
42
|
// The $$id parameter is injected by Vite plugin, not user-provided
|
|
41
|
-
//
|
|
42
|
-
// NOTE: For export-only loader files, the Vite plugin replaces the entire
|
|
43
|
-
// file with object literals (bypassing this function). This function only
|
|
44
|
-
// runs when loaders are in mixed files (not export-only).
|
|
45
43
|
export function createLoader<T>(
|
|
46
44
|
_fn: LoaderFn<T, Record<string, string | undefined>, any>,
|
|
47
45
|
_fetchable?: true | FetchableLoaderOptions,
|
|
@@ -49,11 +47,20 @@ export function createLoader<T>(
|
|
|
49
47
|
): LoaderDefinition<Awaited<T>, Record<string, string | undefined>> {
|
|
50
48
|
const loaderId = __injectedId || "";
|
|
51
49
|
|
|
52
|
-
|
|
50
|
+
// Client/SSR build of createLoader. Under a test runner it needs no id
|
|
51
|
+
// (loaderId stays ""; the react-server build in loader.rsc.ts adds the runtime
|
|
52
|
+
// fallback for whole-router construction). Otherwise (dev or a real build) a
|
|
53
|
+
// missing id means an UNSUPPORTED shape the plugin skipped — fail loud rather
|
|
54
|
+
// than ship `$$id: ""` (which would make a client useLoader read the wrong
|
|
55
|
+
// key). The rich diagnostic stays behind the NODE_ENV check so production folds
|
|
56
|
+
// it away and ships the small throw. isUnderTestRunner() is runtime-safe.
|
|
57
|
+
if (!loaderId && !isUnderTestRunner()) {
|
|
58
|
+
if (process.env.NODE_ENV !== "production") {
|
|
59
|
+
throw missingInjectedIdError("Loader", "createLoader");
|
|
60
|
+
}
|
|
53
61
|
throw new Error(
|
|
54
|
-
"[
|
|
55
|
-
"
|
|
56
|
-
"the loader is exported with: export const MyLoader = createLoader(...)",
|
|
62
|
+
"[rango] Loader is missing $$id — the build plugin did not inject one. " +
|
|
63
|
+
"Export it as `export const X = createLoader(...)`.",
|
|
57
64
|
);
|
|
58
65
|
}
|
|
59
66
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Builds the error thrown when a create*() call (createLoader / createHandle)
|
|
2
|
+
// reaches runtime without an injected $$id. The exposeInternalIds Vite transform
|
|
3
|
+
// injects $$id only for an EXPORTED const declaration, so a non-exported const,
|
|
4
|
+
// an `export let/var`, or an inline create*() call gets none. Previously this
|
|
5
|
+
// failed with a terse message and no source location; this helper adds the
|
|
6
|
+
// offending call site (best-effort, from the stack) and actionable guidance.
|
|
7
|
+
//
|
|
8
|
+
// The "<Kind> is missing $$id" prefix is preserved so existing tests and any
|
|
9
|
+
// log scrapers keep matching. Dev-only: the call sites guard on
|
|
10
|
+
// process.env.NODE_ENV === "development", so production builds fold the branch
|
|
11
|
+
// away and tree-shake this module out.
|
|
12
|
+
|
|
13
|
+
// create*() implementation files to skip when locating the user's call site.
|
|
14
|
+
const SELF_FILES = new Set([
|
|
15
|
+
"missing-id-error",
|
|
16
|
+
"loader",
|
|
17
|
+
"loader.rsc",
|
|
18
|
+
"handle",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Best-effort "path:line:column" of the user's create*() call, parsed from the
|
|
23
|
+
* current stack. Skips @rangojs/router internals and node_modules. Returns
|
|
24
|
+
* undefined if nothing usable is found (stack parsing is inherently fragile).
|
|
25
|
+
*/
|
|
26
|
+
function findUserCallSite(): string | undefined {
|
|
27
|
+
try {
|
|
28
|
+
const stack = new Error().stack;
|
|
29
|
+
if (!stack) return undefined;
|
|
30
|
+
for (const frame of stack.split("\n").slice(1)) {
|
|
31
|
+
const m = frame.match(
|
|
32
|
+
/(?:\(|@|\s)(?:file:\/\/)?((?:\/|[A-Za-z]:[\\/])[^()\s]+?\.(?:ts|tsx|js|jsx|mts|cts)):(\d+):(\d+)\)?/,
|
|
33
|
+
);
|
|
34
|
+
if (!m) continue;
|
|
35
|
+
const path = m[1];
|
|
36
|
+
if (path.includes("node_modules") || path.includes("@rangojs/router")) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const base = path
|
|
40
|
+
.split(/[\\/]/)
|
|
41
|
+
.pop()!
|
|
42
|
+
.replace(/\.(?:ts|tsx|js|jsx|mts|cts)$/, "");
|
|
43
|
+
if (SELF_FILES.has(base)) continue;
|
|
44
|
+
return `${path}:${m[2]}:${m[3]}`;
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// best-effort only
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function missingInjectedIdError(
|
|
53
|
+
kind: "Loader" | "Handle",
|
|
54
|
+
fnName: "createLoader" | "createHandle",
|
|
55
|
+
): Error {
|
|
56
|
+
const site = findUserCallSite();
|
|
57
|
+
const at = site ? ` (created at ${site})` : "";
|
|
58
|
+
return new Error(
|
|
59
|
+
`[rango] ${kind} is missing $$id${at}.\n` +
|
|
60
|
+
`The @rangojs/router:expose-internal-ids Vite transform injects ${fnName}()'s ` +
|
|
61
|
+
`stable $$id from an EXPORTED const declaration only:\n` +
|
|
62
|
+
` export const X = ${fnName}(...)\n` +
|
|
63
|
+
` const X = ${fnName}(...); export { X }\n` +
|
|
64
|
+
`A non-exported const, an \`export let/var\`, or an inline ${fnName}(...) ` +
|
|
65
|
+
`call gets no $$id — export it as \`export const\`. (A matching ` +
|
|
66
|
+
`"Unsupported ${fnName} shape" warning names the exact file:line.)`,
|
|
67
|
+
);
|
|
68
|
+
}
|
package/src/outlet-context.ts
CHANGED
package/src/prerender.ts
CHANGED
|
@@ -38,6 +38,7 @@ import type { ReverseFunction } from "./reverse.js";
|
|
|
38
38
|
import type { DefaultReverseRouteMap } from "./types/global-namespace.js";
|
|
39
39
|
import type { UseItems, HandlerUseItem } from "./route-types.js";
|
|
40
40
|
import { isCachedFunction } from "./cache/taint.js";
|
|
41
|
+
import { isUnderTestRunner } from "./runtime-env.js";
|
|
41
42
|
|
|
42
43
|
// -- Named route resolution types -------------------------------------------
|
|
43
44
|
|
|
@@ -69,9 +70,9 @@ type BuildReverseFunction = [DefaultReverseRouteMap] extends [
|
|
|
69
70
|
* Default route map for Prerender named route resolution.
|
|
70
71
|
* Uses GeneratedRouteMap (from gen file) to avoid circular dependencies.
|
|
71
72
|
*/
|
|
72
|
-
type DefaultPrerenderRouteMap = keyof
|
|
73
|
+
type DefaultPrerenderRouteMap = keyof Rango.GeneratedRouteMap extends never
|
|
73
74
|
? {}
|
|
74
|
-
:
|
|
75
|
+
: Rango.GeneratedRouteMap;
|
|
75
76
|
|
|
76
77
|
/** Extract params from a route map entry (string pattern or { path } object). */
|
|
77
78
|
type ExtractParamsFromEntry<TEntry> = TEntry extends string
|
|
@@ -273,6 +274,11 @@ export interface PrerenderHandlerDefinition<
|
|
|
273
274
|
use?: () => UseItems<HandlerUseItem>;
|
|
274
275
|
}
|
|
275
276
|
|
|
277
|
+
// Process-stable fallback id counter (mirrors createHandle / createLoader). Only
|
|
278
|
+
// assigned in a bare unit test where the Vite plugin did not inject an id; never
|
|
279
|
+
// fires in a real build (the plugin always injects).
|
|
280
|
+
let runtimePrerenderIdCounter = 0;
|
|
281
|
+
|
|
276
282
|
// -- Overloads --------------------------------------------------------------
|
|
277
283
|
//
|
|
278
284
|
// T accepts: named route string (global or .local) OR explicit param object.
|
|
@@ -376,12 +382,27 @@ export function Prerender<TParams extends Record<string, any>>(
|
|
|
376
382
|
);
|
|
377
383
|
}
|
|
378
384
|
|
|
379
|
-
|
|
385
|
+
// Throw unless under a test runner. The plugin always injects $$id for a
|
|
386
|
+
// supported `export const` Prerender on every build, so a missing id means
|
|
387
|
+
// either no plugin (a bare test — fall back below) or an UNSUPPORTED shape the
|
|
388
|
+
// plugin silently skipped (dev OR a real build — fail loud; a synthetic id
|
|
389
|
+
// would degrade to a silent prerender miss). The message is already small (no
|
|
390
|
+
// stack-parsing diagnostic), so it ships as-is. isUnderTestRunner() is
|
|
391
|
+
// runtime-safe — never a bare `process.env` access.
|
|
392
|
+
if (!id && !isUnderTestRunner()) {
|
|
380
393
|
throw new Error(
|
|
381
|
-
"[
|
|
382
|
-
"
|
|
394
|
+
"[rango] Prerender: missing $$id. Use `export const X = Prerender(...)` " +
|
|
395
|
+
"and ensure the exposeInternalIds Vite plugin is configured.",
|
|
383
396
|
);
|
|
384
397
|
}
|
|
398
|
+
// Under vitest with no plugin id: assign a process-stable runtime id so a
|
|
399
|
+
// whole-app router with Prerender routes constructs in a bare test (for
|
|
400
|
+
// dispatch / assertGeneratedRoutesMatch). Never reached in a real build (the
|
|
401
|
+
// throw above fires there); prerender storage/lookup keys on routeName +
|
|
402
|
+
// paramHash, never $$id (mirrors createHandle / createLoader).
|
|
403
|
+
if (!id) {
|
|
404
|
+
id = `__rango_runtime_prerender_${runtimePrerenderIdCounter++}`;
|
|
405
|
+
}
|
|
385
406
|
|
|
386
407
|
return {
|
|
387
408
|
__brand: "prerenderHandler" as const,
|
|
@@ -499,7 +520,7 @@ export function Passthrough<
|
|
|
499
520
|
): PassthroughHandlerDefinition<TParams, TEnv> {
|
|
500
521
|
if (!isPrerenderHandler(prerenderDef)) {
|
|
501
522
|
throw new Error(
|
|
502
|
-
"[
|
|
523
|
+
"[rango] Passthrough: first argument must be a Prerender() definition.",
|
|
503
524
|
);
|
|
504
525
|
}
|
|
505
526
|
return {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime-neutral Response shape utilities.
|
|
3
|
+
*
|
|
4
|
+
* Kept at the src/ root so both `router/` and `rsc/` can depend on it
|
|
5
|
+
* without creating a cross-layer import cycle.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* True when a Response represents a WebSocket upgrade handoff and must not
|
|
10
|
+
* be reconstructed or mutated:
|
|
11
|
+
*
|
|
12
|
+
* - Status 101 (Switching Protocols) is outside the standard Response
|
|
13
|
+
* constructor's 200–599 range, so `new Response(body, { status: 101 })`
|
|
14
|
+
* throws RangeError on Node/undici and any spec-compliant runtime.
|
|
15
|
+
* - Cloudflare's workerd attaches a non-standard `webSocket` property on
|
|
16
|
+
* the upgrade Response (e.g. from `acceptWebSocket`/`handleWebSocketUpgrade`
|
|
17
|
+
* or the `agents` library's `routeAgentRequest`). That property is dropped
|
|
18
|
+
* by a `new Response(...)` copy, breaking the upgrade even on workerd
|
|
19
|
+
* where the status range is relaxed.
|
|
20
|
+
*
|
|
21
|
+
* Callers should short-circuit header/body merges for these responses.
|
|
22
|
+
*/
|
|
23
|
+
export function isWebSocketUpgradeResponse(response: Response): boolean {
|
|
24
|
+
return (
|
|
25
|
+
response.status === 101 ||
|
|
26
|
+
(response as unknown as { webSocket?: unknown }).webSocket != null
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Location truthiness (not presence) so an empty `Location: ""` is not a redirect.
|
|
31
|
+
export function isRedirectResponse(response: Response): boolean {
|
|
32
|
+
return (
|
|
33
|
+
response.status >= 300 &&
|
|
34
|
+
response.status < 400 &&
|
|
35
|
+
Boolean(response.headers.get("Location"))
|
|
36
|
+
);
|
|
37
|
+
}
|
package/src/reverse.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtractParams } from "./types.js";
|
|
2
2
|
import type { SearchSchema, ResolveSearchSchema } from "./search-params.js";
|
|
3
3
|
import { serializeSearchParams } from "./search-params.js";
|
|
4
|
+
import { substitutePatternParams } from "./router/substitute-pattern-params.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Sanitize prefix string by removing leading slash
|
|
@@ -218,6 +219,67 @@ export type ExtractLocalRoutes<TPatterns> = TPatterns extends {
|
|
|
218
219
|
? TPatterns
|
|
219
220
|
: Record<string, string>;
|
|
220
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Params accepted by `useReverse(routes)`. The route's own params are
|
|
224
|
+
* required, and additional string keys are permitted so callers can
|
|
225
|
+
* override values that would otherwise be auto-filled from the matched
|
|
226
|
+
* route's `useParams()` (e.g. an enclosing `:tenantId` mount segment).
|
|
227
|
+
*/
|
|
228
|
+
export type LocalReverseParams<TPattern extends string> =
|
|
229
|
+
ExtractParams<TPattern> & {
|
|
230
|
+
readonly [extra: string]: string | undefined;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Type-safe local reverse function.
|
|
235
|
+
*
|
|
236
|
+
* Returned by `useReverse(routes)` on the client. The route map is the
|
|
237
|
+
* exposure boundary (a generated `routes` from a `urls()` module) and the
|
|
238
|
+
* scope is implicit from that import. Names may be written with or without a
|
|
239
|
+
* leading dot — `reverse("post")` and `reverse(".post")` are identical. The dot
|
|
240
|
+
* is a cosmetic readability convention (and parity with `ctx.reverse(".name")`);
|
|
241
|
+
* there is no separate global namespace here, so it carries no meaning.
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```typescript
|
|
245
|
+
* const reverse = useReverse(blogRoutes);
|
|
246
|
+
* reverse("index"); // ✓ no params (dot optional)
|
|
247
|
+
* reverse(".index"); // ✓ identical to the above
|
|
248
|
+
* reverse("post", { postId: "hello" }); // ✓ with params
|
|
249
|
+
* reverse("search", {}, { q: "hi" }); // ✓ with search schema
|
|
250
|
+
* reverse("typo"); // ✗ compile error
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
export type LocalReverseFunction<TLocalRoutes> = {
|
|
254
|
+
/**
|
|
255
|
+
* Route without params (leading dot optional)
|
|
256
|
+
*/
|
|
257
|
+
<TName extends keyof TLocalRoutes & string>(
|
|
258
|
+
name: IsEmptyObject<
|
|
259
|
+
ExtractParams<RoutePatternFor<TLocalRoutes, TName>>
|
|
260
|
+
> extends true
|
|
261
|
+
? TName | `.${TName}`
|
|
262
|
+
: never,
|
|
263
|
+
): string;
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Route with params (leading dot optional)
|
|
267
|
+
*/
|
|
268
|
+
<TName extends keyof TLocalRoutes & string>(
|
|
269
|
+
name: TName | `.${TName}`,
|
|
270
|
+
params: LocalReverseParams<RoutePatternFor<TLocalRoutes, TName>>,
|
|
271
|
+
): string;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Route with params and search (leading dot optional)
|
|
275
|
+
*/
|
|
276
|
+
<TName extends keyof TLocalRoutes & string>(
|
|
277
|
+
name: TName | `.${TName}`,
|
|
278
|
+
params: LocalReverseParams<RoutePatternFor<TLocalRoutes, TName>>,
|
|
279
|
+
search: ResolveSearchSchema<ExtractSearchSchema<TLocalRoutes, TName>>,
|
|
280
|
+
): string;
|
|
281
|
+
};
|
|
282
|
+
|
|
221
283
|
/**
|
|
222
284
|
* Extract the response data type for a named route from a UrlPatterns instance.
|
|
223
285
|
* Re-exported from urls.ts for consumer convenience.
|
|
@@ -301,42 +363,9 @@ export function createReverse<TRoutes extends Record<string, string>>(
|
|
|
301
363
|
throw new Error(`Unknown route: ${name}`);
|
|
302
364
|
}
|
|
303
365
|
|
|
304
|
-
let result =
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
// Strip constraint syntax: :param(a|b) -> use "param" as key
|
|
308
|
-
// Optional params (:param?) are omitted when not provided
|
|
309
|
-
let hadOmittedOptional = false;
|
|
310
|
-
result = result.replace(
|
|
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,
|
|
324
|
-
(_, key) => {
|
|
325
|
-
const value = params[key];
|
|
326
|
-
if (value === undefined) {
|
|
327
|
-
throw new Error(`Missing param "${key}" for route "${name}"`);
|
|
328
|
-
}
|
|
329
|
-
return encodeURIComponent(value);
|
|
330
|
-
},
|
|
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
|
-
const hadTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
|
|
336
|
-
result = result.replace(/\/\/+/g, "/").replace(/\/+$/, "") || "/";
|
|
337
|
-
if (hadTrailingSlash && !result.endsWith("/")) result += "/";
|
|
338
|
-
}
|
|
339
|
-
}
|
|
366
|
+
let result = params
|
|
367
|
+
? substitutePatternParams(pattern, params, name)
|
|
368
|
+
: pattern;
|
|
340
369
|
|
|
341
370
|
// Append search params as query string
|
|
342
371
|
if (search) {
|
|
@@ -4,7 +4,7 @@ import { Suspense, use, useId } from "react";
|
|
|
4
4
|
import { invariant } from "./errors";
|
|
5
5
|
import { OutletProvider } from "./outlet-provider.js";
|
|
6
6
|
import type { ResolvedSegment } from "./types.js";
|
|
7
|
-
import {
|
|
7
|
+
import { decodeLoaderResults } from "./decode-loader-results.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Stable async wrapper component for route content
|
|
@@ -26,10 +26,6 @@ export function RouteContentWrapper({
|
|
|
26
26
|
fallback?: ReactNode;
|
|
27
27
|
segmentId?: string;
|
|
28
28
|
}): ReactNode {
|
|
29
|
-
if (!content) {
|
|
30
|
-
// Already resolved
|
|
31
|
-
return content as ReactNode;
|
|
32
|
-
}
|
|
33
29
|
return (
|
|
34
30
|
<Suspense
|
|
35
31
|
fallback={fallback ?? null}
|
|
@@ -159,28 +155,10 @@ function LoaderResolver({
|
|
|
159
155
|
? use(loaderDataPromise)
|
|
160
156
|
: loaderDataPromise;
|
|
161
157
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
loaderIds.forEach((id, i) => {
|
|
167
|
-
const result = resolvedData[i];
|
|
168
|
-
|
|
169
|
-
if (isLoaderDataResult(result)) {
|
|
170
|
-
if (result.ok) {
|
|
171
|
-
loaderData[id] = result.data;
|
|
172
|
-
} else {
|
|
173
|
-
if (result.fallback) {
|
|
174
|
-
loaderErrorFallback = result.fallback;
|
|
175
|
-
} else {
|
|
176
|
-
throw new Error(result.error.message);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
} else {
|
|
180
|
-
// Legacy format - direct data
|
|
181
|
-
loaderData[id] = result;
|
|
182
|
-
}
|
|
183
|
-
});
|
|
158
|
+
const { loaderData, errorFallback } = decodeLoaderResults(
|
|
159
|
+
resolvedData,
|
|
160
|
+
loaderIds,
|
|
161
|
+
);
|
|
184
162
|
|
|
185
163
|
return (
|
|
186
164
|
<OutletProvider
|
|
@@ -190,7 +168,7 @@ function LoaderResolver({
|
|
|
190
168
|
parallel={parallel}
|
|
191
169
|
loaderData={Object.keys(loaderData).length > 0 ? loaderData : undefined}
|
|
192
170
|
>
|
|
193
|
-
{
|
|
171
|
+
{errorFallback ?? children}
|
|
194
172
|
</OutletProvider>
|
|
195
173
|
);
|
|
196
174
|
}
|