@rangojs/router 0.0.0-experimental.97 → 0.0.0-experimental.98914650
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 +24 -9
- package/dist/bin/rango.js +157 -63
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +1584 -639
- package/package.json +71 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +263 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +3 -1
- package/skills/hooks/SKILL.md +235 -28
- package/skills/host-router/SKILL.md +122 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +29 -5
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +173 -17
- package/skills/loader/SKILL.md +170 -23
- package/skills/middleware/SKILL.md +16 -10
- package/skills/migrate-nextjs/SKILL.md +38 -16
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +11 -7
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +250 -25
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +114 -47
- package/skills/route/SKILL.md +42 -5
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +78 -42
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +316 -26
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/vercel/SKILL.md +107 -0
- 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/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +37 -143
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +30 -59
- package/src/browser/navigation-client.ts +96 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +32 -82
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +60 -127
- package/src/browser/prefetch/cache.ts +82 -72
- package/src/browser/prefetch/fetch.ts +108 -33
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +41 -48
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +11 -11
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +20 -5
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +70 -34
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +168 -44
- package/src/browser/types.ts +36 -21
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +89 -10
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +122 -22
- 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-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +134 -32
- package/src/cache/cache-scope.ts +100 -74
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2255 -238
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +61 -20
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +22 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +25 -61
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +17 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +31 -23
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +63 -9
- package/src/index.ts +64 -9
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +65 -40
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +244 -281
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +40 -17
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -15
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +4 -41
- package/src/router/intercept-resolution.ts +14 -19
- package/src/router/lazy-includes.ts +9 -46
- package/src/router/loader-resolution.ts +91 -46
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +57 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +150 -271
- package/src/router/match-middleware/cache-store.ts +3 -33
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +31 -80
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +5 -112
- package/src/router/middleware.ts +118 -133
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +62 -67
- package/src/router/prerender-match.ts +99 -63
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +50 -56
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +68 -35
- package/src/router/router-options.ts +55 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +44 -63
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +40 -37
- package/src/router/segment-resolution/revalidation.ts +203 -285
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +87 -48
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +80 -41
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +83 -78
- package/src/rsc/helpers.ts +93 -5
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +12 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +76 -62
- package/src/rsc/rsc-rendering.ts +41 -60
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +62 -67
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +10 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +199 -142
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +150 -51
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +165 -87
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -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 +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +97 -22
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +18 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +19 -5
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +95 -82
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +26 -34
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/state.ts +39 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +185 -10
- package/src/vite/plugins/cjs-to-esm.ts +3 -18
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +12 -11
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +4 -75
- package/src/vite/plugins/expose-id-utils.ts +3 -54
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +57 -67
- package/src/vite/plugins/performance-tracks.ts +9 -16
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +26 -49
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +2 -32
- package/src/vite/plugins/version-plugin.ts +32 -23
- package/src/vite/plugins/virtual-entries.ts +35 -17
- package/src/vite/rango.ts +148 -115
- package/src/vite/router-discovery.ts +220 -68
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -34
- package/src/vite/utils/shared-utils.ts +95 -43
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -11,12 +11,16 @@ import {
|
|
|
11
11
|
getContext,
|
|
12
12
|
getNamePrefix,
|
|
13
13
|
getUrlPrefix,
|
|
14
|
+
requireDslContext,
|
|
14
15
|
type EntryData,
|
|
16
|
+
type EntryPropDatas,
|
|
17
|
+
type EntryPropSegments,
|
|
18
|
+
type HelperContext,
|
|
15
19
|
type InterceptEntry,
|
|
16
20
|
} from "../server/context";
|
|
17
21
|
import { invariant } from "../errors";
|
|
18
22
|
import { isCachedFunction } from "../cache/taint.js";
|
|
19
|
-
import {
|
|
23
|
+
import { RangoContext } from "../server/context";
|
|
20
24
|
import { isStaticHandler } from "../static-handler.js";
|
|
21
25
|
import RootLayout from "../server/root-layout";
|
|
22
26
|
import type {
|
|
@@ -38,6 +42,7 @@ import type {
|
|
|
38
42
|
} from "../route-types.js";
|
|
39
43
|
import type { RouteHelpers } from "./helpers-types.js";
|
|
40
44
|
import { resolveHandlerUse, mergeHandlerUse } from "./resolve-handler-use.js";
|
|
45
|
+
import { ALL_USE_ITEM_TYPES } from "./use-item-types.js";
|
|
41
46
|
|
|
42
47
|
/**
|
|
43
48
|
* Check if an item contains routes (directly or inside nested structures like cache).
|
|
@@ -61,16 +66,105 @@ const hasRoutesInItem = (item: AllUseItems): boolean => {
|
|
|
61
66
|
return false;
|
|
62
67
|
};
|
|
63
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Fresh empty collections shared by every from-scratch segment entry. Returns
|
|
71
|
+
* new arrays/objects per call so no two entries share mutable references.
|
|
72
|
+
* mountPath is intentionally NOT included here — each call site adds it from
|
|
73
|
+
* getUrlPrefix() where applicable: the route() and transition() helpers add
|
|
74
|
+
* none, while path() (which also builds a `type: "route"` entry) and the
|
|
75
|
+
* structural helpers (layout/cache/middleware/parallel) do.
|
|
76
|
+
*/
|
|
77
|
+
const emptySegmentBase = (): EntryPropDatas &
|
|
78
|
+
EntryPropSegments & { loading: undefined } => ({
|
|
79
|
+
loading: undefined,
|
|
80
|
+
middleware: [],
|
|
81
|
+
revalidate: [],
|
|
82
|
+
errorBoundary: [],
|
|
83
|
+
notFoundBoundary: [],
|
|
84
|
+
layout: [],
|
|
85
|
+
parallel: {},
|
|
86
|
+
intercept: [],
|
|
87
|
+
loader: [],
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Run a children/use callback as a nested scope, flatten the result, and assert
|
|
92
|
+
* every item is a valid use item. `kind` preserves the existing error wording
|
|
93
|
+
* ("use()" vs "children" callback).
|
|
94
|
+
*/
|
|
95
|
+
function runAndValidateUseItems(
|
|
96
|
+
store: ReturnType<typeof getContext>,
|
|
97
|
+
namespace: string,
|
|
98
|
+
entry: EntryData,
|
|
99
|
+
cb: () => any,
|
|
100
|
+
label: string,
|
|
101
|
+
kind: "use" | "children",
|
|
102
|
+
): AllUseItems[] {
|
|
103
|
+
const result = store.run(namespace, entry, cb)?.flat(3);
|
|
104
|
+
return validateUseItems(result, namespace, label, kind);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Assert an already-invoked, flattened callback result is a use-item array. */
|
|
108
|
+
function validateUseItems(
|
|
109
|
+
result: any,
|
|
110
|
+
namespace: string,
|
|
111
|
+
label: string,
|
|
112
|
+
kind: "use" | "children",
|
|
113
|
+
): AllUseItems[] {
|
|
114
|
+
invariant(
|
|
115
|
+
Array.isArray(result) && result.every((item) => isValidUseItem(item)),
|
|
116
|
+
`${label}() ${kind === "use" ? "use()" : "children"} callback must return an array of use items [${namespace}]`,
|
|
117
|
+
);
|
|
118
|
+
return result as AllUseItems[];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** True when a children/use result contains no routes (directly or nested). */
|
|
122
|
+
const isOrphan = (result: AllUseItems[]): boolean =>
|
|
123
|
+
!result.some((item) => item != null && hasRoutesInItem(item));
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Register a routeless structural entry as an orphan sibling: clear its parent
|
|
127
|
+
* pointer so it leaves the middleware/parent-pointer chain (LOAD-BEARING — see
|
|
128
|
+
* docs/tree-structure.md) and push it onto the parent's layout[] so it renders
|
|
129
|
+
* as a wrapper. Used by cache()/middleware()/transition(); layout() runs extra
|
|
130
|
+
* validation and registers inline.
|
|
131
|
+
*/
|
|
132
|
+
const attachOrphanSibling = (
|
|
133
|
+
parent: EntryData | null,
|
|
134
|
+
entry: EntryData,
|
|
135
|
+
): void => {
|
|
136
|
+
entry.parent = null;
|
|
137
|
+
if (parent && "layout" in parent) parent.layout.push(entry);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Run `fn` with `ctx.parent` temporarily redirected to `temp` — a satellite
|
|
142
|
+
* entry that captures the attachments declared by a use() callback — restoring
|
|
143
|
+
* the original parent afterward, including on throw. loader()/intercept() each
|
|
144
|
+
* build their own tempParent shape (intercept keeps a loading get/set accessor
|
|
145
|
+
* and a captured-layouts array); this only centralizes the save/restore.
|
|
146
|
+
*/
|
|
147
|
+
function withParent<T>(ctx: HelperContext, temp: EntryData, fn: () => T): T {
|
|
148
|
+
const original = ctx.parent;
|
|
149
|
+
ctx.parent = temp;
|
|
150
|
+
try {
|
|
151
|
+
return fn();
|
|
152
|
+
} finally {
|
|
153
|
+
ctx.parent = original;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
64
157
|
const revalidate: RouteHelpers<any, any>["revalidate"] = (fn) => {
|
|
65
|
-
const ctx =
|
|
66
|
-
|
|
158
|
+
const { store, ctx } = requireDslContext(
|
|
159
|
+
"revalidate() must be called inside urls()",
|
|
160
|
+
);
|
|
67
161
|
|
|
68
162
|
// Attach to last entry in stack
|
|
69
163
|
const parent = ctx.parent;
|
|
70
164
|
if (!parent || !("revalidate" in parent)) {
|
|
71
165
|
invariant(false, "No parent entry available for revalidate()");
|
|
72
166
|
}
|
|
73
|
-
const name = `$${
|
|
167
|
+
const name = `$${store.getNextIndex("revalidate")}`;
|
|
74
168
|
parent.revalidate.push(fn);
|
|
75
169
|
return { name, type: "revalidate" } as RevalidateItem;
|
|
76
170
|
};
|
|
@@ -108,15 +202,16 @@ const revalidate: RouteHelpers<any, any>["revalidate"] = (fn) => {
|
|
|
108
202
|
* ```
|
|
109
203
|
*/
|
|
110
204
|
const errorBoundary: RouteHelpers<any, any>["errorBoundary"] = (fallback) => {
|
|
111
|
-
const ctx =
|
|
112
|
-
|
|
205
|
+
const { store, ctx } = requireDslContext(
|
|
206
|
+
"errorBoundary() must be called inside urls()",
|
|
207
|
+
);
|
|
113
208
|
|
|
114
209
|
// Attach to parent entry in stack
|
|
115
210
|
const parent = ctx.parent;
|
|
116
211
|
if (!parent || !("errorBoundary" in parent)) {
|
|
117
212
|
invariant(false, "No parent entry available for errorBoundary()");
|
|
118
213
|
}
|
|
119
|
-
const name = `$${
|
|
214
|
+
const name = `$${store.getNextIndex("errorBoundary")}`;
|
|
120
215
|
parent.errorBoundary.push(fallback);
|
|
121
216
|
return { name, type: "errorBoundary" } as ErrorBoundaryItem;
|
|
122
217
|
};
|
|
@@ -155,15 +250,16 @@ const errorBoundary: RouteHelpers<any, any>["errorBoundary"] = (fallback) => {
|
|
|
155
250
|
const notFoundBoundary: RouteHelpers<any, any>["notFoundBoundary"] = (
|
|
156
251
|
fallback,
|
|
157
252
|
) => {
|
|
158
|
-
const ctx =
|
|
159
|
-
|
|
253
|
+
const { store, ctx } = requireDslContext(
|
|
254
|
+
"notFoundBoundary() must be called inside urls()",
|
|
255
|
+
);
|
|
160
256
|
|
|
161
257
|
// Attach to parent entry in stack
|
|
162
258
|
const parent = ctx.parent;
|
|
163
259
|
if (!parent || !("notFoundBoundary" in parent)) {
|
|
164
260
|
invariant(false, "No parent entry available for notFoundBoundary()");
|
|
165
261
|
}
|
|
166
|
-
const name = `$${
|
|
262
|
+
const name = `$${store.getNextIndex("notFoundBoundary")}`;
|
|
167
263
|
parent.notFoundBoundary.push(fallback);
|
|
168
264
|
return { name, type: "notFoundBoundary" } as NotFoundBoundaryItem;
|
|
169
265
|
};
|
|
@@ -177,8 +273,9 @@ const notFoundBoundary: RouteHelpers<any, any>["notFoundBoundary"] = (
|
|
|
177
273
|
* for the intercept to activate.
|
|
178
274
|
*/
|
|
179
275
|
const when: RouteHelpers<any, any>["when"] = (fn) => {
|
|
180
|
-
const ctx =
|
|
181
|
-
|
|
276
|
+
const { store, ctx } = requireDslContext(
|
|
277
|
+
"when() must be called inside intercept()",
|
|
278
|
+
);
|
|
182
279
|
|
|
183
280
|
// The when() function needs to be captured by the intercept's tempParent
|
|
184
281
|
// which should have a `when` array. If not present, we're not inside intercept()
|
|
@@ -190,7 +287,7 @@ const when: RouteHelpers<any, any>["when"] = (fn) => {
|
|
|
190
287
|
);
|
|
191
288
|
}
|
|
192
289
|
|
|
193
|
-
const name = `$${
|
|
290
|
+
const name = `$${store.getNextIndex("when")}`;
|
|
194
291
|
parent.when.push(fn);
|
|
195
292
|
return { name, type: "when" } as WhenItem;
|
|
196
293
|
};
|
|
@@ -205,21 +302,21 @@ const when: RouteHelpers<any, any>["when"] = (fn) => {
|
|
|
205
302
|
* Supports these call signatures:
|
|
206
303
|
* - cache() - no args, uses app-level defaults (for loader caching)
|
|
207
304
|
* - cache(() => [...]) - wraps children with app-level defaults
|
|
208
|
-
* - cache('profileName') - uses a named cache profile
|
|
209
|
-
* - cache('profileName', () => [...]) - named profile with children
|
|
210
305
|
* - cache({ ttl: 60 }, () => [...]) - with explicit options
|
|
306
|
+
*
|
|
307
|
+
* Named cache profiles are applied via the `"use cache: <profile>"` directive,
|
|
308
|
+
* not a `cache("profileName")` form in the route tree.
|
|
211
309
|
*/
|
|
212
310
|
const cache: RouteHelpers<any, any>["cache"] = (
|
|
213
311
|
optionsOrChildren?:
|
|
214
312
|
| PartialCacheOptions
|
|
215
313
|
| false
|
|
216
|
-
| string
|
|
217
314
|
| (() => UseItems<AllUseItems>),
|
|
218
315
|
maybeChildren?: () => UseItems<AllUseItems>,
|
|
219
316
|
) => {
|
|
220
|
-
const store =
|
|
221
|
-
|
|
222
|
-
|
|
317
|
+
const { store, ctx } = requireDslContext(
|
|
318
|
+
"cache() must be called inside urls()",
|
|
319
|
+
);
|
|
223
320
|
|
|
224
321
|
// Handle overloaded signature
|
|
225
322
|
let options: PartialCacheOptions | false;
|
|
@@ -229,18 +326,6 @@ const cache: RouteHelpers<any, any>["cache"] = (
|
|
|
229
326
|
// cache() - no args, use defaults
|
|
230
327
|
options = {};
|
|
231
328
|
children = undefined;
|
|
232
|
-
} else if (typeof optionsOrChildren === "string") {
|
|
233
|
-
// cache('profileName') or cache('profileName', () => [...])
|
|
234
|
-
// Resolve from context-scoped profiles (set per-router via HelperContext).
|
|
235
|
-
const ctxStore = RSCRouterContext.getStore();
|
|
236
|
-
const profile = ctxStore?.cacheProfiles?.[optionsOrChildren];
|
|
237
|
-
invariant(
|
|
238
|
-
profile,
|
|
239
|
-
`cache("${optionsOrChildren}"): unknown cache profile. ` +
|
|
240
|
-
`Define it in createRouter({ cacheProfiles: { "${optionsOrChildren}": { ttl: ... } } }).`,
|
|
241
|
-
);
|
|
242
|
-
options = { ttl: profile.ttl, swr: profile.swr, tags: profile.tags };
|
|
243
|
-
children = maybeChildren;
|
|
244
329
|
} else if (typeof optionsOrChildren === "function") {
|
|
245
330
|
// cache(() => [...]) - use empty options (will use defaults)
|
|
246
331
|
options = {};
|
|
@@ -271,26 +356,18 @@ const cache: RouteHelpers<any, any>["cache"] = (
|
|
|
271
356
|
// Create orphan cache entry (like orphan layout)
|
|
272
357
|
// Subsequent siblings in the same array will attach to this entry
|
|
273
358
|
const namespace = `${ctx.namespace}.${cacheIndex}`;
|
|
274
|
-
const
|
|
359
|
+
const urlPrefix = getUrlPrefix();
|
|
275
360
|
|
|
276
361
|
const entry = {
|
|
362
|
+
...emptySegmentBase(),
|
|
277
363
|
id: namespace,
|
|
278
364
|
shortCode: store.getShortCode("cache"),
|
|
279
365
|
type: "cache",
|
|
280
366
|
parent: parent, // link to current parent for hierarchy
|
|
281
367
|
cache: cacheConfig,
|
|
282
368
|
handler: RootLayout,
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
revalidate: [],
|
|
286
|
-
errorBoundary: [],
|
|
287
|
-
notFoundBoundary: [],
|
|
288
|
-
layout: [],
|
|
289
|
-
parallel: {},
|
|
290
|
-
intercept: [],
|
|
291
|
-
loader: [],
|
|
292
|
-
...(cacheUrlPrefix ? { mountPath: cacheUrlPrefix } : {}),
|
|
293
|
-
} as EntryData;
|
|
369
|
+
...(urlPrefix ? { mountPath: urlPrefix } : {}),
|
|
370
|
+
} satisfies EntryData;
|
|
294
371
|
|
|
295
372
|
// Attach to parent's layout array (cache entries are structural like layouts)
|
|
296
373
|
if (parent && "layout" in parent) {
|
|
@@ -304,10 +381,10 @@ const cache: RouteHelpers<any, any>["cache"] = (
|
|
|
304
381
|
return { name: namespace, type: "cache" } as CacheItem;
|
|
305
382
|
}
|
|
306
383
|
|
|
307
|
-
// Inside a loader() use() callback, only the direct form — cache()/cache(opts)
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
//
|
|
384
|
+
// Inside a loader() use() callback, only the direct form — cache()/cache(opts)
|
|
385
|
+
// — writes cache config to the loader entry. The wrapper form creates a
|
|
386
|
+
// structural cache boundary with its own children scope, which has no effect
|
|
387
|
+
// on the loader and would silently no-op.
|
|
311
388
|
invariant(
|
|
312
389
|
!(ctx.parent && (ctx.parent as any).type === "loader"),
|
|
313
390
|
"cache() wrapper form is not valid inside loader() use(). Use cache({...}) without children to configure the loader's cache.",
|
|
@@ -317,9 +394,10 @@ const cache: RouteHelpers<any, any>["cache"] = (
|
|
|
317
394
|
const namespace = `${ctx.namespace}.${cacheIndex}`;
|
|
318
395
|
const cacheShortCode = store.getShortCode("cache");
|
|
319
396
|
|
|
320
|
-
const
|
|
397
|
+
const urlPrefix = getUrlPrefix();
|
|
321
398
|
|
|
322
399
|
const entry = {
|
|
400
|
+
...emptySegmentBase(),
|
|
323
401
|
id: namespace,
|
|
324
402
|
shortCode: cacheShortCode,
|
|
325
403
|
type: "cache",
|
|
@@ -327,40 +405,22 @@ const cache: RouteHelpers<any, any>["cache"] = (
|
|
|
327
405
|
cache: cacheConfig,
|
|
328
406
|
// Cache entries render like layouts (with Outlet as default handler)
|
|
329
407
|
handler: RootLayout, // RootLayout just renders <Outlet />
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
revalidate: [],
|
|
333
|
-
errorBoundary: [],
|
|
334
|
-
notFoundBoundary: [],
|
|
335
|
-
layout: [],
|
|
336
|
-
parallel: {},
|
|
337
|
-
intercept: [],
|
|
338
|
-
loader: [],
|
|
339
|
-
...(cacheUrlPrefix2 ? { mountPath: cacheUrlPrefix2 } : {}),
|
|
340
|
-
} as EntryData;
|
|
408
|
+
...(urlPrefix ? { mountPath: urlPrefix } : {}),
|
|
409
|
+
} satisfies EntryData;
|
|
341
410
|
|
|
342
411
|
// Run children with cache entry as parent
|
|
343
|
-
const result =
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
412
|
+
const result = runAndValidateUseItems(
|
|
413
|
+
store,
|
|
414
|
+
namespace,
|
|
415
|
+
entry,
|
|
416
|
+
children,
|
|
417
|
+
"cache",
|
|
418
|
+
"children",
|
|
348
419
|
);
|
|
349
420
|
|
|
350
|
-
//
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
Array.isArray(result) &&
|
|
354
|
-
result.some((item) => hasRoutesInItem(item));
|
|
355
|
-
|
|
356
|
-
if (!hasRoutes) {
|
|
357
|
-
const parent = ctx.parent;
|
|
358
|
-
if (parent && "layout" in parent) {
|
|
359
|
-
// Attach to parent's layout array (cache entries are structural like layouts)
|
|
360
|
-
entry.parent = null;
|
|
361
|
-
parent.layout.push(entry);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
421
|
+
// Cache entries are structural like layouts: with no routes inside, register
|
|
422
|
+
// as an orphan sibling.
|
|
423
|
+
if (isOrphan(result)) attachOrphanSibling(ctx.parent, entry);
|
|
364
424
|
|
|
365
425
|
return { name: namespace, type: "cache", uses: result } as CacheItem;
|
|
366
426
|
};
|
|
@@ -406,9 +466,9 @@ const middleware: RouteHelpers<any, any>["middleware"] = (...args: any[]) => {
|
|
|
406
466
|
}
|
|
407
467
|
}
|
|
408
468
|
|
|
409
|
-
const store =
|
|
410
|
-
|
|
411
|
-
|
|
469
|
+
const { store, ctx } = requireDslContext(
|
|
470
|
+
"middleware() must be called inside urls()",
|
|
471
|
+
);
|
|
412
472
|
|
|
413
473
|
if (!children) {
|
|
414
474
|
// Sibling mode: attach to parent entry
|
|
@@ -427,22 +487,15 @@ const middleware: RouteHelpers<any, any>["middleware"] = (...args: any[]) => {
|
|
|
427
487
|
|
|
428
488
|
const urlPrefix = getUrlPrefix();
|
|
429
489
|
const entry = {
|
|
490
|
+
...emptySegmentBase(),
|
|
430
491
|
id: namespace,
|
|
431
492
|
shortCode: store.getShortCode("layout"),
|
|
432
493
|
type: "layout",
|
|
433
494
|
parent: ctx.parent,
|
|
434
495
|
handler: RootLayout,
|
|
435
|
-
loading: undefined,
|
|
436
496
|
middleware: [...fns],
|
|
437
|
-
revalidate: [],
|
|
438
|
-
errorBoundary: [],
|
|
439
|
-
notFoundBoundary: [],
|
|
440
|
-
layout: [],
|
|
441
|
-
parallel: {},
|
|
442
|
-
intercept: [],
|
|
443
|
-
loader: [],
|
|
444
497
|
...(urlPrefix ? { mountPath: urlPrefix } : {}),
|
|
445
|
-
}
|
|
498
|
+
} satisfies EntryData;
|
|
446
499
|
|
|
447
500
|
// Run children callback. If the second arg was actually a middleware fn
|
|
448
501
|
// (old variadic form: middleware(mw1, mw2)), this will return a non-array
|
|
@@ -455,25 +508,14 @@ const middleware: RouteHelpers<any, any>["middleware"] = (...args: any[]) => {
|
|
|
455
508
|
"To pass multiple middleware, use middleware([fn1, fn2]).",
|
|
456
509
|
);
|
|
457
510
|
|
|
458
|
-
const result =
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
511
|
+
const result = validateUseItems(
|
|
512
|
+
rawResult.flat(3),
|
|
513
|
+
namespace,
|
|
514
|
+
"middleware",
|
|
515
|
+
"children",
|
|
463
516
|
);
|
|
464
517
|
|
|
465
|
-
|
|
466
|
-
result &&
|
|
467
|
-
Array.isArray(result) &&
|
|
468
|
-
result.some((item) => item != null && hasRoutesInItem(item));
|
|
469
|
-
|
|
470
|
-
if (!hasRoutes) {
|
|
471
|
-
const parent = ctx.parent;
|
|
472
|
-
if (parent && "layout" in parent) {
|
|
473
|
-
entry.parent = null;
|
|
474
|
-
parent.layout.push(entry);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
518
|
+
if (isOrphan(result)) attachOrphanSibling(ctx.parent, entry);
|
|
477
519
|
|
|
478
520
|
return {
|
|
479
521
|
name: namespace,
|
|
@@ -483,9 +525,9 @@ const middleware: RouteHelpers<any, any>["middleware"] = (...args: any[]) => {
|
|
|
483
525
|
};
|
|
484
526
|
|
|
485
527
|
const parallel: RouteHelpers<any, any>["parallel"] = (slots, use) => {
|
|
486
|
-
const store =
|
|
487
|
-
|
|
488
|
-
|
|
528
|
+
const { store, ctx } = requireDslContext(
|
|
529
|
+
"parallel() must be called inside urls()",
|
|
530
|
+
);
|
|
489
531
|
|
|
490
532
|
if (!ctx.parent || !ctx.parent?.parallel) {
|
|
491
533
|
invariant(false, "No parent entry available for parallel()");
|
|
@@ -537,20 +579,12 @@ const parallel: RouteHelpers<any, any>["parallel"] = (slots, use) => {
|
|
|
537
579
|
// Create full EntryData for parallel with its own loaders/revalidate/loading
|
|
538
580
|
const parallelUrlPrefix = getUrlPrefix();
|
|
539
581
|
const entry = {
|
|
582
|
+
...emptySegmentBase(),
|
|
540
583
|
id: namespace,
|
|
541
584
|
shortCode: store.getShortCode("parallel"),
|
|
542
585
|
type: "parallel",
|
|
543
586
|
parent: null, // Parallels don't participate in parent chain traversal
|
|
544
587
|
handler: unwrappedSlots,
|
|
545
|
-
loading: undefined, // Allow loading() to attach loading state
|
|
546
|
-
middleware: [],
|
|
547
|
-
revalidate: [],
|
|
548
|
-
errorBoundary: [],
|
|
549
|
-
notFoundBoundary: [],
|
|
550
|
-
layout: [],
|
|
551
|
-
parallel: {},
|
|
552
|
-
intercept: [],
|
|
553
|
-
loader: [],
|
|
554
588
|
...(parallelUrlPrefix ? { mountPath: parallelUrlPrefix } : {}),
|
|
555
589
|
...(hasStaticSlot
|
|
556
590
|
? {
|
|
@@ -605,10 +639,13 @@ const parallel: RouteHelpers<any, any>["parallel"] = (slots, use) => {
|
|
|
605
639
|
"parallel",
|
|
606
640
|
);
|
|
607
641
|
if (slotMergedUse) {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
642
|
+
runAndValidateUseItems(
|
|
643
|
+
store,
|
|
644
|
+
namespace,
|
|
645
|
+
slotEntry,
|
|
646
|
+
slotMergedUse,
|
|
647
|
+
"parallel",
|
|
648
|
+
"use",
|
|
612
649
|
);
|
|
613
650
|
}
|
|
614
651
|
|
|
@@ -648,9 +685,9 @@ const intercept = (
|
|
|
648
685
|
handler: any,
|
|
649
686
|
use?: () => any[],
|
|
650
687
|
) => {
|
|
651
|
-
const store =
|
|
652
|
-
|
|
653
|
-
|
|
688
|
+
const { store, ctx } = requireDslContext(
|
|
689
|
+
"intercept() must be called inside urls()",
|
|
690
|
+
);
|
|
654
691
|
|
|
655
692
|
if (!ctx.parent || !ctx.parent?.intercept) {
|
|
656
693
|
invariant(false, "No parent entry available for intercept()");
|
|
@@ -689,15 +726,13 @@ const intercept = (
|
|
|
689
726
|
|
|
690
727
|
// Run merged use callback to collect loaders, revalidate, middleware, etc.
|
|
691
728
|
if (mergedUse) {
|
|
692
|
-
//
|
|
693
|
-
// so that middleware, loader, revalidate attach to the intercept entry
|
|
694
|
-
const originalParent = ctx.parent;
|
|
695
|
-
|
|
696
|
-
// Capture layouts in a temporary array
|
|
729
|
+
// Capture layout() calls into a temporary array
|
|
697
730
|
const capturedLayouts: EntryData[] = [];
|
|
698
731
|
|
|
732
|
+
// Temporary parent so middleware/loader/revalidate/when attach to the
|
|
733
|
+
// intercept entry; the loading get/set accessor mirrors writes onto `entry`.
|
|
699
734
|
const tempParent = {
|
|
700
|
-
...
|
|
735
|
+
...ctx.parent,
|
|
701
736
|
middleware: entry.middleware,
|
|
702
737
|
revalidate: entry.revalidate,
|
|
703
738
|
errorBoundary: entry.errorBoundary,
|
|
@@ -705,7 +740,6 @@ const intercept = (
|
|
|
705
740
|
loader: entry.loader,
|
|
706
741
|
layout: capturedLayouts, // Capture layout() calls
|
|
707
742
|
when: entry.when, // Capture when() conditions
|
|
708
|
-
// Use getter/setter to capture loading on the entry
|
|
709
743
|
get loading() {
|
|
710
744
|
return entry.loading;
|
|
711
745
|
},
|
|
@@ -713,12 +747,10 @@ const intercept = (
|
|
|
713
747
|
entry.loading = value;
|
|
714
748
|
},
|
|
715
749
|
};
|
|
716
|
-
ctx.parent = tempParent as EntryData;
|
|
717
750
|
|
|
718
|
-
const result =
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
ctx.parent = originalParent;
|
|
751
|
+
const result = withParent(ctx, tempParent as EntryData, () =>
|
|
752
|
+
mergedUse()?.flat(3),
|
|
753
|
+
);
|
|
722
754
|
|
|
723
755
|
// Extract layout from captured layouts (use first one if multiple)
|
|
724
756
|
// Layout inside intercept should always be ReactNode or Handler, not Record slots
|
|
@@ -728,10 +760,7 @@ const intercept = (
|
|
|
728
760
|
| Handler<any, any, any>;
|
|
729
761
|
}
|
|
730
762
|
|
|
731
|
-
|
|
732
|
-
Array.isArray(result) && result.every((item) => isValidUseItem(item)),
|
|
733
|
-
`intercept() use() callback must return an array of use items [${namespace}]`,
|
|
734
|
-
);
|
|
763
|
+
validateUseItems(result, namespace, "intercept", "use");
|
|
735
764
|
}
|
|
736
765
|
|
|
737
766
|
ctx.parent.intercept.push(entry);
|
|
@@ -741,10 +770,10 @@ const intercept = (
|
|
|
741
770
|
/**
|
|
742
771
|
* Loader helper - attaches a loader to the current entry
|
|
743
772
|
*/
|
|
744
|
-
const
|
|
745
|
-
const store =
|
|
746
|
-
|
|
747
|
-
|
|
773
|
+
const loader: RouteHelpers<any, any>["loader"] = (loaderDef, use) => {
|
|
774
|
+
const { store, ctx } = requireDslContext(
|
|
775
|
+
"loader() must be called inside urls()",
|
|
776
|
+
);
|
|
748
777
|
|
|
749
778
|
// Attach to last entry in stack
|
|
750
779
|
if (!ctx.parent || !ctx.parent?.loader) {
|
|
@@ -765,23 +794,22 @@ const loaderFn: RouteHelpers<any, any>["loader"] = (loaderDef, use) => {
|
|
|
765
794
|
|
|
766
795
|
// If any use callback is in effect, run it to collect revalidation rules and cache config
|
|
767
796
|
if (mergedUse) {
|
|
768
|
-
// Temporarily set context for revalidate()/cache() calls to target this loader
|
|
769
|
-
const originalParent = ctx.parent;
|
|
770
797
|
// Create a temporary "parent" with type "loader" so cache() can detect it.
|
|
771
798
|
// Save existing .cache to distinguish inherited config from newly set config.
|
|
772
|
-
const parentCache = (
|
|
799
|
+
const parentCache = (ctx.parent as any).cache;
|
|
773
800
|
const tempParent = {
|
|
774
|
-
...
|
|
801
|
+
...ctx.parent,
|
|
775
802
|
type: "loader",
|
|
776
803
|
revalidate: loaderEntry.revalidate,
|
|
777
804
|
};
|
|
778
|
-
ctx.parent = tempParent as EntryData;
|
|
779
805
|
|
|
780
|
-
const result =
|
|
806
|
+
const result = withParent(ctx, tempParent as EntryData, () =>
|
|
807
|
+
mergedUse()?.flat(3),
|
|
808
|
+
);
|
|
781
809
|
|
|
782
810
|
// Copy cache config only if cache() was called during the use() callback.
|
|
783
|
-
// The spread
|
|
784
|
-
//
|
|
811
|
+
// The spread may carry an inherited .cache from a parent cache() boundary —
|
|
812
|
+
// only copy if it was newly set.
|
|
785
813
|
if (
|
|
786
814
|
(tempParent as any).cache &&
|
|
787
815
|
(tempParent as any).cache !== parentCache
|
|
@@ -789,13 +817,7 @@ const loaderFn: RouteHelpers<any, any>["loader"] = (loaderDef, use) => {
|
|
|
789
817
|
(loaderEntry as any).cache = (tempParent as any).cache;
|
|
790
818
|
}
|
|
791
819
|
|
|
792
|
-
|
|
793
|
-
ctx.parent = originalParent;
|
|
794
|
-
|
|
795
|
-
invariant(
|
|
796
|
-
Array.isArray(result) && result.every((item) => isValidUseItem(item)),
|
|
797
|
-
`loader() use() callback must return an array of use items [${name}]`,
|
|
798
|
-
);
|
|
820
|
+
validateUseItems(result, name, "loader", "use");
|
|
799
821
|
}
|
|
800
822
|
|
|
801
823
|
ctx.parent.loader.push(loaderEntry);
|
|
@@ -806,10 +828,10 @@ const loaderFn: RouteHelpers<any, any>["loader"] = (loaderDef, use) => {
|
|
|
806
828
|
* Loading helper - attaches a loading component to the current entry
|
|
807
829
|
* Loading components are static (no context) and shown during navigation
|
|
808
830
|
*/
|
|
809
|
-
const
|
|
810
|
-
const store =
|
|
811
|
-
|
|
812
|
-
|
|
831
|
+
const loading: RouteHelpers<any, any>["loading"] = (component, options) => {
|
|
832
|
+
const { store, ctx } = requireDslContext(
|
|
833
|
+
"loading() must be called inside urls()",
|
|
834
|
+
);
|
|
813
835
|
|
|
814
836
|
const parent = ctx.parent;
|
|
815
837
|
if (!parent || !("loading" in parent)) {
|
|
@@ -832,10 +854,13 @@ const loadingFn: RouteHelpers<any, any>["loading"] = (component, options) => {
|
|
|
832
854
|
};
|
|
833
855
|
|
|
834
856
|
/**
|
|
835
|
-
* Transition helper -
|
|
836
|
-
*
|
|
857
|
+
* Transition helper - opts the entry (or a wrapped group of routes) into
|
|
858
|
+
* transition-driven navigation by attaching a TransitionConfig. This drives the
|
|
859
|
+
* commit through startTransition (content hold on all React versions) and, on
|
|
860
|
+
* experimental React, places a `<ViewTransition>` boundary unless
|
|
861
|
+
* `viewTransition: false`. See skills/view-transitions for the matrix.
|
|
837
862
|
*/
|
|
838
|
-
const
|
|
863
|
+
const transition = (
|
|
839
864
|
configOrChildren?: TransitionConfig | (() => UseItems<AllUseItems>),
|
|
840
865
|
maybeChildren?: () => UseItems<AllUseItems>,
|
|
841
866
|
): TransitionItem => {
|
|
@@ -849,9 +874,9 @@ const transitionFn = (
|
|
|
849
874
|
const children: (() => UseItems<AllUseItems>) | undefined =
|
|
850
875
|
typeof configOrChildren === "function" ? configOrChildren : maybeChildren;
|
|
851
876
|
|
|
852
|
-
const store =
|
|
853
|
-
|
|
854
|
-
|
|
877
|
+
const { store, ctx } = requireDslContext(
|
|
878
|
+
"transition() must be called inside urls()",
|
|
879
|
+
);
|
|
855
880
|
|
|
856
881
|
const name = `$${store.getNextIndex("transition")}`;
|
|
857
882
|
|
|
@@ -868,68 +893,43 @@ const transitionFn = (
|
|
|
868
893
|
// Position 2: wrapper — create a transparent layout with transition config
|
|
869
894
|
const namespace = `${ctx.namespace}.${store.getNextIndex("transition")}`;
|
|
870
895
|
const entry = {
|
|
896
|
+
...emptySegmentBase(),
|
|
871
897
|
id: namespace,
|
|
872
898
|
shortCode: store.getShortCode("layout"),
|
|
873
899
|
type: "layout",
|
|
874
900
|
parent: ctx.parent,
|
|
875
901
|
handler: RootLayout,
|
|
876
|
-
loading: undefined,
|
|
877
902
|
transition: config,
|
|
878
|
-
|
|
879
|
-
revalidate: [],
|
|
880
|
-
errorBoundary: [],
|
|
881
|
-
notFoundBoundary: [],
|
|
882
|
-
layout: [],
|
|
883
|
-
parallel: {},
|
|
884
|
-
intercept: [],
|
|
885
|
-
loader: [],
|
|
886
|
-
} as EntryData;
|
|
887
|
-
|
|
888
|
-
const result = store.run(namespace, entry, children)?.flat(3);
|
|
903
|
+
} satisfies EntryData;
|
|
889
904
|
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
905
|
+
const result = runAndValidateUseItems(
|
|
906
|
+
store,
|
|
907
|
+
namespace,
|
|
908
|
+
entry,
|
|
909
|
+
children,
|
|
910
|
+
"transition",
|
|
911
|
+
"children",
|
|
893
912
|
);
|
|
894
913
|
|
|
895
|
-
|
|
896
|
-
result &&
|
|
897
|
-
Array.isArray(result) &&
|
|
898
|
-
result.some((item) => hasRoutesInItem(item));
|
|
899
|
-
|
|
900
|
-
if (!hasRoutes) {
|
|
901
|
-
const parent = ctx.parent;
|
|
902
|
-
if (parent && "layout" in parent) {
|
|
903
|
-
entry.parent = null;
|
|
904
|
-
parent.layout.push(entry);
|
|
905
|
-
}
|
|
906
|
-
}
|
|
914
|
+
if (isOrphan(result)) attachOrphanSibling(ctx.parent, entry);
|
|
907
915
|
|
|
908
916
|
return { name: namespace, type: "transition" } as TransitionItem;
|
|
909
917
|
};
|
|
910
918
|
|
|
911
|
-
const
|
|
912
|
-
const store =
|
|
913
|
-
|
|
914
|
-
|
|
919
|
+
const route: RouteHelpers<any, any>["route"] = (name, handler, use) => {
|
|
920
|
+
const { store, ctx } = requireDslContext(
|
|
921
|
+
"route() must be called inside urls()",
|
|
922
|
+
);
|
|
915
923
|
|
|
916
924
|
const namespace = `${ctx.namespace}.${store.getNextIndex("route")}.${name}`;
|
|
917
925
|
|
|
918
926
|
const entry = {
|
|
927
|
+
...emptySegmentBase(),
|
|
919
928
|
id: namespace,
|
|
920
929
|
shortCode: store.getShortCode("route"),
|
|
921
930
|
type: "route",
|
|
922
931
|
parent: ctx.parent,
|
|
923
932
|
handler: handler as unknown as Handler<any, any, any>,
|
|
924
|
-
loading: undefined, // Allow loading() to attach loading state
|
|
925
|
-
middleware: [],
|
|
926
|
-
revalidate: [],
|
|
927
|
-
errorBoundary: [],
|
|
928
|
-
notFoundBoundary: [],
|
|
929
|
-
layout: [],
|
|
930
|
-
parallel: {},
|
|
931
|
-
intercept: [],
|
|
932
|
-
loader: [],
|
|
933
933
|
} satisfies EntryData;
|
|
934
934
|
|
|
935
935
|
/* We will throw if user is registring same route name twice */
|
|
@@ -944,10 +944,13 @@ const routeFn: RouteHelpers<any, any>["route"] = (name, handler, use) => {
|
|
|
944
944
|
const mergedUse = mergeHandlerUse(handlerUseFn, use, "route");
|
|
945
945
|
/* Run use and attach handlers */
|
|
946
946
|
if (mergedUse) {
|
|
947
|
-
const result =
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
947
|
+
const result = runAndValidateUseItems(
|
|
948
|
+
store,
|
|
949
|
+
namespace,
|
|
950
|
+
entry,
|
|
951
|
+
mergedUse,
|
|
952
|
+
"route",
|
|
953
|
+
"use",
|
|
951
954
|
);
|
|
952
955
|
return { name: namespace, type: "route", uses: result } as RouteItem;
|
|
953
956
|
}
|
|
@@ -957,9 +960,9 @@ const routeFn: RouteHelpers<any, any>["route"] = (name, handler, use) => {
|
|
|
957
960
|
};
|
|
958
961
|
|
|
959
962
|
const layout: RouteHelpers<any, any>["layout"] = (handler, use) => {
|
|
960
|
-
const store =
|
|
961
|
-
|
|
962
|
-
|
|
963
|
+
const { store, ctx } = requireDslContext(
|
|
964
|
+
"layout() must be called inside urls()",
|
|
965
|
+
);
|
|
963
966
|
|
|
964
967
|
invariant(
|
|
965
968
|
!ctx.parent || ctx.parent.type !== "parallel",
|
|
@@ -977,20 +980,12 @@ const layout: RouteHelpers<any, any>["layout"] = (handler, use) => {
|
|
|
977
980
|
|
|
978
981
|
const urlPrefix = getUrlPrefix();
|
|
979
982
|
const entry = {
|
|
983
|
+
...emptySegmentBase(),
|
|
980
984
|
id: namespace,
|
|
981
985
|
shortCode,
|
|
982
986
|
type: "layout",
|
|
983
987
|
parent: ctx.parent,
|
|
984
988
|
handler: unwrappedHandler,
|
|
985
|
-
loading: undefined, // Allow loading() to attach loading state
|
|
986
|
-
middleware: [],
|
|
987
|
-
revalidate: [],
|
|
988
|
-
errorBoundary: [],
|
|
989
|
-
notFoundBoundary: [],
|
|
990
|
-
parallel: {},
|
|
991
|
-
intercept: [],
|
|
992
|
-
layout: [],
|
|
993
|
-
loader: [],
|
|
994
989
|
...(urlPrefix ? { mountPath: urlPrefix } : {}),
|
|
995
990
|
...(isStatic
|
|
996
991
|
? {
|
|
@@ -1012,11 +1007,13 @@ const layout: RouteHelpers<any, any>["layout"] = (handler, use) => {
|
|
|
1012
1007
|
// Run merged use callback if present
|
|
1013
1008
|
let result: AllUseItems[] | undefined;
|
|
1014
1009
|
if (mergedUse) {
|
|
1015
|
-
result =
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1010
|
+
result = runAndValidateUseItems(
|
|
1011
|
+
store,
|
|
1012
|
+
namespace,
|
|
1013
|
+
entry,
|
|
1014
|
+
mergedUse,
|
|
1015
|
+
"layout",
|
|
1016
|
+
"use",
|
|
1020
1017
|
);
|
|
1021
1018
|
}
|
|
1022
1019
|
|
|
@@ -1058,9 +1055,7 @@ const layout: RouteHelpers<any, any>["layout"] = (handler, use) => {
|
|
|
1058
1055
|
`Orphan layouts can only be defined inside route or layout > check [${namespace}]`,
|
|
1059
1056
|
);
|
|
1060
1057
|
|
|
1061
|
-
|
|
1062
|
-
entry.parent = null;
|
|
1063
|
-
parent.layout.push(entry);
|
|
1058
|
+
attachOrphanSibling(parent, entry);
|
|
1064
1059
|
}
|
|
1065
1060
|
}
|
|
1066
1061
|
|
|
@@ -1073,33 +1068,15 @@ const layout: RouteHelpers<any, any>["layout"] = (handler, use) => {
|
|
|
1073
1068
|
} as LayoutItem;
|
|
1074
1069
|
};
|
|
1075
1070
|
|
|
1076
|
-
const isValidUseItem = (item: any): item is AllUseItems | undefined | null =>
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
(item
|
|
1081
|
-
typeof item === "object" &&
|
|
1082
|
-
"type" in item &&
|
|
1083
|
-
[
|
|
1084
|
-
"layout",
|
|
1085
|
-
"route",
|
|
1086
|
-
"middleware",
|
|
1087
|
-
"revalidate",
|
|
1088
|
-
"parallel",
|
|
1089
|
-
"intercept",
|
|
1090
|
-
"loader",
|
|
1091
|
-
"loading",
|
|
1092
|
-
"errorBoundary",
|
|
1093
|
-
"notFoundBoundary",
|
|
1094
|
-
"when",
|
|
1095
|
-
"cache",
|
|
1096
|
-
"transition",
|
|
1097
|
-
"include", // For urls() include() helper
|
|
1098
|
-
].includes(item.type))
|
|
1099
|
-
);
|
|
1100
|
-
};
|
|
1071
|
+
const isValidUseItem = (item: any): item is AllUseItems | undefined | null =>
|
|
1072
|
+
item == null ||
|
|
1073
|
+
(typeof item === "object" &&
|
|
1074
|
+
"type" in item &&
|
|
1075
|
+
ALL_USE_ITEM_TYPES.has(item.type));
|
|
1101
1076
|
|
|
1102
|
-
//
|
|
1077
|
+
// DSL helpers exported for direct import from @rangojs/router and for
|
|
1078
|
+
// assembly into the RouteHelpers object in helper-factories.ts. The route-item
|
|
1079
|
+
// types are discriminated by their `type` literal, so the helpers carry no brand.
|
|
1103
1080
|
export {
|
|
1104
1081
|
layout,
|
|
1105
1082
|
cache,
|
|
@@ -1110,25 +1087,11 @@ export {
|
|
|
1110
1087
|
when,
|
|
1111
1088
|
errorBoundary,
|
|
1112
1089
|
notFoundBoundary,
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
const isOrphanLayout = (item: AllUseItems): boolean => {
|
|
1119
|
-
return (
|
|
1120
|
-
item.type === "layout" &&
|
|
1121
|
-
!item.uses?.some((child) => hasRoutesInItem(child))
|
|
1122
|
-
);
|
|
1123
|
-
};
|
|
1124
|
-
|
|
1125
|
-
// Internal exports used by helper-factories.ts
|
|
1126
|
-
export {
|
|
1127
|
-
routeFn,
|
|
1128
|
-
loaderFn,
|
|
1129
|
-
loadingFn,
|
|
1130
|
-
transitionFn,
|
|
1131
|
-
hasRoutesInItem,
|
|
1090
|
+
route,
|
|
1091
|
+
loader,
|
|
1092
|
+
loading,
|
|
1093
|
+
transition,
|
|
1132
1094
|
isValidUseItem,
|
|
1133
|
-
|
|
1095
|
+
emptySegmentBase,
|
|
1096
|
+
runAndValidateUseItems,
|
|
1134
1097
|
};
|