@rangojs/router 0.23.0 → 0.24.1
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/bin/rango.js +4 -3
- package/dist/types/browser/merge-segment-loaders.d.ts +5 -2
- package/dist/types/browser/partial-update.d.ts +6 -4
- package/dist/types/browser/react/NavigationProvider.d.ts +11 -7
- package/dist/types/browser/react/context.d.ts +3 -2
- package/dist/types/browser/types.d.ts +5 -1
- package/dist/types/build/route-types/scan-filter.d.ts +7 -1
- package/dist/types/cache/document-cache-policy.d.ts +24 -0
- package/dist/types/cache/document-cache.d.ts +2 -10
- package/dist/types/client-urls/types.d.ts +8 -0
- package/dist/types/context-var.d.ts +12 -0
- package/dist/types/errors.d.ts +10 -0
- package/dist/types/handles/pushed-by.d.ts +8 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.rsc.d.ts +2 -0
- package/dist/types/response-utils.d.ts +7 -0
- package/dist/types/reverse.d.ts +8 -8
- package/dist/types/route-definition/rewrite.d.ts +56 -0
- package/dist/types/route-map-builder.d.ts +7 -2
- package/dist/types/router/basename.d.ts +7 -0
- package/dist/types/router/handler-context.d.ts +1 -5
- package/dist/types/router/internal-params.d.ts +8 -0
- package/dist/types/router/middleware-types.d.ts +15 -0
- package/dist/types/router/revalidation.d.ts +1 -2
- package/dist/types/router/route-trie-builder.d.ts +10 -0
- package/dist/types/router/segment-resolution/mask-nested.d.ts +4 -2
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +36 -13
- package/dist/types/rsc/full-payload.d.ts +11 -0
- package/dist/types/rsc/handler.d.ts +33 -0
- package/dist/types/rsc/helpers.d.ts +8 -0
- package/dist/types/rsc/loader-fetch.d.ts +16 -0
- package/dist/types/rsc/rewrite-passes.d.ts +74 -0
- package/dist/types/rsc/ssr-setup.d.ts +3 -1
- package/dist/types/server/request-context.d.ts +39 -2
- package/dist/types/server/rewrite-trail.d.ts +49 -0
- package/dist/types/server/slot-accessors.d.ts +29 -0
- package/dist/types/testing/index.d.ts +1 -1
- package/dist/types/testing/render-route.d.ts +8 -0
- package/dist/types/testing/run-middleware.d.ts +20 -0
- package/dist/types/testing/serve-shell-request.d.ts +2 -1
- package/dist/types/thenable-status.d.ts +6 -0
- package/dist/types/types/request-scope.d.ts +7 -0
- package/dist/types/vite/discovery/state.d.ts +12 -9
- package/dist/vite/index.js +164 -103
- package/package.json +1 -1
- package/skills/caching/SKILL.md +12 -0
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +86 -1
- package/skills/hooks/SKILL.md +11 -6
- package/skills/hooks/data.md +3 -1
- package/skills/loader/SKILL.md +7 -1
- package/skills/middleware/SKILL.md +183 -2
- package/skills/ppr/SKILL.md +9 -0
- package/skills/rango/SKILL.md +1 -1
- package/skills/server-actions/SKILL.md +27 -0
- package/skills/testing/client-components.md +1 -0
- package/skills/testing/middleware.md +31 -10
- package/skills/testing/response-routes.md +1 -0
- package/skills/testing/serve-shell-request.md +19 -1
- package/skills/use-cache/SKILL.md +4 -2
- package/src/__augment-tests__/augmented.check.ts +20 -0
- package/src/browser/event-controller.ts +3 -0
- package/src/browser/merge-segment-loaders.ts +31 -18
- package/src/browser/navigation-bridge.ts +19 -3
- package/src/browser/partial-update.ts +27 -12
- package/src/browser/react/Link.tsx +7 -9
- package/src/browser/react/NavigationProvider.tsx +46 -26
- package/src/browser/react/context.ts +3 -2
- package/src/browser/react/use-router.ts +2 -4
- package/src/browser/server-action-bridge.ts +6 -0
- package/src/browser/types.ts +5 -1
- package/src/build/route-types/scan-filter.ts +11 -4
- package/src/cache/document-cache-policy.ts +131 -0
- package/src/cache/document-cache.ts +12 -119
- package/src/client-urls/client-root.tsx +3 -3
- package/src/client-urls/client-urls.ts +8 -1
- package/src/client-urls/types.ts +8 -0
- package/src/context-var.ts +32 -2
- package/src/errors.ts +19 -0
- package/src/handles/pushed-by.ts +26 -0
- package/src/index.rsc.ts +8 -0
- package/src/index.ts +7 -0
- package/src/response-utils.ts +19 -0
- package/src/reverse.ts +17 -14
- package/src/route-definition/redirect.ts +2 -15
- package/src/route-definition/rewrite.ts +146 -0
- package/src/route-map-builder.ts +14 -11
- package/src/router/basename.ts +20 -0
- package/src/router/handler-context.ts +3 -13
- package/src/router/intercept-resolution.ts +29 -12
- package/src/router/internal-params.ts +16 -0
- package/src/router/loader-resolution.ts +2 -0
- package/src/router/match-middleware/background-revalidation.ts +11 -9
- package/src/router/match-middleware/cache-lookup.ts +20 -15
- package/src/router/match-middleware/cache-store.ts +9 -11
- package/src/router/match-middleware/intercept-resolution.ts +4 -2
- package/src/router/middleware-types.ts +16 -0
- package/src/router/middleware.ts +127 -49
- package/src/router/revalidation.ts +1 -1
- package/src/router/route-trie-builder.ts +28 -1
- package/src/router/segment-resolution/loader-snapshot.ts +18 -4
- package/src/router/segment-resolution/mask-nested.ts +8 -4
- package/src/router/segment-resolution/prefetch-deferral.ts +57 -17
- package/src/router/segment-resolution/revalidation.ts +57 -33
- package/src/rsc/full-payload.ts +37 -5
- package/src/rsc/handler.ts +82 -55
- package/src/rsc/helpers.ts +24 -0
- package/src/rsc/loader-fetch.ts +79 -24
- package/src/rsc/response-cache-serve.ts +18 -2
- package/src/rsc/response-route-handler.ts +4 -7
- package/src/rsc/rewrite-passes.ts +203 -0
- package/src/rsc/rsc-rendering.ts +4 -2
- package/src/rsc/ssr-setup.ts +11 -5
- package/src/server/cookie-parse.ts +36 -12
- package/src/server/cookie-store.ts +7 -3
- package/src/server/handle-store.ts +48 -17
- package/src/server/request-context.ts +154 -63
- package/src/server/request-headers.ts +4 -14
- package/src/server/rewrite-trail.ts +190 -0
- package/src/server/slot-accessors.ts +57 -0
- package/src/ssr/index.tsx +118 -16
- package/src/testing/dispatch.ts +138 -67
- package/src/testing/index.ts +1 -0
- package/src/testing/render-route.tsx +16 -3
- package/src/testing/run-loader.ts +2 -0
- package/src/testing/run-middleware.ts +56 -11
- package/src/testing/serve-shell-request.ts +15 -2
- package/src/thenable-status.ts +11 -0
- package/src/types/request-scope.ts +8 -0
- package/src/vite/discovery/state.ts +21 -13
- package/src/vite/plugins/expose-internal-ids.ts +2 -3
- package/src/vite/router-discovery.ts +29 -27
package/dist/vite/index.js
CHANGED
|
@@ -119,7 +119,7 @@ __export(client_ref_hashing_exports, {
|
|
|
119
119
|
hashRefKey: () => hashRefKey,
|
|
120
120
|
transformClientRefs: () => transformClientRefs
|
|
121
121
|
});
|
|
122
|
-
import { relative as
|
|
122
|
+
import { relative as relative4 } from "node:path";
|
|
123
123
|
import { createHash as createHash3 } from "node:crypto";
|
|
124
124
|
function hashRefKey(relativeId) {
|
|
125
125
|
return createHash3("sha256").update(relativeId).digest("hex").slice(0, 12);
|
|
@@ -132,10 +132,10 @@ function computeProductionHash(projectRoot, refKey) {
|
|
|
132
132
|
const absPath = decodeURIComponent(
|
|
133
133
|
refKey.slice(CLIENT_IN_SERVER_PKG_PROXY_PREFIX.length)
|
|
134
134
|
);
|
|
135
|
-
toHash =
|
|
135
|
+
toHash = relative4(projectRoot, absPath).replaceAll("\\", "/");
|
|
136
136
|
} else if (refKey.startsWith(FS_PREFIX)) {
|
|
137
137
|
const absPath = refKey.slice(FS_PREFIX.length - 1);
|
|
138
|
-
toHash =
|
|
138
|
+
toHash = relative4(projectRoot, absPath).replaceAll("\\", "/");
|
|
139
139
|
} else if (refKey.startsWith("/")) {
|
|
140
140
|
toHash = refKey.slice(1);
|
|
141
141
|
} else {
|
|
@@ -1903,6 +1903,70 @@ function transformInlineHandlers(fnName, virtualPrefix, s, code, filePath, virtu
|
|
|
1903
1903
|
return true;
|
|
1904
1904
|
}
|
|
1905
1905
|
|
|
1906
|
+
// src/build/route-types/scan-filter.ts
|
|
1907
|
+
import { isAbsolute, join, relative } from "node:path";
|
|
1908
|
+
import { readdirSync } from "node:fs";
|
|
1909
|
+
import picomatch from "picomatch";
|
|
1910
|
+
var DEFAULT_EXCLUDE_PATTERNS = [
|
|
1911
|
+
"**/__tests__/**",
|
|
1912
|
+
"**/__mocks__/**",
|
|
1913
|
+
"**/dist/**",
|
|
1914
|
+
"**/coverage/**",
|
|
1915
|
+
"**/*.test.{ts,tsx,js,jsx}",
|
|
1916
|
+
"**/*.spec.{ts,tsx,js,jsx}"
|
|
1917
|
+
];
|
|
1918
|
+
function createScanFilter(root, opts) {
|
|
1919
|
+
const { include, exclude } = opts;
|
|
1920
|
+
const hasInclude = include && include.length > 0;
|
|
1921
|
+
const hasCustomExclude = exclude !== void 0;
|
|
1922
|
+
if (!hasInclude && !hasCustomExclude) return void 0;
|
|
1923
|
+
const effectiveExclude = exclude ?? DEFAULT_EXCLUDE_PATTERNS;
|
|
1924
|
+
const includeMatcher = hasInclude ? picomatch(include) : null;
|
|
1925
|
+
const excludeMatcher = effectiveExclude.length > 0 ? picomatch(effectiveExclude) : null;
|
|
1926
|
+
return (absolutePath) => {
|
|
1927
|
+
const rel = relative(root, absolutePath);
|
|
1928
|
+
if (excludeMatcher && excludeMatcher(rel)) return false;
|
|
1929
|
+
if (includeMatcher) return includeMatcher(rel);
|
|
1930
|
+
return true;
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
function isUniversallySkippedScanDir(name) {
|
|
1934
|
+
return name === "node_modules" || name === "dist" || name === "coverage" || name.startsWith(".");
|
|
1935
|
+
}
|
|
1936
|
+
function isInSkippedScanDir(root, absolutePath) {
|
|
1937
|
+
const rel = relative(root, absolutePath);
|
|
1938
|
+
if (isAbsolute(rel)) return false;
|
|
1939
|
+
const dirs = rel.split(/[\\/]/).slice(0, -1);
|
|
1940
|
+
if (dirs[0] === "..") return false;
|
|
1941
|
+
return dirs.some(isUniversallySkippedScanDir);
|
|
1942
|
+
}
|
|
1943
|
+
function isSkippedSourceScanDir(name) {
|
|
1944
|
+
return isUniversallySkippedScanDir(name) || name === "build";
|
|
1945
|
+
}
|
|
1946
|
+
function findTsFiles(dir, filter) {
|
|
1947
|
+
const results = [];
|
|
1948
|
+
let entries;
|
|
1949
|
+
try {
|
|
1950
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
1951
|
+
} catch (err) {
|
|
1952
|
+
console.warn(
|
|
1953
|
+
`[rango] Failed to scan directory ${dir}: ${err.message}`
|
|
1954
|
+
);
|
|
1955
|
+
return results;
|
|
1956
|
+
}
|
|
1957
|
+
for (const entry of entries) {
|
|
1958
|
+
const fullPath = join(dir, entry.name);
|
|
1959
|
+
if (entry.isDirectory()) {
|
|
1960
|
+
if (isSkippedSourceScanDir(entry.name)) continue;
|
|
1961
|
+
results.push(...findTsFiles(fullPath, filter));
|
|
1962
|
+
} else if ((entry.name.endsWith(".ts") || entry.name.endsWith(".tsx") || entry.name.endsWith(".js") || entry.name.endsWith(".jsx")) && !entry.name.includes(".gen.")) {
|
|
1963
|
+
if (filter && !filter(fullPath)) continue;
|
|
1964
|
+
results.push(fullPath);
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
return results;
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1906
1970
|
// src/vite/plugins/expose-ids/types.ts
|
|
1907
1971
|
var PRERENDER_CONFIG = {
|
|
1908
1972
|
fnName: "Prerender",
|
|
@@ -2443,8 +2507,14 @@ function compareStrings(a, b) {
|
|
|
2443
2507
|
// src/vite/discovery/state.ts
|
|
2444
2508
|
var VIRTUAL_ROUTES_MANIFEST_ID = "virtual:rsc-router/routes-manifest";
|
|
2445
2509
|
var VIRTUAL_LOADER_MANIFEST_ID = "virtual:rsc-router/loader-manifest";
|
|
2446
|
-
function
|
|
2447
|
-
|
|
2510
|
+
function recordDiscoveryFailure(state, err) {
|
|
2511
|
+
state.lastDiscoveryError = {
|
|
2512
|
+
message: err?.message ?? String(err),
|
|
2513
|
+
at: Date.now()
|
|
2514
|
+
};
|
|
2515
|
+
}
|
|
2516
|
+
function canRefreshRuntimeDiscovery(state) {
|
|
2517
|
+
return state.perRouterManifests.length > 0 || !!state.lastDiscoveryError;
|
|
2448
2518
|
}
|
|
2449
2519
|
function createDiscoveryState(entryPath, opts) {
|
|
2450
2520
|
return {
|
|
@@ -2746,7 +2816,6 @@ ${lazyImports.join(",\n")}
|
|
|
2746
2816
|
if (!isBuild) return;
|
|
2747
2817
|
if (this.environment && this.environment.name !== "rsc") return;
|
|
2748
2818
|
const fs2 = await import("node:fs/promises");
|
|
2749
|
-
const SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage"]);
|
|
2750
2819
|
async function scanDir(dir) {
|
|
2751
2820
|
const results = [];
|
|
2752
2821
|
try {
|
|
@@ -2754,7 +2823,7 @@ ${lazyImports.join(",\n")}
|
|
|
2754
2823
|
for (const entry of entries) {
|
|
2755
2824
|
const fullPath = path4.join(dir, entry.name);
|
|
2756
2825
|
if (entry.isDirectory()) {
|
|
2757
|
-
if (!
|
|
2826
|
+
if (!isSkippedSourceScanDir(entry.name)) {
|
|
2758
2827
|
results.push(...await scanDir(fullPath));
|
|
2759
2828
|
}
|
|
2760
2829
|
} else if (/\.(ts|tsx|js|jsx)$/.test(entry.name)) {
|
|
@@ -4234,7 +4303,7 @@ function transitionWhenHoistPlugin() {
|
|
|
4234
4303
|
import { normalizePath as normalizePath4, parseAst as parseAst5 } from "vite";
|
|
4235
4304
|
import MagicString5 from "magic-string";
|
|
4236
4305
|
import { createHash as createHash2 } from "node:crypto";
|
|
4237
|
-
import { relative } from "node:path";
|
|
4306
|
+
import { relative as relative2 } from "node:path";
|
|
4238
4307
|
import { hasDirective as hasDirective3 } from "@vitejs/plugin-rsc/transforms";
|
|
4239
4308
|
|
|
4240
4309
|
// src/client-urls/hoisted-revalidate.ts
|
|
@@ -4379,7 +4448,7 @@ async function hoistClientUrlsRevalidates(code, id, deps) {
|
|
|
4379
4448
|
directImport: false
|
|
4380
4449
|
});
|
|
4381
4450
|
if (calls.length === 0) return null;
|
|
4382
|
-
const relativeFile = normalizePath4(
|
|
4451
|
+
const relativeFile = normalizePath4(relative2(deps.root, file));
|
|
4383
4452
|
const s = new MagicString5(code);
|
|
4384
4453
|
const used = /* @__PURE__ */ new Map();
|
|
4385
4454
|
const entries = /* @__PURE__ */ new Map();
|
|
@@ -4600,7 +4669,7 @@ import ${JSON.stringify(`${file}?${CLIENT_URLS_REVALIDATE_QUERY}`)};
|
|
|
4600
4669
|
// src/vite/plugins/client-ref-dedup.ts
|
|
4601
4670
|
init_debug();
|
|
4602
4671
|
import { existsSync, readFileSync } from "node:fs";
|
|
4603
|
-
import { isAbsolute, join, normalize, relative as
|
|
4672
|
+
import { isAbsolute as isAbsolute2, join as join2, normalize, relative as relative3 } from "node:path";
|
|
4604
4673
|
var debug4 = createRangoDebugger(NS.transform);
|
|
4605
4674
|
var CLIENT_IN_SERVER_PROXY_PREFIX = "virtual:vite-rsc/client-in-server-package-proxy/";
|
|
4606
4675
|
var DEDUP_PREFIX = "\0rango:dedup/";
|
|
@@ -4633,7 +4702,7 @@ function readPackageMetadata(source, cache3) {
|
|
|
4633
4702
|
if (!root) return;
|
|
4634
4703
|
const cached = cache3.get(root);
|
|
4635
4704
|
if (cached !== void 0) return cached ?? void 0;
|
|
4636
|
-
const packageJsonPath =
|
|
4705
|
+
const packageJsonPath = join2(root, "package.json");
|
|
4637
4706
|
if (!existsSync(packageJsonPath)) {
|
|
4638
4707
|
cache3.set(root, null);
|
|
4639
4708
|
return;
|
|
@@ -4686,8 +4755,8 @@ function getPublicSpecifierCandidates(metadata, packageSpecifier, source) {
|
|
|
4686
4755
|
return [];
|
|
4687
4756
|
}
|
|
4688
4757
|
const candidates = /* @__PURE__ */ new Set();
|
|
4689
|
-
const sourceRelative = `./${
|
|
4690
|
-
if (sourceRelative.startsWith("./../") ||
|
|
4758
|
+
const sourceRelative = `./${relative3(metadata.root, source).replaceAll("\\", "/")}`;
|
|
4759
|
+
if (sourceRelative.startsWith("./../") || isAbsolute2(sourceRelative.slice(2))) {
|
|
4691
4760
|
return [];
|
|
4692
4761
|
}
|
|
4693
4762
|
for (const [exportKey, target] of Object.entries(metadata.exports)) {
|
|
@@ -4774,7 +4843,7 @@ function clientRefDedup() {
|
|
|
4774
4843
|
configResolved(config) {
|
|
4775
4844
|
const clientEnv = config.environments?.["client"];
|
|
4776
4845
|
clientExclude = clientEnv?.optimizeDeps?.exclude ?? config.optimizeDeps?.exclude ?? [];
|
|
4777
|
-
rootImporter =
|
|
4846
|
+
rootImporter = join2(config.root, "index.html");
|
|
4778
4847
|
resolvePackage = config.createResolver({ scan: true });
|
|
4779
4848
|
},
|
|
4780
4849
|
buildEnd() {
|
|
@@ -4848,7 +4917,7 @@ import { parseAst as parseAst6 } from "vite";
|
|
|
4848
4917
|
// src/vite/discovery/build-versions.ts
|
|
4849
4918
|
import { createHash as createHash4 } from "node:crypto";
|
|
4850
4919
|
import { isBuiltin } from "node:module";
|
|
4851
|
-
import { posix, relative as
|
|
4920
|
+
import { posix, relative as relative5 } from "node:path";
|
|
4852
4921
|
|
|
4853
4922
|
// src/router-versions.ts
|
|
4854
4923
|
var DEFAULT_ROUTER_VERSIONS_KEY = "*";
|
|
@@ -5443,7 +5512,7 @@ function portableModuleId(id, projectRoot) {
|
|
|
5443
5512
|
return path6.slice(nodeModules + 1).replace(PNPM_PEER_SUFFIX, "$1");
|
|
5444
5513
|
}
|
|
5445
5514
|
if (isAbsolutePath(path6)) {
|
|
5446
|
-
return
|
|
5515
|
+
return relative5(projectRoot, path6).replaceAll("\\", "/");
|
|
5447
5516
|
}
|
|
5448
5517
|
return withoutWorkingDirectory(path6).replace(
|
|
5449
5518
|
/([?&][^=&]+=)([^&]+)/g,
|
|
@@ -5860,7 +5929,7 @@ import { resolve } from "node:path";
|
|
|
5860
5929
|
// package.json
|
|
5861
5930
|
var package_default = {
|
|
5862
5931
|
name: "@rangojs/router",
|
|
5863
|
-
version: "0.
|
|
5932
|
+
version: "0.24.1",
|
|
5864
5933
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
5865
5934
|
keywords: [
|
|
5866
5935
|
"react",
|
|
@@ -6319,69 +6388,6 @@ declare global {
|
|
|
6319
6388
|
`;
|
|
6320
6389
|
}
|
|
6321
6390
|
|
|
6322
|
-
// src/build/route-types/scan-filter.ts
|
|
6323
|
-
import { isAbsolute as isAbsolute2, join as join2, relative as relative5 } from "node:path";
|
|
6324
|
-
import { readdirSync } from "node:fs";
|
|
6325
|
-
import picomatch from "picomatch";
|
|
6326
|
-
var DEFAULT_EXCLUDE_PATTERNS = [
|
|
6327
|
-
"**/__tests__/**",
|
|
6328
|
-
"**/__mocks__/**",
|
|
6329
|
-
"**/dist/**",
|
|
6330
|
-
"**/coverage/**",
|
|
6331
|
-
"**/*.test.{ts,tsx,js,jsx}",
|
|
6332
|
-
"**/*.spec.{ts,tsx,js,jsx}"
|
|
6333
|
-
];
|
|
6334
|
-
function createScanFilter(root, opts) {
|
|
6335
|
-
const { include, exclude } = opts;
|
|
6336
|
-
const hasInclude = include && include.length > 0;
|
|
6337
|
-
const hasCustomExclude = exclude !== void 0;
|
|
6338
|
-
if (!hasInclude && !hasCustomExclude) return void 0;
|
|
6339
|
-
const effectiveExclude = exclude ?? DEFAULT_EXCLUDE_PATTERNS;
|
|
6340
|
-
const includeMatcher = hasInclude ? picomatch(include) : null;
|
|
6341
|
-
const excludeMatcher = effectiveExclude.length > 0 ? picomatch(effectiveExclude) : null;
|
|
6342
|
-
return (absolutePath) => {
|
|
6343
|
-
const rel = relative5(root, absolutePath);
|
|
6344
|
-
if (excludeMatcher && excludeMatcher(rel)) return false;
|
|
6345
|
-
if (includeMatcher) return includeMatcher(rel);
|
|
6346
|
-
return true;
|
|
6347
|
-
};
|
|
6348
|
-
}
|
|
6349
|
-
function isUniversallySkippedScanDir(name) {
|
|
6350
|
-
return name === "node_modules" || name === "dist" || name === "coverage" || name.startsWith(".");
|
|
6351
|
-
}
|
|
6352
|
-
function isInSkippedScanDir(root, absolutePath) {
|
|
6353
|
-
const rel = relative5(root, absolutePath);
|
|
6354
|
-
if (isAbsolute2(rel)) return false;
|
|
6355
|
-
const dirs = rel.split(/[\\/]/).slice(0, -1);
|
|
6356
|
-
if (dirs[0] === "..") return false;
|
|
6357
|
-
return dirs.some(isUniversallySkippedScanDir);
|
|
6358
|
-
}
|
|
6359
|
-
function findTsFiles(dir, filter) {
|
|
6360
|
-
const results = [];
|
|
6361
|
-
let entries;
|
|
6362
|
-
try {
|
|
6363
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
6364
|
-
} catch (err) {
|
|
6365
|
-
console.warn(
|
|
6366
|
-
`[rango] Failed to scan directory ${dir}: ${err.message}`
|
|
6367
|
-
);
|
|
6368
|
-
return results;
|
|
6369
|
-
}
|
|
6370
|
-
for (const entry of entries) {
|
|
6371
|
-
const fullPath = join2(dir, entry.name);
|
|
6372
|
-
if (entry.isDirectory()) {
|
|
6373
|
-
if (isUniversallySkippedScanDir(entry.name) || entry.name === "build") {
|
|
6374
|
-
continue;
|
|
6375
|
-
}
|
|
6376
|
-
results.push(...findTsFiles(fullPath, filter));
|
|
6377
|
-
} else if ((entry.name.endsWith(".ts") || entry.name.endsWith(".tsx") || entry.name.endsWith(".js") || entry.name.endsWith(".jsx")) && !entry.name.includes(".gen.")) {
|
|
6378
|
-
if (filter && !filter(fullPath)) continue;
|
|
6379
|
-
results.push(fullPath);
|
|
6380
|
-
}
|
|
6381
|
-
}
|
|
6382
|
-
return results;
|
|
6383
|
-
}
|
|
6384
|
-
|
|
6385
6391
|
// src/build/route-types/per-module-writer.ts
|
|
6386
6392
|
import ts4 from "typescript";
|
|
6387
6393
|
|
|
@@ -8684,7 +8690,7 @@ function insertSegments(node, segments, index, leafBase, paramNames) {
|
|
|
8684
8690
|
}
|
|
8685
8691
|
const segment = segments[index];
|
|
8686
8692
|
if (segment.type === "static") {
|
|
8687
|
-
if (!node.s) node.s =
|
|
8693
|
+
if (!node.s) node.s = /* @__PURE__ */ Object.create(null);
|
|
8688
8694
|
if (!node.s[segment.value]) node.s[segment.value] = {};
|
|
8689
8695
|
insertSegments(
|
|
8690
8696
|
node.s[segment.value],
|
|
@@ -8984,6 +8990,13 @@ function jsonParseExpression(value) {
|
|
|
8984
8990
|
}
|
|
8985
8991
|
|
|
8986
8992
|
// src/context-var.ts
|
|
8993
|
+
function createVar(options) {
|
|
8994
|
+
return {
|
|
8995
|
+
__brand: "context-var",
|
|
8996
|
+
key: /* @__PURE__ */ Symbol(),
|
|
8997
|
+
cache: options?.cache !== false
|
|
8998
|
+
};
|
|
8999
|
+
}
|
|
8987
9000
|
function hasContextVars(variables) {
|
|
8988
9001
|
return Object.keys(variables).length > 0 || Object.getOwnPropertySymbols(variables).length > 0;
|
|
8989
9002
|
}
|
|
@@ -8996,7 +9009,11 @@ function getNonCacheableKeys(variables) {
|
|
|
8996
9009
|
}
|
|
8997
9010
|
return variables[NON_CACHEABLE_KEYS];
|
|
8998
9011
|
}
|
|
8999
|
-
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
|
|
9012
|
+
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
|
|
9013
|
+
"__proto__",
|
|
9014
|
+
"constructor",
|
|
9015
|
+
"prototype"
|
|
9016
|
+
]);
|
|
9000
9017
|
function contextSet(variables, keyOrVar, value, options) {
|
|
9001
9018
|
if (typeof keyOrVar === "string") {
|
|
9002
9019
|
if (FORBIDDEN_KEYS.has(keyOrVar)) {
|
|
@@ -10084,6 +10101,14 @@ function getAppVersion() {
|
|
|
10084
10101
|
return currentVersion2;
|
|
10085
10102
|
}
|
|
10086
10103
|
|
|
10104
|
+
// src/router/basename.ts
|
|
10105
|
+
function prefixBasename(path6, basename2) {
|
|
10106
|
+
if (!basename2 || !path6.startsWith("/") || path6 === basename2 || path6.startsWith(basename2 + "/") || path6.startsWith(basename2 + "?") || path6.startsWith(basename2 + "#")) {
|
|
10107
|
+
return path6;
|
|
10108
|
+
}
|
|
10109
|
+
return path6 === "/" ? basename2 : basename2 + path6;
|
|
10110
|
+
}
|
|
10111
|
+
|
|
10087
10112
|
// src/loader-redirect.tsx
|
|
10088
10113
|
init_validate_redirect_origin();
|
|
10089
10114
|
init_redirect_origin();
|
|
@@ -10493,13 +10518,10 @@ var Link = forwardRef(function Link2({
|
|
|
10493
10518
|
}, ref) {
|
|
10494
10519
|
const ctx = useContext15(NavigationStoreContext);
|
|
10495
10520
|
const isExternal = isExternalUrl(to, ctx?.origin);
|
|
10496
|
-
const resolvedTo = useMemo8(
|
|
10497
|
-
|
|
10498
|
-
|
|
10499
|
-
|
|
10500
|
-
return to;
|
|
10501
|
-
return to === "/" ? bn : bn + to;
|
|
10502
|
-
}, [to, isExternal, ctx?.basename]);
|
|
10521
|
+
const resolvedTo = useMemo8(
|
|
10522
|
+
() => isExternal ? to : prefixBasename(to, ctx?.basename),
|
|
10523
|
+
[to, isExternal, ctx?.basename]
|
|
10524
|
+
);
|
|
10503
10525
|
const configuredStrategy = prefetch ?? ctx?.defaultPrefetch ?? ENVIRONMENT_DEFAULT_PREFETCH;
|
|
10504
10526
|
const internalRef = useRef13(null);
|
|
10505
10527
|
const setRef = useCallback5(
|
|
@@ -10756,6 +10778,19 @@ var ALL_USE_ITEM_TYPES = new Set(
|
|
|
10756
10778
|
|
|
10757
10779
|
// src/server/request-context.ts
|
|
10758
10780
|
import { AsyncLocalStorage as AsyncLocalStorage5 } from "node:async_hooks";
|
|
10781
|
+
|
|
10782
|
+
// src/server/slot-accessors.ts
|
|
10783
|
+
function slotAccessor(slot, enumerable) {
|
|
10784
|
+
return {
|
|
10785
|
+
get() {
|
|
10786
|
+
return this[slot]();
|
|
10787
|
+
},
|
|
10788
|
+
enumerable,
|
|
10789
|
+
configurable: true
|
|
10790
|
+
};
|
|
10791
|
+
}
|
|
10792
|
+
|
|
10793
|
+
// src/server/request-context.ts
|
|
10759
10794
|
init_cookie_name();
|
|
10760
10795
|
|
|
10761
10796
|
// src/router/segment-resolution/mask-nested.ts
|
|
@@ -10823,12 +10858,38 @@ var THEME_READ = {
|
|
|
10823
10858
|
// src/router/segment-resolution/prefetch-deferral.ts
|
|
10824
10859
|
init_prefetch_markers();
|
|
10825
10860
|
|
|
10861
|
+
// src/cache/document-cache-policy.ts
|
|
10862
|
+
init_cookie_name();
|
|
10863
|
+
|
|
10864
|
+
// src/server/rewrite-trail.ts
|
|
10865
|
+
var REWRITE_TRAIL = createVar();
|
|
10866
|
+
|
|
10826
10867
|
// src/cache/invalidation-order.ts
|
|
10827
10868
|
var order = globalThis[/* @__PURE__ */ Symbol.for("rangojs-router:tag-invalidation-order")] ??= { seq: 0, tags: /* @__PURE__ */ new Map(), forgotten: 0 };
|
|
10828
10869
|
var responseStarts = globalThis[/* @__PURE__ */ Symbol.for("rangojs-router:response-execution-start")] ??= /* @__PURE__ */ new WeakMap();
|
|
10829
10870
|
|
|
10830
10871
|
// src/server/request-context.ts
|
|
10831
10872
|
var requestContextStorage = new AsyncLocalStorage5();
|
|
10873
|
+
var RES_SLOT = /* @__PURE__ */ Symbol("rango.requestContext.res");
|
|
10874
|
+
var RENDER_BARRIER_SLOT = /* @__PURE__ */ Symbol(
|
|
10875
|
+
"rango.requestContext.renderBarrier"
|
|
10876
|
+
);
|
|
10877
|
+
var RES_DESCRIPTOR = {
|
|
10878
|
+
...slotAccessor(RES_SLOT, true),
|
|
10879
|
+
set(_) {
|
|
10880
|
+
throw new Error(
|
|
10881
|
+
"ctx.res is read-only. Use ctx.header() to set response headers, or cookies() for cookie mutations."
|
|
10882
|
+
);
|
|
10883
|
+
}
|
|
10884
|
+
};
|
|
10885
|
+
var RENDER_BARRIER_DESCRIPTOR = slotAccessor(
|
|
10886
|
+
RENDER_BARRIER_SLOT,
|
|
10887
|
+
true
|
|
10888
|
+
);
|
|
10889
|
+
var DERIVED_RENDER_BARRIER_DESCRIPTOR = {
|
|
10890
|
+
...RENDER_BARRIER_DESCRIPTOR,
|
|
10891
|
+
enumerable: false
|
|
10892
|
+
};
|
|
10832
10893
|
|
|
10833
10894
|
// src/route-definition/redirect.ts
|
|
10834
10895
|
init_redirect_origin();
|
|
@@ -13213,12 +13274,18 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
13213
13274
|
"acquireBuildEnv",
|
|
13214
13275
|
() => acquireBuildEnv(s, viteCommand, viteMode)
|
|
13215
13276
|
);
|
|
13216
|
-
|
|
13277
|
+
const temp = await timed(
|
|
13217
13278
|
debugDiscovery,
|
|
13218
13279
|
"getOrCreateTempServer",
|
|
13219
13280
|
() => getOrCreateTempServer()
|
|
13220
|
-
)
|
|
13221
|
-
|
|
13281
|
+
);
|
|
13282
|
+
tempRscEnv = temp.env;
|
|
13283
|
+
if (!tempRscEnv) {
|
|
13284
|
+
recordDiscoveryFailure(
|
|
13285
|
+
s,
|
|
13286
|
+
temp.error ?? "temp runner unavailable for discovery"
|
|
13287
|
+
);
|
|
13288
|
+
} else {
|
|
13222
13289
|
optimizerHashBefore2 = tempRscEnv.depsOptimizer?.metadata?.browserHash;
|
|
13223
13290
|
await timed(
|
|
13224
13291
|
debugDiscovery,
|
|
@@ -13236,6 +13303,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
13236
13303
|
);
|
|
13237
13304
|
}
|
|
13238
13305
|
} catch (err) {
|
|
13306
|
+
recordDiscoveryFailure(s, err);
|
|
13239
13307
|
emitDiscoveryFailure(
|
|
13240
13308
|
err,
|
|
13241
13309
|
optimizerHashBefore2,
|
|
@@ -13277,10 +13345,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
13277
13345
|
() => propagateDiscoveryState(rscEnv)
|
|
13278
13346
|
);
|
|
13279
13347
|
} catch (err) {
|
|
13280
|
-
s
|
|
13281
|
-
message: err?.message ?? String(err),
|
|
13282
|
-
at: Date.now()
|
|
13283
|
-
};
|
|
13348
|
+
recordDiscoveryFailure(s, err);
|
|
13284
13349
|
emitDiscoveryFailure(
|
|
13285
13350
|
err,
|
|
13286
13351
|
optimizerHashBefore,
|
|
@@ -13685,7 +13750,6 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
13685
13750
|
const refreshRuntimeDiscovery = async () => {
|
|
13686
13751
|
const rscEnv = server.environments?.rsc;
|
|
13687
13752
|
const hasMainRunner = !!rscEnv?.runner;
|
|
13688
|
-
if (!hasMainRunner && s.perRouterManifests.length === 0) return;
|
|
13689
13753
|
let previousRouteShape = routeShapeSignature();
|
|
13690
13754
|
await gate.runRefreshCycle(async () => {
|
|
13691
13755
|
const hmrStart = performance.now();
|
|
@@ -13756,10 +13820,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
13756
13820
|
forceCloudflareWorkerReload(rscEnv, expectedEpoch);
|
|
13757
13821
|
}
|
|
13758
13822
|
} catch (err) {
|
|
13759
|
-
s
|
|
13760
|
-
message: err?.message ?? String(err),
|
|
13761
|
-
at: Date.now()
|
|
13762
|
-
};
|
|
13823
|
+
recordDiscoveryFailure(s, err);
|
|
13763
13824
|
const whenError2 = showTransitionWhenError(err);
|
|
13764
13825
|
if (whenError2) {
|
|
13765
13826
|
console.error(whenError2.message);
|
|
@@ -13879,7 +13940,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
13879
13940
|
"watcher: regenerated gen files (%sms)",
|
|
13880
13941
|
(performance.now() - regenStart).toFixed(1)
|
|
13881
13942
|
);
|
|
13882
|
-
if (canRefreshRuntimeDiscovery(s
|
|
13943
|
+
if (canRefreshRuntimeDiscovery(s)) {
|
|
13883
13944
|
refreshRuntimeDiscovery().catch((err) => {
|
|
13884
13945
|
console.warn(
|
|
13885
13946
|
`[rango] Runtime re-discovery error: ${err.message}`
|
|
@@ -14003,7 +14064,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
14003
14064
|
}
|
|
14004
14065
|
s.cachedRouterFiles = void 0;
|
|
14005
14066
|
}
|
|
14006
|
-
if (canRefreshRuntimeDiscovery(s
|
|
14067
|
+
if (canRefreshRuntimeDiscovery(s)) {
|
|
14007
14068
|
gate.noteRouteEvent();
|
|
14008
14069
|
}
|
|
14009
14070
|
scheduleRouteRegeneration();
|
package/package.json
CHANGED
package/skills/caching/SKILL.md
CHANGED
|
@@ -881,10 +881,22 @@ another, request-scoped APIs are guarded inside a cache scope:
|
|
|
881
881
|
| Response writes: `ctx.headers.set()`, `setCookie()`, `setStatus()`, `onResponse()` | **throws** — response side effects lost on a hit |
|
|
882
882
|
| `ctx.get(var)` where the var is `{ cache: false }` | **throws** on read |
|
|
883
883
|
| `ctx.theme`, `getRequestContext().theme` | **throws** — the theme is the visitor's cookie |
|
|
884
|
+
| `ctx.rewrittenFrom` (any ctx, `getRequestContext().rewrittenFrom`) | **throws** — the visitor's URL of a rewritten request |
|
|
884
885
|
| `ctx.set(var, value)` for a cacheable var | allowed (children are cached too) |
|
|
885
886
|
| Any of the above **inside a registered loader** (`loader(...)`) | **allowed** — loaders always run fresh |
|
|
886
887
|
| Any read in the boundary's own `key()`, `keyGenerator`, `condition()` or `tags()` | **allowed** — it picks or labels the entry, never rendered |
|
|
887
888
|
|
|
889
|
+
`ctx.rewrittenFrom` (the visitor's URL when middleware rewrote the request,
|
|
890
|
+
`/middleware` → "Rewrite") throws on every read inside a `cache()` boundary
|
|
891
|
+
or a `"use cache"` function, on a request nothing rewrote too, like
|
|
892
|
+
`cookies()`: the entry would hold whichever URL the first caller came from,
|
|
893
|
+
and a read that worked only on direct visits would break the first time a
|
|
894
|
+
rewritten request reached it. A PPR shell capture and a render the document
|
|
895
|
+
cache stores read it as `undefined` instead of throwing: those renders are
|
|
896
|
+
the target page as a direct visit renders it, and the rewritten visitor's
|
|
897
|
+
own render (outside them) still sees its URL. A response route under
|
|
898
|
+
`cache()` whose handler read it is not stored.
|
|
899
|
+
|
|
888
900
|
A loader body invoked from a handler with `await ctx.use(Loader)` may read
|
|
889
901
|
`cookies()`/`headers()`/`ctx.request.headers`, but its response writes still throw: on a hit the
|
|
890
902
|
handler is skipped, so that loader never runs.
|
package/skills/catalog.json
CHANGED
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
},
|
|
144
144
|
{
|
|
145
145
|
"name": "middleware",
|
|
146
|
-
"description": "Define global (router.use) and route (middleware()) middleware in @rangojs/router — auth gates, logging, response headers, context variables, redirects. Use when gating routes behind auth checks, logging requests, setting headers or ctx variables before a handler runs, or deciding whether middleware wraps server actions.",
|
|
146
|
+
"description": "Define global (router.use) and route (middleware()) middleware in @rangojs/router — auth gates, logging, response headers, context variables, redirects, rewrites (throw rewrite() renders another route at the same URL). Use when gating routes behind auth checks, logging requests, setting headers or ctx variables before a handler runs, showing a maintenance page at the visitor's URL, or deciding whether middleware wraps server actions.",
|
|
147
147
|
"argumentHint": "[middleware-name]",
|
|
148
148
|
"path": "skills/middleware/SKILL.md"
|
|
149
149
|
},
|
|
@@ -287,6 +287,80 @@ Two scars worth copying:
|
|
|
287
287
|
actions (`isAction(CartActions)`) while product/related loaders hold —
|
|
288
288
|
three freshness outcomes in a single commit, decided per loader.
|
|
289
289
|
|
|
290
|
+
## After an action: what the server decides
|
|
291
|
+
|
|
292
|
+
The browser sends the action with the ids of the segments the page holds.
|
|
293
|
+
Everything after that is decided on the server, while it handles that request:
|
|
294
|
+
|
|
295
|
+
- **The action** runs there, and its return value is what the call resolves
|
|
296
|
+
with (`await action()`, `useActionState`).
|
|
297
|
+
- **Which loaders rerun.** Each group loader's own `revalidate()` answers, with
|
|
298
|
+
the action's `actionResult` and `formData` (above). A loader that holds keeps
|
|
299
|
+
the data the page shows.
|
|
300
|
+
- **The browser's caches.** When the response arrives the browser marks its
|
|
301
|
+
history cache stale and drops its prefetches, unless the action called
|
|
302
|
+
`keepClientCache()` (`/server-actions` → "Client Cache After an Action").
|
|
303
|
+
- **A loader that fails on its rerun** reaches your `onError` on the server and
|
|
304
|
+
the page as an error, below.
|
|
305
|
+
|
|
306
|
+
A form's React state survives an action's revalidation in a group, as on a
|
|
307
|
+
`urls()` route: the commit renders into the mounted page instead of remounting
|
|
308
|
+
it, so a controlled input keeps what the user typed.
|
|
309
|
+
|
|
310
|
+
### A loader that fails on its rerun
|
|
311
|
+
|
|
312
|
+
The action has already run when its loaders rerun, so a loader that throws
|
|
313
|
+
there does not undo it. Where the error lands decides what the user sees:
|
|
314
|
+
|
|
315
|
+
| The route | Called directly (`await action()`) | From `<form action>`, `useActionState` or `useTransition` |
|
|
316
|
+
| ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
317
|
+
| A group route, or a `urls()` route without `loading()` | The call rejects with the loader's error (`"An error occurred"` in a build), although the action returned. Nothing of that revalidation commits: the page keeps its data and its React state. | React hands the rejection to the nearest React error boundary above the form. With none of yours, the router's root error boundary replaces the page. |
|
|
318
|
+
| A `urls()` route with `loading()` | The call resolves with the action's result. The commit renders, the loader's read throws, and the nearest React error boundary catches it; with none of yours, the root one replaces the page. | The same. |
|
|
319
|
+
| Under an `errorBoundary()` (for a group, around the mount) | Its fallback replaces the route: the page and its form state unmount. | The same. |
|
|
320
|
+
|
|
321
|
+
A group route's `loading()` does not change the first row. For a failure the
|
|
322
|
+
user can retry (a flaky upstream, a rate limit), return it as data instead of
|
|
323
|
+
throwing, and render it where the data goes:
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
// stock.loader.ts
|
|
327
|
+
export const StockLoader = createLoader(async (ctx) => {
|
|
328
|
+
try {
|
|
329
|
+
return { ok: true as const, stock: await inventory.count(ctx.params.sku) };
|
|
330
|
+
} catch {
|
|
331
|
+
return { ok: false as const, reason: "Stock is unavailable. Try again." };
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// in the group's "use client" module
|
|
336
|
+
function Stock() {
|
|
337
|
+
const { data } = useLoader(StockLoader);
|
|
338
|
+
return data.ok ? (
|
|
339
|
+
<p>{data.stock} in stock</p>
|
|
340
|
+
) : (
|
|
341
|
+
<p role="status">{data.reason}</p>
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
The action resolves with its result, the page and its form state stay, and the
|
|
347
|
+
next action or navigation that reruns the loader replaces the message. Keep
|
|
348
|
+
throwing for a failure that should stop the page, with an `errorBoundary()`
|
|
349
|
+
around the mount for it.
|
|
350
|
+
|
|
351
|
+
A failure returned as data is an ordinary value, so whatever stores the
|
|
352
|
+
loader's value stores it too: a `cache()` on the loader serves it for its TTL,
|
|
353
|
+
a `ppr` shell bakes an `ssr: false` loader's value, and a prerender stores it
|
|
354
|
+
at build time. A thrown error is never stored (a stale entry keeps serving).
|
|
355
|
+
To cache the expensive part, cache the call the loader makes (`"use cache"` on
|
|
356
|
+
that function: a call that throws stores nothing) and turn the throw into
|
|
357
|
+
data in a loader that is not cached itself.
|
|
358
|
+
|
|
359
|
+
Pinned by dev + production e2e in the router repository (a group, its `urls()`
|
|
360
|
+
twin, each with and without `loading()`, and the group under an
|
|
361
|
+
`errorBoundary()`), and by a `serveShellRequest({ action })` test of the
|
|
362
|
+
response.
|
|
363
|
+
|
|
290
364
|
## Loaders are full citizens: signals and handles
|
|
291
365
|
|
|
292
366
|
Everything a DSL loader can do in the server tree works here — the loader body
|
|
@@ -373,6 +447,16 @@ need it, not the route.
|
|
|
373
447
|
|
|
374
448
|
Pinned end to end by dev + production e2e in the router repository.
|
|
375
449
|
|
|
450
|
+
**Without JavaScript.** A boundary that is not complete at the first flush
|
|
451
|
+
ships its fallback and swaps the content in with a script, which a browser
|
|
452
|
+
without JavaScript never runs. That includes a client component a loader's
|
|
453
|
+
answer draws under an inline `<Suspense>`: a fast loader's answer usually
|
|
454
|
+
makes the first flush, and `ssr: false` guarantees the loader's data does. It
|
|
455
|
+
does not cover the client component's module: the first document a build's
|
|
456
|
+
fresh server renders can still be waiting for it. The dev server matches a
|
|
457
|
+
build here: when the shell is ready, it waits for the client components the
|
|
458
|
+
payload has named and that are still loading, and for nothing else.
|
|
459
|
+
|
|
376
460
|
## `prefetch: false` — keep a loader out of prefetches
|
|
377
461
|
|
|
378
462
|
A `<Link>` prefetch of a group route runs its loaders. Flag the expensive one
|
|
@@ -442,7 +526,8 @@ a hard load of the target URL renders the full route.
|
|
|
442
526
|
reconciles instead of remounting. The canonical commit runs in a
|
|
443
527
|
`startTransition` when no new segments mount, and the wrapping layout sees
|
|
444
528
|
`useOutlet().pending === true` for the held window; a route-level
|
|
445
|
-
`loading()` does not re-flash there. `
|
|
529
|
+
`loading()` does not re-flash there. Across different routes, a `layout()`
|
|
530
|
+
keeps its instance while the navigation stays under it. `transition()` is not needed for the
|
|
446
531
|
hold; it adds the view-transition animation. Inline `<Suspense>` above each
|
|
447
532
|
`useLoader` read is still the finer-grained tool when different reads on
|
|
448
533
|
one route should wait independently.
|
package/skills/hooks/SKILL.md
CHANGED
|
@@ -112,12 +112,17 @@ cannot mismatch its server HTML.
|
|
|
112
112
|
- Navigation state, params and action state: the component shows the live
|
|
113
113
|
value right after it hydrates.
|
|
114
114
|
- Handle data a loader pushed late, and the entry's location state: both wait
|
|
115
|
-
until the whole document has streamed and been revealed
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
changes.
|
|
115
|
+
until the whole document has streamed and been revealed, and every boundary
|
|
116
|
+
has hydrated, including any boundary still suspended on the client (a lazy
|
|
117
|
+
chunk, a deferred-hydration gate; that wait has no upper bound). Then
|
|
118
|
+
location state lands in a transition and handle data in the release
|
|
119
|
+
notification. `useLocationState` readers re-render when the entry's
|
|
120
|
+
state object changes; `useHandle` readers when their selection changes.
|
|
121
|
+
- State set before then by a commit that renders no new page (a shallow
|
|
122
|
+
navigation with `revalidate: false`, Back/Forward or a revalidation that
|
|
123
|
+
keeps every segment, an action's location state) waits the same way: readers
|
|
124
|
+
keep `undefined` until every boundary has hydrated, then show the latest
|
|
125
|
+
entry's state.
|
|
121
126
|
- A navigation that commits before then mounts its page with the live values
|
|
122
127
|
from its first render. A boundary of the document that persists across that
|
|
123
128
|
navigation and has not hydrated yet then hydrates against the live values;
|
package/skills/hooks/data.md
CHANGED
|
@@ -146,7 +146,9 @@ clears the shared error.
|
|
|
146
146
|
**`load()` calls that stay local** (no broadcast, per-hook state, same
|
|
147
147
|
semantics as the old per-component `useState`):
|
|
148
148
|
|
|
149
|
-
- `load({ params: { ... } })` — explicit params.
|
|
149
|
+
- `load({ params: { ... } })` — explicit params. They are string values; a
|
|
150
|
+
`__proto__`, `constructor` or `prototype` key or a non-string value is
|
|
151
|
+
rejected and `load()` fails with `Invalid loader params`.
|
|
150
152
|
- `load({ method: "POST", body })` — mutations.
|
|
151
153
|
- Any `load()` on a `useFetchLoader(loader)` whose loader is **not**
|
|
152
154
|
registered on the current route. Two unrelated components calling
|