@rangojs/router 0.0.0-experimental.32 → 0.0.0-experimental.3232cd17
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/AGENTS.md +4 -0
- package/README.md +198 -44
- package/dist/bin/rango.js +287 -105
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3248 -1117
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +73 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +107 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +245 -21
- package/skills/caching/SKILL.md +302 -6
- 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 +364 -0
- package/skills/hooks/SKILL.md +270 -30
- package/skills/host-router/SKILL.md +82 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +49 -5
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +294 -30
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +203 -7
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +250 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +97 -5
- package/skills/router-setup/SKILL.md +90 -5
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- 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 +329 -27
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +67 -40
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +86 -147
- 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/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +148 -19
- package/src/browser/navigation-client.ts +187 -67
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +18 -66
- package/src/browser/partial-update.ts +123 -94
- package/src/browser/prefetch/cache.ts +214 -36
- package/src/browser/prefetch/fetch.ts +260 -38
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +126 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +93 -11
- package/src/browser/react/NavigationProvider.tsx +115 -34
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +49 -7
- 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 +23 -69
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +22 -5
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +215 -76
- package/src/browser/scroll-restoration.ts +46 -39
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +176 -50
- package/src/browser/types.ts +95 -11
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +137 -32
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- 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 +278 -96
- package/src/build/route-types/scan-filter.ts +9 -2
- 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 +149 -43
- package/src/cache/cache-scope.ts +148 -81
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2550 -93
- package/src/cache/cf/index.ts +11 -17
- package/src/cache/document-cache.ts +78 -27
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +23 -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/taint.ts +55 -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 +108 -290
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +84 -2
- package/src/debug.ts +2 -2
- 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 +70 -22
- 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 +52 -26
- package/src/index.ts +100 -38
- 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-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +437 -274
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +113 -37
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +52 -10
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -17
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +108 -9
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +45 -22
- package/src/router/handler-context.ts +83 -41
- package/src/router/intercept-resolution.ts +25 -23
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +213 -30
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +49 -45
- package/src/router/match-api.ts +120 -204
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +27 -6
- package/src/router/match-middleware/cache-lookup.ts +205 -249
- package/src/router/match-middleware/cache-store.ts +45 -32
- package/src/router/match-middleware/intercept-resolution.ts +8 -28
- package/src/router/match-middleware/segment-resolution.ts +52 -18
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +104 -40
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +173 -143
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +109 -63
- package/src/router/prerender-match.ts +190 -54
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +63 -55
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +6 -28
- package/src/router/router-interfaces.ts +100 -35
- package/src/router/router-options.ts +91 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +242 -75
- package/src/router/segment-resolution/helpers.ts +63 -24
- package/src/router/segment-resolution/loader-cache.ts +41 -37
- package/src/router/segment-resolution/revalidation.ts +456 -372
- 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 +2 -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 +91 -46
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +134 -43
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +492 -383
- package/src/rsc/helpers.ts +162 -46
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +30 -3
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +90 -63
- package/src/rsc/rsc-rendering.ts +56 -54
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +74 -67
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +25 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +134 -0
- package/src/segment-system.tsx +272 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +309 -61
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +26 -24
- package/src/server/loader-registry.ts +10 -28
- package/src/server/request-context.ts +338 -126
- package/src/ssr/index.tsx +23 -15
- package/src/static-handler.ts +27 -18
- 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 +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +233 -81
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +107 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -14
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +192 -99
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +51 -6
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin-types.ts +187 -69
- package/src/vite/plugins/cjs-to-esm.ts +8 -18
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/plugins/expose-action-id.ts +49 -98
- package/src/vite/plugins/expose-id-utils.ts +11 -50
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
- package/src/vite/plugins/expose-internal-ids.ts +554 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +41 -20
- package/src/vite/plugins/virtual-entries.ts +2 -17
- package/src/vite/rango.ts +257 -289
- package/src/vite/router-discovery.ts +930 -140
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/banner.ts +4 -4
- 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 +20 -52
- package/src/vite/utils/prerender-utils.ts +27 -29
- package/src/vite/utils/shared-utils.ts +92 -42
- 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 {
|
|
@@ -37,6 +41,8 @@ import type {
|
|
|
37
41
|
UseItems,
|
|
38
42
|
} from "../route-types.js";
|
|
39
43
|
import type { RouteHelpers } from "./helpers-types.js";
|
|
44
|
+
import { resolveHandlerUse, mergeHandlerUse } from "./resolve-handler-use.js";
|
|
45
|
+
import { ALL_USE_ITEM_TYPES } from "./use-item-types.js";
|
|
40
46
|
|
|
41
47
|
/**
|
|
42
48
|
* Check if an item contains routes (directly or inside nested structures like cache).
|
|
@@ -54,19 +60,111 @@ const hasRoutesInItem = (item: AllUseItems): boolean => {
|
|
|
54
60
|
if (item.type === "layout" && item.uses) {
|
|
55
61
|
return item.uses.some((child) => hasRoutesInItem(child));
|
|
56
62
|
}
|
|
63
|
+
if (item.type === "middleware" && item.uses) {
|
|
64
|
+
return item.uses.some((child) => hasRoutesInItem(child));
|
|
65
|
+
}
|
|
57
66
|
return false;
|
|
58
67
|
};
|
|
59
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
|
+
|
|
60
157
|
const revalidate: RouteHelpers<any, any>["revalidate"] = (fn) => {
|
|
61
|
-
const ctx =
|
|
62
|
-
|
|
158
|
+
const { store, ctx } = requireDslContext(
|
|
159
|
+
"revalidate() must be called inside urls()",
|
|
160
|
+
);
|
|
63
161
|
|
|
64
162
|
// Attach to last entry in stack
|
|
65
163
|
const parent = ctx.parent;
|
|
66
164
|
if (!parent || !("revalidate" in parent)) {
|
|
67
165
|
invariant(false, "No parent entry available for revalidate()");
|
|
68
166
|
}
|
|
69
|
-
const name = `$${
|
|
167
|
+
const name = `$${store.getNextIndex("revalidate")}`;
|
|
70
168
|
parent.revalidate.push(fn);
|
|
71
169
|
return { name, type: "revalidate" } as RevalidateItem;
|
|
72
170
|
};
|
|
@@ -104,15 +202,16 @@ const revalidate: RouteHelpers<any, any>["revalidate"] = (fn) => {
|
|
|
104
202
|
* ```
|
|
105
203
|
*/
|
|
106
204
|
const errorBoundary: RouteHelpers<any, any>["errorBoundary"] = (fallback) => {
|
|
107
|
-
const ctx =
|
|
108
|
-
|
|
205
|
+
const { store, ctx } = requireDslContext(
|
|
206
|
+
"errorBoundary() must be called inside urls()",
|
|
207
|
+
);
|
|
109
208
|
|
|
110
209
|
// Attach to parent entry in stack
|
|
111
210
|
const parent = ctx.parent;
|
|
112
211
|
if (!parent || !("errorBoundary" in parent)) {
|
|
113
212
|
invariant(false, "No parent entry available for errorBoundary()");
|
|
114
213
|
}
|
|
115
|
-
const name = `$${
|
|
214
|
+
const name = `$${store.getNextIndex("errorBoundary")}`;
|
|
116
215
|
parent.errorBoundary.push(fallback);
|
|
117
216
|
return { name, type: "errorBoundary" } as ErrorBoundaryItem;
|
|
118
217
|
};
|
|
@@ -151,15 +250,16 @@ const errorBoundary: RouteHelpers<any, any>["errorBoundary"] = (fallback) => {
|
|
|
151
250
|
const notFoundBoundary: RouteHelpers<any, any>["notFoundBoundary"] = (
|
|
152
251
|
fallback,
|
|
153
252
|
) => {
|
|
154
|
-
const ctx =
|
|
155
|
-
|
|
253
|
+
const { store, ctx } = requireDslContext(
|
|
254
|
+
"notFoundBoundary() must be called inside urls()",
|
|
255
|
+
);
|
|
156
256
|
|
|
157
257
|
// Attach to parent entry in stack
|
|
158
258
|
const parent = ctx.parent;
|
|
159
259
|
if (!parent || !("notFoundBoundary" in parent)) {
|
|
160
260
|
invariant(false, "No parent entry available for notFoundBoundary()");
|
|
161
261
|
}
|
|
162
|
-
const name = `$${
|
|
262
|
+
const name = `$${store.getNextIndex("notFoundBoundary")}`;
|
|
163
263
|
parent.notFoundBoundary.push(fallback);
|
|
164
264
|
return { name, type: "notFoundBoundary" } as NotFoundBoundaryItem;
|
|
165
265
|
};
|
|
@@ -173,8 +273,9 @@ const notFoundBoundary: RouteHelpers<any, any>["notFoundBoundary"] = (
|
|
|
173
273
|
* for the intercept to activate.
|
|
174
274
|
*/
|
|
175
275
|
const when: RouteHelpers<any, any>["when"] = (fn) => {
|
|
176
|
-
const ctx =
|
|
177
|
-
|
|
276
|
+
const { store, ctx } = requireDslContext(
|
|
277
|
+
"when() must be called inside intercept()",
|
|
278
|
+
);
|
|
178
279
|
|
|
179
280
|
// The when() function needs to be captured by the intercept's tempParent
|
|
180
281
|
// which should have a `when` array. If not present, we're not inside intercept()
|
|
@@ -186,7 +287,7 @@ const when: RouteHelpers<any, any>["when"] = (fn) => {
|
|
|
186
287
|
);
|
|
187
288
|
}
|
|
188
289
|
|
|
189
|
-
const name = `$${
|
|
290
|
+
const name = `$${store.getNextIndex("when")}`;
|
|
190
291
|
parent.when.push(fn);
|
|
191
292
|
return { name, type: "when" } as WhenItem;
|
|
192
293
|
};
|
|
@@ -201,21 +302,21 @@ const when: RouteHelpers<any, any>["when"] = (fn) => {
|
|
|
201
302
|
* Supports these call signatures:
|
|
202
303
|
* - cache() - no args, uses app-level defaults (for loader caching)
|
|
203
304
|
* - cache(() => [...]) - wraps children with app-level defaults
|
|
204
|
-
* - cache('profileName') - uses a named cache profile
|
|
205
|
-
* - cache('profileName', () => [...]) - named profile with children
|
|
206
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.
|
|
207
309
|
*/
|
|
208
310
|
const cache: RouteHelpers<any, any>["cache"] = (
|
|
209
311
|
optionsOrChildren?:
|
|
210
312
|
| PartialCacheOptions
|
|
211
313
|
| false
|
|
212
|
-
| string
|
|
213
314
|
| (() => UseItems<AllUseItems>),
|
|
214
315
|
maybeChildren?: () => UseItems<AllUseItems>,
|
|
215
316
|
) => {
|
|
216
|
-
const store =
|
|
217
|
-
|
|
218
|
-
|
|
317
|
+
const { store, ctx } = requireDslContext(
|
|
318
|
+
"cache() must be called inside urls()",
|
|
319
|
+
);
|
|
219
320
|
|
|
220
321
|
// Handle overloaded signature
|
|
221
322
|
let options: PartialCacheOptions | false;
|
|
@@ -225,18 +326,6 @@ const cache: RouteHelpers<any, any>["cache"] = (
|
|
|
225
326
|
// cache() - no args, use defaults
|
|
226
327
|
options = {};
|
|
227
328
|
children = undefined;
|
|
228
|
-
} else if (typeof optionsOrChildren === "string") {
|
|
229
|
-
// cache('profileName') or cache('profileName', () => [...])
|
|
230
|
-
// Resolve from context-scoped profiles (set per-router via HelperContext).
|
|
231
|
-
const ctxStore = RSCRouterContext.getStore();
|
|
232
|
-
const profile = ctxStore?.cacheProfiles?.[optionsOrChildren];
|
|
233
|
-
invariant(
|
|
234
|
-
profile,
|
|
235
|
-
`cache("${optionsOrChildren}"): unknown cache profile. ` +
|
|
236
|
-
`Define it in createRouter({ cacheProfiles: { "${optionsOrChildren}": { ttl: ... } } }).`,
|
|
237
|
-
);
|
|
238
|
-
options = { ttl: profile.ttl, swr: profile.swr, tags: profile.tags };
|
|
239
|
-
children = maybeChildren;
|
|
240
329
|
} else if (typeof optionsOrChildren === "function") {
|
|
241
330
|
// cache(() => [...]) - use empty options (will use defaults)
|
|
242
331
|
options = {};
|
|
@@ -267,26 +356,18 @@ const cache: RouteHelpers<any, any>["cache"] = (
|
|
|
267
356
|
// Create orphan cache entry (like orphan layout)
|
|
268
357
|
// Subsequent siblings in the same array will attach to this entry
|
|
269
358
|
const namespace = `${ctx.namespace}.${cacheIndex}`;
|
|
270
|
-
const
|
|
359
|
+
const urlPrefix = getUrlPrefix();
|
|
271
360
|
|
|
272
361
|
const entry = {
|
|
362
|
+
...emptySegmentBase(),
|
|
273
363
|
id: namespace,
|
|
274
364
|
shortCode: store.getShortCode("cache"),
|
|
275
365
|
type: "cache",
|
|
276
366
|
parent: parent, // link to current parent for hierarchy
|
|
277
367
|
cache: cacheConfig,
|
|
278
368
|
handler: RootLayout,
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
revalidate: [],
|
|
282
|
-
errorBoundary: [],
|
|
283
|
-
notFoundBoundary: [],
|
|
284
|
-
layout: [],
|
|
285
|
-
parallel: [],
|
|
286
|
-
intercept: [],
|
|
287
|
-
loader: [],
|
|
288
|
-
...(cacheUrlPrefix ? { mountPath: cacheUrlPrefix } : {}),
|
|
289
|
-
} as EntryData;
|
|
369
|
+
...(urlPrefix ? { mountPath: urlPrefix } : {}),
|
|
370
|
+
} satisfies EntryData;
|
|
290
371
|
|
|
291
372
|
// Attach to parent's layout array (cache entries are structural like layouts)
|
|
292
373
|
if (parent && "layout" in parent) {
|
|
@@ -300,13 +381,23 @@ const cache: RouteHelpers<any, any>["cache"] = (
|
|
|
300
381
|
return { name: namespace, type: "cache" } as CacheItem;
|
|
301
382
|
}
|
|
302
383
|
|
|
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.
|
|
388
|
+
invariant(
|
|
389
|
+
!(ctx.parent && (ctx.parent as any).type === "loader"),
|
|
390
|
+
"cache() wrapper form is not valid inside loader() use(). Use cache({...}) without children to configure the loader's cache.",
|
|
391
|
+
);
|
|
392
|
+
|
|
303
393
|
// With children: create a cache entry (like layout with caching semantics)
|
|
304
394
|
const namespace = `${ctx.namespace}.${cacheIndex}`;
|
|
305
395
|
const cacheShortCode = store.getShortCode("cache");
|
|
306
396
|
|
|
307
|
-
const
|
|
397
|
+
const urlPrefix = getUrlPrefix();
|
|
308
398
|
|
|
309
399
|
const entry = {
|
|
400
|
+
...emptySegmentBase(),
|
|
310
401
|
id: namespace,
|
|
311
402
|
shortCode: cacheShortCode,
|
|
312
403
|
type: "cache",
|
|
@@ -314,48 +405,57 @@ const cache: RouteHelpers<any, any>["cache"] = (
|
|
|
314
405
|
cache: cacheConfig,
|
|
315
406
|
// Cache entries render like layouts (with Outlet as default handler)
|
|
316
407
|
handler: RootLayout, // RootLayout just renders <Outlet />
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
revalidate: [],
|
|
320
|
-
errorBoundary: [],
|
|
321
|
-
notFoundBoundary: [],
|
|
322
|
-
layout: [],
|
|
323
|
-
parallel: [],
|
|
324
|
-
intercept: [],
|
|
325
|
-
loader: [],
|
|
326
|
-
...(cacheUrlPrefix2 ? { mountPath: cacheUrlPrefix2 } : {}),
|
|
327
|
-
} as EntryData;
|
|
408
|
+
...(urlPrefix ? { mountPath: urlPrefix } : {}),
|
|
409
|
+
} satisfies EntryData;
|
|
328
410
|
|
|
329
411
|
// Run children with cache entry as parent
|
|
330
|
-
const result =
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
412
|
+
const result = runAndValidateUseItems(
|
|
413
|
+
store,
|
|
414
|
+
namespace,
|
|
415
|
+
entry,
|
|
416
|
+
children,
|
|
417
|
+
"cache",
|
|
418
|
+
"children",
|
|
335
419
|
);
|
|
336
420
|
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
Array.isArray(result) &&
|
|
341
|
-
result.some((item) => hasRoutesInItem(item));
|
|
342
|
-
|
|
343
|
-
if (!hasRoutes) {
|
|
344
|
-
const parent = ctx.parent;
|
|
345
|
-
if (parent && "layout" in parent) {
|
|
346
|
-
// Attach to parent's layout array (cache entries are structural like layouts)
|
|
347
|
-
entry.parent = null;
|
|
348
|
-
parent.layout.push(entry);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
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);
|
|
351
424
|
|
|
352
425
|
return { name: namespace, type: "cache", uses: result } as CacheItem;
|
|
353
426
|
};
|
|
354
427
|
|
|
355
|
-
const middleware: RouteHelpers<any, any>["middleware"] = (...
|
|
428
|
+
const middleware: RouteHelpers<any, any>["middleware"] = (...args: any[]) => {
|
|
429
|
+
// Four call forms:
|
|
430
|
+
// middleware(fn) — single fn, sibling
|
|
431
|
+
// middleware(fn, () => [...]) — single fn, wrapping
|
|
432
|
+
// middleware([fn1, fn2]) — array, sibling
|
|
433
|
+
// middleware([fn1, fn2], () => [...]) — array, wrapping
|
|
434
|
+
const isArray = Array.isArray(args[0]);
|
|
435
|
+
|
|
436
|
+
// Reject the removed variadic form before executing anything.
|
|
437
|
+
// middleware(fn1, fn2, fn3) — 3+ args, always wrong.
|
|
438
|
+
// middleware(fn1, fn2) where fn2 is a middleware fn (length >= 1), not a
|
|
439
|
+
// children callback (length === 0) — legacy two-fn form, reject early.
|
|
440
|
+
if (
|
|
441
|
+
args.length > 2 ||
|
|
442
|
+
(!isArray &&
|
|
443
|
+
args.length === 2 &&
|
|
444
|
+
typeof args[1] === "function" &&
|
|
445
|
+
args[1].length > 0)
|
|
446
|
+
) {
|
|
447
|
+
throw new Error(
|
|
448
|
+
"middleware() no longer accepts variadic arguments. " +
|
|
449
|
+
"Use middleware([fn1, fn2, ...]) instead of middleware(fn1, fn2, ...).",
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const fns: MiddlewareFn<any>[] = isArray ? args[0] : [args[0]];
|
|
454
|
+
const children: (() => any[]) | undefined =
|
|
455
|
+
typeof args[1] === "function" ? args[1] : undefined;
|
|
456
|
+
|
|
356
457
|
// Prevent "use cache" functions from being used as middleware.
|
|
357
|
-
|
|
358
|
-
for (const f of fn) {
|
|
458
|
+
for (const f of fns) {
|
|
359
459
|
if (isCachedFunction(f)) {
|
|
360
460
|
throw new Error(
|
|
361
461
|
`A "use cache" function cannot be used as middleware. ` +
|
|
@@ -366,23 +466,68 @@ const middleware: RouteHelpers<any, any>["middleware"] = (...fn) => {
|
|
|
366
466
|
}
|
|
367
467
|
}
|
|
368
468
|
|
|
369
|
-
const ctx =
|
|
370
|
-
|
|
469
|
+
const { store, ctx } = requireDslContext(
|
|
470
|
+
"middleware() must be called inside urls()",
|
|
471
|
+
);
|
|
371
472
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
473
|
+
if (!children) {
|
|
474
|
+
// Sibling mode: attach to parent entry
|
|
475
|
+
const parent = ctx.parent;
|
|
476
|
+
if (!parent || !("middleware" in parent)) {
|
|
477
|
+
invariant(false, "No parent entry available for middleware()");
|
|
478
|
+
}
|
|
479
|
+
const name = `$${store.getNextIndex("middleware")}`;
|
|
480
|
+
parent.middleware.push(...fns);
|
|
481
|
+
return { name, type: "middleware" } as MiddlewareItem;
|
|
376
482
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
483
|
+
|
|
484
|
+
// Wrapping mode: create a transparent layout that carries the middleware
|
|
485
|
+
const mwIndex = store.getNextIndex("middleware");
|
|
486
|
+
const namespace = `${ctx.namespace}.${mwIndex}`;
|
|
487
|
+
|
|
488
|
+
const urlPrefix = getUrlPrefix();
|
|
489
|
+
const entry = {
|
|
490
|
+
...emptySegmentBase(),
|
|
491
|
+
id: namespace,
|
|
492
|
+
shortCode: store.getShortCode("layout"),
|
|
493
|
+
type: "layout",
|
|
494
|
+
parent: ctx.parent,
|
|
495
|
+
handler: RootLayout,
|
|
496
|
+
middleware: [...fns],
|
|
497
|
+
...(urlPrefix ? { mountPath: urlPrefix } : {}),
|
|
498
|
+
} satisfies EntryData;
|
|
499
|
+
|
|
500
|
+
// Run children callback. If the second arg was actually a middleware fn
|
|
501
|
+
// (old variadic form: middleware(mw1, mw2)), this will return a non-array
|
|
502
|
+
// and the invariant below gives a clear migration error.
|
|
503
|
+
const rawResult = store.run(namespace, entry, children);
|
|
504
|
+
|
|
505
|
+
invariant(
|
|
506
|
+
Array.isArray(rawResult),
|
|
507
|
+
"middleware(fn, children) expects the second argument to return an array of use items. " +
|
|
508
|
+
"To pass multiple middleware, use middleware([fn1, fn2]).",
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
const result = validateUseItems(
|
|
512
|
+
rawResult.flat(3),
|
|
513
|
+
namespace,
|
|
514
|
+
"middleware",
|
|
515
|
+
"children",
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
if (isOrphan(result)) attachOrphanSibling(ctx.parent, entry);
|
|
519
|
+
|
|
520
|
+
return {
|
|
521
|
+
name: namespace,
|
|
522
|
+
type: "middleware",
|
|
523
|
+
uses: result,
|
|
524
|
+
} as MiddlewareItem;
|
|
380
525
|
};
|
|
381
526
|
|
|
382
527
|
const parallel: RouteHelpers<any, any>["parallel"] = (slots, use) => {
|
|
383
|
-
const store =
|
|
384
|
-
|
|
385
|
-
|
|
528
|
+
const { store, ctx } = requireDslContext(
|
|
529
|
+
"parallel() must be called inside urls()",
|
|
530
|
+
);
|
|
386
531
|
|
|
387
532
|
if (!ctx.parent || !ctx.parent?.parallel) {
|
|
388
533
|
invariant(false, "No parent entry available for parallel()");
|
|
@@ -393,15 +538,29 @@ const parallel: RouteHelpers<any, any>["parallel"] = (slots, use) => {
|
|
|
393
538
|
"parallel() cannot be nested inside another parallel()",
|
|
394
539
|
);
|
|
395
540
|
|
|
541
|
+
const slotNames = Object.keys(slots as Record<string, any>) as `@${string}`[];
|
|
542
|
+
|
|
396
543
|
const namespace = `${ctx.namespace}.$${store.getNextIndex("parallel")}`;
|
|
397
544
|
|
|
398
|
-
// Unwrap
|
|
545
|
+
// Unwrap slot values. A slot value can be:
|
|
546
|
+
// - a Handler / ReactNode (legacy form)
|
|
547
|
+
// - a Static() definition (build-time only)
|
|
548
|
+
// - a slot descriptor `{ handler, use? }` for slot-local overrides
|
|
549
|
+
// The descriptor's `use` runs after the broadcast `use` for that slot,
|
|
550
|
+
// so single-assignment items like `loading()` placed there win without
|
|
551
|
+
// affecting siblings.
|
|
399
552
|
const unwrappedSlots: Record<string, any> = {};
|
|
553
|
+
const slotLocalUses: Record<string, (() => any[]) | undefined> = {};
|
|
400
554
|
let hasStaticSlot = false;
|
|
401
555
|
const staticSlotIds: Record<string, string> = {};
|
|
402
|
-
for (const [slotName,
|
|
556
|
+
for (const [slotName, rawSlot] of Object.entries(
|
|
403
557
|
slots as Record<string, any>,
|
|
404
558
|
)) {
|
|
559
|
+
let slotHandler: any = rawSlot;
|
|
560
|
+
if (isSlotDescriptor(rawSlot)) {
|
|
561
|
+
slotHandler = rawSlot.handler;
|
|
562
|
+
slotLocalUses[slotName] = rawSlot.use;
|
|
563
|
+
}
|
|
405
564
|
if (isStaticHandler(slotHandler)) {
|
|
406
565
|
hasStaticSlot = true;
|
|
407
566
|
unwrappedSlots[slotName] = slotHandler.handler;
|
|
@@ -420,20 +579,12 @@ const parallel: RouteHelpers<any, any>["parallel"] = (slots, use) => {
|
|
|
420
579
|
// Create full EntryData for parallel with its own loaders/revalidate/loading
|
|
421
580
|
const parallelUrlPrefix = getUrlPrefix();
|
|
422
581
|
const entry = {
|
|
582
|
+
...emptySegmentBase(),
|
|
423
583
|
id: namespace,
|
|
424
584
|
shortCode: store.getShortCode("parallel"),
|
|
425
585
|
type: "parallel",
|
|
426
586
|
parent: null, // Parallels don't participate in parent chain traversal
|
|
427
587
|
handler: unwrappedSlots,
|
|
428
|
-
loading: undefined, // Allow loading() to attach loading state
|
|
429
|
-
middleware: [],
|
|
430
|
-
revalidate: [],
|
|
431
|
-
errorBoundary: [],
|
|
432
|
-
notFoundBoundary: [],
|
|
433
|
-
layout: [],
|
|
434
|
-
parallel: [],
|
|
435
|
-
intercept: [],
|
|
436
|
-
loader: [],
|
|
437
588
|
...(parallelUrlPrefix ? { mountPath: parallelUrlPrefix } : {}),
|
|
438
589
|
...(hasStaticSlot
|
|
439
590
|
? {
|
|
@@ -445,19 +596,86 @@ const parallel: RouteHelpers<any, any>["parallel"] = (slots, use) => {
|
|
|
445
596
|
: {}),
|
|
446
597
|
} satisfies EntryData;
|
|
447
598
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
599
|
+
for (const slotName of slotNames) {
|
|
600
|
+
const slotEntry = {
|
|
601
|
+
...entry,
|
|
602
|
+
handler: { [slotName]: unwrappedSlots[slotName]! },
|
|
603
|
+
middleware: [...entry.middleware],
|
|
604
|
+
revalidate: [...entry.revalidate],
|
|
605
|
+
errorBoundary: [...entry.errorBoundary],
|
|
606
|
+
notFoundBoundary: [...entry.notFoundBoundary],
|
|
607
|
+
layout: [...entry.layout],
|
|
608
|
+
parallel: { ...entry.parallel },
|
|
609
|
+
intercept: [...entry.intercept],
|
|
610
|
+
loader: [...entry.loader],
|
|
611
|
+
...(entry.staticHandlerIds?.[slotName]
|
|
612
|
+
? {
|
|
613
|
+
isStaticPrerender: true as const,
|
|
614
|
+
staticHandlerIds: { [slotName]: entry.staticHandlerIds[slotName]! },
|
|
615
|
+
}
|
|
616
|
+
: {
|
|
617
|
+
isStaticPrerender: undefined,
|
|
618
|
+
staticHandlerIds: undefined,
|
|
619
|
+
}),
|
|
620
|
+
} satisfies EntryData;
|
|
621
|
+
|
|
622
|
+
// Per-slot merge order (narrowest-scope-wins for single-assignment items
|
|
623
|
+
// like loading()):
|
|
624
|
+
// 1. handler.use — defaults baked into the handler
|
|
625
|
+
// 2. shared `use` — broadcast at the parallel() call site
|
|
626
|
+
// 3. slot-local `use` — per-slot override via `{ handler, use }` descriptor
|
|
627
|
+
// Items that accumulate (loader, middleware, revalidate, …) compose
|
|
628
|
+
// across all three layers regardless of order.
|
|
629
|
+
const rawSlot = (slots as Record<string, any>)[slotName];
|
|
630
|
+
const slotHandlerForUse = isSlotDescriptor(rawSlot)
|
|
631
|
+
? rawSlot.handler
|
|
632
|
+
: rawSlot;
|
|
633
|
+
const slotHandlerUse = resolveHandlerUse(slotHandlerForUse);
|
|
634
|
+
const slotLocalUse = slotLocalUses[slotName];
|
|
635
|
+
const explicitUse = combineExplicitUses(use, slotLocalUse);
|
|
636
|
+
const slotMergedUse = mergeHandlerUse(
|
|
637
|
+
slotHandlerUse,
|
|
638
|
+
explicitUse,
|
|
639
|
+
"parallel",
|
|
454
640
|
);
|
|
455
|
-
|
|
641
|
+
if (slotMergedUse) {
|
|
642
|
+
runAndValidateUseItems(
|
|
643
|
+
store,
|
|
644
|
+
namespace,
|
|
645
|
+
slotEntry,
|
|
646
|
+
slotMergedUse,
|
|
647
|
+
"parallel",
|
|
648
|
+
"use",
|
|
649
|
+
);
|
|
650
|
+
}
|
|
456
651
|
|
|
457
|
-
|
|
652
|
+
ctx.parent.parallel[slotName] = slotEntry;
|
|
653
|
+
}
|
|
458
654
|
return { name: namespace, type: "parallel" } as ParallelItem;
|
|
459
655
|
};
|
|
460
656
|
|
|
657
|
+
function isSlotDescriptor(
|
|
658
|
+
value: unknown,
|
|
659
|
+
): value is { handler: unknown; use?: () => any[] } {
|
|
660
|
+
return (
|
|
661
|
+
typeof value === "object" &&
|
|
662
|
+
value !== null &&
|
|
663
|
+
!("__brand" in value) &&
|
|
664
|
+
"handler" in value &&
|
|
665
|
+
typeof (value as any).handler !== "undefined"
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function combineExplicitUses(
|
|
670
|
+
sharedUse: (() => any[]) | undefined,
|
|
671
|
+
slotLocalUse: (() => any[]) | undefined,
|
|
672
|
+
): (() => any[]) | undefined {
|
|
673
|
+
if (!sharedUse && !slotLocalUse) return undefined;
|
|
674
|
+
if (!slotLocalUse) return sharedUse;
|
|
675
|
+
if (!sharedUse) return slotLocalUse;
|
|
676
|
+
return () => [...sharedUse(), ...slotLocalUse()];
|
|
677
|
+
}
|
|
678
|
+
|
|
461
679
|
/**
|
|
462
680
|
* Intercept helper - defines an intercepting route for soft navigation
|
|
463
681
|
*/
|
|
@@ -467,9 +685,9 @@ const intercept = (
|
|
|
467
685
|
handler: any,
|
|
468
686
|
use?: () => any[],
|
|
469
687
|
) => {
|
|
470
|
-
const store =
|
|
471
|
-
|
|
472
|
-
|
|
688
|
+
const { store, ctx } = requireDslContext(
|
|
689
|
+
"intercept() must be called inside urls()",
|
|
690
|
+
);
|
|
473
691
|
|
|
474
692
|
if (!ctx.parent || !ctx.parent?.intercept) {
|
|
475
693
|
invariant(false, "No parent entry available for intercept()");
|
|
@@ -502,17 +720,19 @@ const intercept = (
|
|
|
502
720
|
when: [], // Selector conditions for conditional interception
|
|
503
721
|
};
|
|
504
722
|
|
|
505
|
-
//
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
// so that middleware, loader, revalidate attach to the intercept entry
|
|
509
|
-
const originalParent = ctx.parent;
|
|
723
|
+
// Merge handler.use defaults with explicit use
|
|
724
|
+
const handlerUseFn = resolveHandlerUse(handler);
|
|
725
|
+
const mergedUse = mergeHandlerUse(handlerUseFn, use, "intercept");
|
|
510
726
|
|
|
511
|
-
|
|
727
|
+
// Run merged use callback to collect loaders, revalidate, middleware, etc.
|
|
728
|
+
if (mergedUse) {
|
|
729
|
+
// Capture layout() calls into a temporary array
|
|
512
730
|
const capturedLayouts: EntryData[] = [];
|
|
513
731
|
|
|
732
|
+
// Temporary parent so middleware/loader/revalidate/when attach to the
|
|
733
|
+
// intercept entry; the loading get/set accessor mirrors writes onto `entry`.
|
|
514
734
|
const tempParent = {
|
|
515
|
-
...
|
|
735
|
+
...ctx.parent,
|
|
516
736
|
middleware: entry.middleware,
|
|
517
737
|
revalidate: entry.revalidate,
|
|
518
738
|
errorBoundary: entry.errorBoundary,
|
|
@@ -520,7 +740,6 @@ const intercept = (
|
|
|
520
740
|
loader: entry.loader,
|
|
521
741
|
layout: capturedLayouts, // Capture layout() calls
|
|
522
742
|
when: entry.when, // Capture when() conditions
|
|
523
|
-
// Use getter/setter to capture loading on the entry
|
|
524
743
|
get loading() {
|
|
525
744
|
return entry.loading;
|
|
526
745
|
},
|
|
@@ -528,12 +747,10 @@ const intercept = (
|
|
|
528
747
|
entry.loading = value;
|
|
529
748
|
},
|
|
530
749
|
};
|
|
531
|
-
ctx.parent = tempParent as EntryData;
|
|
532
|
-
|
|
533
|
-
const result = use()?.flat(3);
|
|
534
750
|
|
|
535
|
-
|
|
536
|
-
|
|
751
|
+
const result = withParent(ctx, tempParent as EntryData, () =>
|
|
752
|
+
mergedUse()?.flat(3),
|
|
753
|
+
);
|
|
537
754
|
|
|
538
755
|
// Extract layout from captured layouts (use first one if multiple)
|
|
539
756
|
// Layout inside intercept should always be ReactNode or Handler, not Record slots
|
|
@@ -543,10 +760,7 @@ const intercept = (
|
|
|
543
760
|
| Handler<any, any, any>;
|
|
544
761
|
}
|
|
545
762
|
|
|
546
|
-
|
|
547
|
-
Array.isArray(result) && result.every((item) => isValidUseItem(item)),
|
|
548
|
-
`intercept() use() callback must return an array of use items [${namespace}]`,
|
|
549
|
-
);
|
|
763
|
+
validateUseItems(result, namespace, "intercept", "use");
|
|
550
764
|
}
|
|
551
765
|
|
|
552
766
|
ctx.parent.intercept.push(entry);
|
|
@@ -556,10 +770,10 @@ const intercept = (
|
|
|
556
770
|
/**
|
|
557
771
|
* Loader helper - attaches a loader to the current entry
|
|
558
772
|
*/
|
|
559
|
-
const
|
|
560
|
-
const store =
|
|
561
|
-
|
|
562
|
-
|
|
773
|
+
const loader: RouteHelpers<any, any>["loader"] = (loaderDef, use) => {
|
|
774
|
+
const { store, ctx } = requireDslContext(
|
|
775
|
+
"loader() must be called inside urls()",
|
|
776
|
+
);
|
|
563
777
|
|
|
564
778
|
// Attach to last entry in stack
|
|
565
779
|
if (!ctx.parent || !ctx.parent?.loader) {
|
|
@@ -574,25 +788,28 @@ const loaderFn: RouteHelpers<any, any>["loader"] = (loaderDef, use) => {
|
|
|
574
788
|
revalidate: [] as ShouldRevalidateFn<any, any>[],
|
|
575
789
|
};
|
|
576
790
|
|
|
577
|
-
//
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
791
|
+
// Merge handler.use defaults (attached to the loader definition) with explicit use
|
|
792
|
+
const handlerUseFn = resolveHandlerUse(loaderDef);
|
|
793
|
+
const mergedUse = mergeHandlerUse(handlerUseFn, use, "loader");
|
|
794
|
+
|
|
795
|
+
// If any use callback is in effect, run it to collect revalidation rules and cache config
|
|
796
|
+
if (mergedUse) {
|
|
581
797
|
// Create a temporary "parent" with type "loader" so cache() can detect it.
|
|
582
798
|
// Save existing .cache to distinguish inherited config from newly set config.
|
|
583
|
-
const parentCache = (
|
|
799
|
+
const parentCache = (ctx.parent as any).cache;
|
|
584
800
|
const tempParent = {
|
|
585
|
-
...
|
|
801
|
+
...ctx.parent,
|
|
586
802
|
type: "loader",
|
|
587
803
|
revalidate: loaderEntry.revalidate,
|
|
588
804
|
};
|
|
589
|
-
ctx.parent = tempParent as EntryData;
|
|
590
805
|
|
|
591
|
-
const result =
|
|
806
|
+
const result = withParent(ctx, tempParent as EntryData, () =>
|
|
807
|
+
mergedUse()?.flat(3),
|
|
808
|
+
);
|
|
592
809
|
|
|
593
810
|
// Copy cache config only if cache() was called during the use() callback.
|
|
594
|
-
// The spread
|
|
595
|
-
//
|
|
811
|
+
// The spread may carry an inherited .cache from a parent cache() boundary —
|
|
812
|
+
// only copy if it was newly set.
|
|
596
813
|
if (
|
|
597
814
|
(tempParent as any).cache &&
|
|
598
815
|
(tempParent as any).cache !== parentCache
|
|
@@ -600,13 +817,7 @@ const loaderFn: RouteHelpers<any, any>["loader"] = (loaderDef, use) => {
|
|
|
600
817
|
(loaderEntry as any).cache = (tempParent as any).cache;
|
|
601
818
|
}
|
|
602
819
|
|
|
603
|
-
|
|
604
|
-
ctx.parent = originalParent;
|
|
605
|
-
|
|
606
|
-
invariant(
|
|
607
|
-
Array.isArray(result) && result.every((item) => isValidUseItem(item)),
|
|
608
|
-
`loader() use() callback must return an array of use items [${name}]`,
|
|
609
|
-
);
|
|
820
|
+
validateUseItems(result, name, "loader", "use");
|
|
610
821
|
}
|
|
611
822
|
|
|
612
823
|
ctx.parent.loader.push(loaderEntry);
|
|
@@ -617,21 +828,25 @@ const loaderFn: RouteHelpers<any, any>["loader"] = (loaderDef, use) => {
|
|
|
617
828
|
* Loading helper - attaches a loading component to the current entry
|
|
618
829
|
* Loading components are static (no context) and shown during navigation
|
|
619
830
|
*/
|
|
620
|
-
const
|
|
621
|
-
const store =
|
|
622
|
-
|
|
623
|
-
|
|
831
|
+
const loading: RouteHelpers<any, any>["loading"] = (component, options) => {
|
|
832
|
+
const { store, ctx } = requireDslContext(
|
|
833
|
+
"loading() must be called inside urls()",
|
|
834
|
+
);
|
|
624
835
|
|
|
625
836
|
const parent = ctx.parent;
|
|
626
837
|
if (!parent || !("loading" in parent)) {
|
|
627
838
|
invariant(false, "No parent entry available for loading()");
|
|
628
839
|
}
|
|
629
840
|
|
|
841
|
+
// Unwrap function form: loading(() => <Skeleton />) → loading(<Skeleton />)
|
|
842
|
+
const resolved =
|
|
843
|
+
typeof component === "function" ? (component as () => any)() : component;
|
|
844
|
+
|
|
630
845
|
// If ssr: false and we're in SSR, set loading to false
|
|
631
846
|
if (options?.ssr === false && ctx.isSSR) {
|
|
632
847
|
parent.loading = false;
|
|
633
848
|
} else {
|
|
634
|
-
parent.loading =
|
|
849
|
+
parent.loading = resolved;
|
|
635
850
|
}
|
|
636
851
|
|
|
637
852
|
const name = `$${store.getNextIndex("loading")}`;
|
|
@@ -639,10 +854,13 @@ const loadingFn: RouteHelpers<any, any>["loading"] = (component, options) => {
|
|
|
639
854
|
};
|
|
640
855
|
|
|
641
856
|
/**
|
|
642
|
-
* Transition helper -
|
|
643
|
-
*
|
|
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.
|
|
644
862
|
*/
|
|
645
|
-
const
|
|
863
|
+
const transition = (
|
|
646
864
|
configOrChildren?: TransitionConfig | (() => UseItems<AllUseItems>),
|
|
647
865
|
maybeChildren?: () => UseItems<AllUseItems>,
|
|
648
866
|
): TransitionItem => {
|
|
@@ -656,9 +874,9 @@ const transitionFn = (
|
|
|
656
874
|
const children: (() => UseItems<AllUseItems>) | undefined =
|
|
657
875
|
typeof configOrChildren === "function" ? configOrChildren : maybeChildren;
|
|
658
876
|
|
|
659
|
-
const store =
|
|
660
|
-
|
|
661
|
-
|
|
877
|
+
const { store, ctx } = requireDslContext(
|
|
878
|
+
"transition() must be called inside urls()",
|
|
879
|
+
);
|
|
662
880
|
|
|
663
881
|
const name = `$${store.getNextIndex("transition")}`;
|
|
664
882
|
|
|
@@ -675,68 +893,43 @@ const transitionFn = (
|
|
|
675
893
|
// Position 2: wrapper — create a transparent layout with transition config
|
|
676
894
|
const namespace = `${ctx.namespace}.${store.getNextIndex("transition")}`;
|
|
677
895
|
const entry = {
|
|
896
|
+
...emptySegmentBase(),
|
|
678
897
|
id: namespace,
|
|
679
898
|
shortCode: store.getShortCode("layout"),
|
|
680
899
|
type: "layout",
|
|
681
900
|
parent: ctx.parent,
|
|
682
901
|
handler: RootLayout,
|
|
683
|
-
loading: undefined,
|
|
684
902
|
transition: config,
|
|
685
|
-
|
|
686
|
-
revalidate: [],
|
|
687
|
-
errorBoundary: [],
|
|
688
|
-
notFoundBoundary: [],
|
|
689
|
-
layout: [],
|
|
690
|
-
parallel: [],
|
|
691
|
-
intercept: [],
|
|
692
|
-
loader: [],
|
|
693
|
-
} as EntryData;
|
|
694
|
-
|
|
695
|
-
const result = store.run(namespace, entry, children)?.flat(3);
|
|
903
|
+
} satisfies EntryData;
|
|
696
904
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
905
|
+
const result = runAndValidateUseItems(
|
|
906
|
+
store,
|
|
907
|
+
namespace,
|
|
908
|
+
entry,
|
|
909
|
+
children,
|
|
910
|
+
"transition",
|
|
911
|
+
"children",
|
|
700
912
|
);
|
|
701
913
|
|
|
702
|
-
|
|
703
|
-
result &&
|
|
704
|
-
Array.isArray(result) &&
|
|
705
|
-
result.some((item) => hasRoutesInItem(item));
|
|
706
|
-
|
|
707
|
-
if (!hasRoutes) {
|
|
708
|
-
const parent = ctx.parent;
|
|
709
|
-
if (parent && "layout" in parent) {
|
|
710
|
-
entry.parent = null;
|
|
711
|
-
parent.layout.push(entry);
|
|
712
|
-
}
|
|
713
|
-
}
|
|
914
|
+
if (isOrphan(result)) attachOrphanSibling(ctx.parent, entry);
|
|
714
915
|
|
|
715
916
|
return { name: namespace, type: "transition" } as TransitionItem;
|
|
716
917
|
};
|
|
717
918
|
|
|
718
|
-
const
|
|
719
|
-
const store =
|
|
720
|
-
|
|
721
|
-
|
|
919
|
+
const route: RouteHelpers<any, any>["route"] = (name, handler, use) => {
|
|
920
|
+
const { store, ctx } = requireDslContext(
|
|
921
|
+
"route() must be called inside urls()",
|
|
922
|
+
);
|
|
722
923
|
|
|
723
924
|
const namespace = `${ctx.namespace}.${store.getNextIndex("route")}.${name}`;
|
|
724
925
|
|
|
725
926
|
const entry = {
|
|
927
|
+
...emptySegmentBase(),
|
|
726
928
|
id: namespace,
|
|
727
929
|
shortCode: store.getShortCode("route"),
|
|
728
930
|
type: "route",
|
|
729
931
|
parent: ctx.parent,
|
|
730
|
-
handler,
|
|
731
|
-
loading: undefined, // Allow loading() to attach loading state
|
|
732
|
-
middleware: [],
|
|
733
|
-
revalidate: [],
|
|
734
|
-
errorBoundary: [],
|
|
735
|
-
notFoundBoundary: [],
|
|
736
|
-
layout: [],
|
|
737
|
-
parallel: [],
|
|
738
|
-
intercept: [],
|
|
739
|
-
loader: [],
|
|
932
|
+
handler: handler as unknown as Handler<any, any, any>,
|
|
740
933
|
} satisfies EntryData;
|
|
741
934
|
|
|
742
935
|
/* We will throw if user is registring same route name twice */
|
|
@@ -746,12 +939,18 @@ const routeFn: RouteHelpers<any, any>["route"] = (name, handler, use) => {
|
|
|
746
939
|
);
|
|
747
940
|
/* Register route entry */
|
|
748
941
|
ctx.manifest.set(name, entry);
|
|
942
|
+
/* Merge handler.use defaults with explicit use */
|
|
943
|
+
const handlerUseFn = resolveHandlerUse(handler);
|
|
944
|
+
const mergedUse = mergeHandlerUse(handlerUseFn, use, "route");
|
|
749
945
|
/* Run use and attach handlers */
|
|
750
|
-
if (
|
|
751
|
-
const result =
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
946
|
+
if (mergedUse) {
|
|
947
|
+
const result = runAndValidateUseItems(
|
|
948
|
+
store,
|
|
949
|
+
namespace,
|
|
950
|
+
entry,
|
|
951
|
+
mergedUse,
|
|
952
|
+
"route",
|
|
953
|
+
"use",
|
|
755
954
|
);
|
|
756
955
|
return { name: namespace, type: "route", uses: result } as RouteItem;
|
|
757
956
|
}
|
|
@@ -761,9 +960,9 @@ const routeFn: RouteHelpers<any, any>["route"] = (name, handler, use) => {
|
|
|
761
960
|
};
|
|
762
961
|
|
|
763
962
|
const layout: RouteHelpers<any, any>["layout"] = (handler, use) => {
|
|
764
|
-
const store =
|
|
765
|
-
|
|
766
|
-
|
|
963
|
+
const { store, ctx } = requireDslContext(
|
|
964
|
+
"layout() must be called inside urls()",
|
|
965
|
+
);
|
|
767
966
|
|
|
768
967
|
invariant(
|
|
769
968
|
!ctx.parent || ctx.parent.type !== "parallel",
|
|
@@ -781,20 +980,12 @@ const layout: RouteHelpers<any, any>["layout"] = (handler, use) => {
|
|
|
781
980
|
|
|
782
981
|
const urlPrefix = getUrlPrefix();
|
|
783
982
|
const entry = {
|
|
983
|
+
...emptySegmentBase(),
|
|
784
984
|
id: namespace,
|
|
785
985
|
shortCode,
|
|
786
986
|
type: "layout",
|
|
787
987
|
parent: ctx.parent,
|
|
788
988
|
handler: unwrappedHandler,
|
|
789
|
-
loading: undefined, // Allow loading() to attach loading state
|
|
790
|
-
middleware: [],
|
|
791
|
-
revalidate: [],
|
|
792
|
-
errorBoundary: [],
|
|
793
|
-
notFoundBoundary: [],
|
|
794
|
-
parallel: [],
|
|
795
|
-
intercept: [],
|
|
796
|
-
layout: [],
|
|
797
|
-
loader: [],
|
|
798
989
|
...(urlPrefix ? { mountPath: urlPrefix } : {}),
|
|
799
990
|
...(isStatic
|
|
800
991
|
? {
|
|
@@ -809,14 +1000,20 @@ const layout: RouteHelpers<any, any>["layout"] = (handler, use) => {
|
|
|
809
1000
|
(handler as any).$$routePrefix = ctx.namePrefix;
|
|
810
1001
|
}
|
|
811
1002
|
|
|
812
|
-
//
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
result = store.run(namespace, entry, use)?.flat(3);
|
|
1003
|
+
// Merge handler.use defaults with explicit use
|
|
1004
|
+
const handlerUseFn = resolveHandlerUse(handler);
|
|
1005
|
+
const mergedUse = mergeHandlerUse(handlerUseFn, use, "layout");
|
|
816
1006
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
1007
|
+
// Run merged use callback if present
|
|
1008
|
+
let result: AllUseItems[] | undefined;
|
|
1009
|
+
if (mergedUse) {
|
|
1010
|
+
result = runAndValidateUseItems(
|
|
1011
|
+
store,
|
|
1012
|
+
namespace,
|
|
1013
|
+
entry,
|
|
1014
|
+
mergedUse,
|
|
1015
|
+
"layout",
|
|
1016
|
+
"use",
|
|
820
1017
|
);
|
|
821
1018
|
}
|
|
822
1019
|
|
|
@@ -858,9 +1055,7 @@ const layout: RouteHelpers<any, any>["layout"] = (handler, use) => {
|
|
|
858
1055
|
`Orphan layouts can only be defined inside route or layout > check [${namespace}]`,
|
|
859
1056
|
);
|
|
860
1057
|
|
|
861
|
-
|
|
862
|
-
entry.parent = null;
|
|
863
|
-
parent.layout.push(entry);
|
|
1058
|
+
attachOrphanSibling(parent, entry);
|
|
864
1059
|
}
|
|
865
1060
|
}
|
|
866
1061
|
|
|
@@ -873,33 +1068,15 @@ const layout: RouteHelpers<any, any>["layout"] = (handler, use) => {
|
|
|
873
1068
|
} as LayoutItem;
|
|
874
1069
|
};
|
|
875
1070
|
|
|
876
|
-
const isValidUseItem = (item: any): item is AllUseItems | undefined | null =>
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
(item
|
|
881
|
-
typeof item === "object" &&
|
|
882
|
-
"type" in item &&
|
|
883
|
-
[
|
|
884
|
-
"layout",
|
|
885
|
-
"route",
|
|
886
|
-
"middleware",
|
|
887
|
-
"revalidate",
|
|
888
|
-
"parallel",
|
|
889
|
-
"intercept",
|
|
890
|
-
"loader",
|
|
891
|
-
"loading",
|
|
892
|
-
"errorBoundary",
|
|
893
|
-
"notFoundBoundary",
|
|
894
|
-
"when",
|
|
895
|
-
"cache",
|
|
896
|
-
"transition",
|
|
897
|
-
"include", // For urls() include() helper
|
|
898
|
-
].includes(item.type))
|
|
899
|
-
);
|
|
900
|
-
};
|
|
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));
|
|
901
1076
|
|
|
902
|
-
//
|
|
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.
|
|
903
1080
|
export {
|
|
904
1081
|
layout,
|
|
905
1082
|
cache,
|
|
@@ -910,25 +1087,11 @@ export {
|
|
|
910
1087
|
when,
|
|
911
1088
|
errorBoundary,
|
|
912
1089
|
notFoundBoundary,
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
const isOrphanLayout = (item: AllUseItems): boolean => {
|
|
919
|
-
return (
|
|
920
|
-
item.type === "layout" &&
|
|
921
|
-
!item.uses?.some((child) => hasRoutesInItem(child))
|
|
922
|
-
);
|
|
923
|
-
};
|
|
924
|
-
|
|
925
|
-
// Internal exports used by helper-factories.ts
|
|
926
|
-
export {
|
|
927
|
-
routeFn,
|
|
928
|
-
loaderFn,
|
|
929
|
-
loadingFn,
|
|
930
|
-
transitionFn,
|
|
931
|
-
hasRoutesInItem,
|
|
1090
|
+
route,
|
|
1091
|
+
loader,
|
|
1092
|
+
loading,
|
|
1093
|
+
transition,
|
|
932
1094
|
isValidUseItem,
|
|
933
|
-
|
|
1095
|
+
emptySegmentBase,
|
|
1096
|
+
runAndValidateUseItems,
|
|
934
1097
|
};
|