@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
package/src/client.rsc.tsx
CHANGED
|
@@ -14,60 +14,43 @@
|
|
|
14
14
|
export {
|
|
15
15
|
Outlet,
|
|
16
16
|
ParallelOutlet,
|
|
17
|
-
OutletProvider,
|
|
18
17
|
useOutlet,
|
|
19
18
|
useLoader,
|
|
20
19
|
ErrorBoundary,
|
|
21
20
|
type ErrorBoundaryProps,
|
|
22
21
|
} from "./client.js";
|
|
23
22
|
|
|
24
|
-
// Re-export the server's createLoader for RSC context
|
|
25
|
-
// This version includes the actual loader function
|
|
26
23
|
export { createLoader } from "./route-definition.js";
|
|
27
24
|
|
|
28
|
-
// Re-export Link component (can be used in server components)
|
|
29
25
|
export {
|
|
30
26
|
Link,
|
|
31
27
|
type LinkProps,
|
|
32
28
|
type PrefetchStrategy,
|
|
33
29
|
} from "./browser/react/Link.js";
|
|
34
30
|
|
|
35
|
-
// Re-export ScrollRestoration (can be used in server components)
|
|
36
31
|
export {
|
|
37
32
|
ScrollRestoration,
|
|
38
33
|
type ScrollRestorationProps,
|
|
39
34
|
} from "./browser/react/ScrollRestoration.js";
|
|
40
35
|
|
|
41
|
-
// Re-export NavigationProvider (needed for setup)
|
|
42
36
|
export {
|
|
43
37
|
NavigationProvider,
|
|
44
38
|
type NavigationProviderProps,
|
|
45
39
|
} from "./browser/react/NavigationProvider.js";
|
|
46
40
|
|
|
47
|
-
// Re-export href function (can be used in server components)
|
|
48
41
|
export { href } from "./href-client.js";
|
|
49
42
|
|
|
50
|
-
// Mount context re-exports (useMount is client-only, but MountContext can be referenced)
|
|
51
43
|
export { MountContext } from "./browser/react/mount-context.js";
|
|
52
44
|
|
|
53
|
-
//
|
|
54
|
-
// because they use client-side state and should only be used in client components
|
|
45
|
+
// useNavigation and useAction are NOT re-exported here because they use client-side state
|
|
55
46
|
|
|
56
|
-
// Handle API - for accumulating data across route segments
|
|
57
|
-
// Works in both RSC and client contexts
|
|
58
47
|
export { createHandle, isHandle, type Handle } from "./handle.js";
|
|
59
48
|
|
|
60
|
-
// Built-in handles
|
|
61
|
-
// Meta handle works in RSC context
|
|
62
49
|
export { Meta } from "./handles/meta.js";
|
|
63
|
-
// MetaTags is a "use client" component that can be imported from RSC
|
|
64
50
|
export { MetaTags } from "./handles/MetaTags.js";
|
|
65
51
|
export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
|
|
66
|
-
// Breadcrumbs handle works in RSC context
|
|
67
52
|
export { Breadcrumbs, type BreadcrumbItem } from "./handles/breadcrumbs.js";
|
|
68
53
|
|
|
69
|
-
// Location state - createLocationState works in RSC (just creates definition)
|
|
70
|
-
// useLocationState is NOT exported here as it uses client hooks
|
|
71
54
|
export {
|
|
72
55
|
createLocationState,
|
|
73
56
|
type LocationStateDefinition,
|
|
@@ -75,11 +58,13 @@ export {
|
|
|
75
58
|
type LocationStateOptions,
|
|
76
59
|
} from "./browser/react/location-state-shared.js";
|
|
77
60
|
|
|
78
|
-
// Re-export useHref - it's a "use client" hook
|
|
79
61
|
export { useHref } from "./browser/react/use-href.js";
|
|
80
62
|
|
|
81
|
-
|
|
63
|
+
export { useReverse } from "./browser/react/use-reverse.js";
|
|
64
|
+
|
|
82
65
|
export { useHandle } from "./browser/react/use-handle.js";
|
|
66
|
+
// Type a deferred-aware consumer narrows: an accumulated entry may be a Promise
|
|
67
|
+
// (a `ctx.use(Handle).defer()` slot) until it resolves.
|
|
68
|
+
export type { DeferredHandleEntry } from "./defer.js";
|
|
83
69
|
|
|
84
|
-
// Re-export useLocationState - it's a "use client" hook
|
|
85
70
|
export { useLocationState } from "./browser/react/location-state.js";
|
package/src/client.tsx
CHANGED
|
@@ -111,6 +111,11 @@ function useSlotSegment(
|
|
|
111
111
|
* the parallel segment with that slot name instead of the default content.
|
|
112
112
|
* This is used for parallel routes and intercepting routes.
|
|
113
113
|
*
|
|
114
|
+
* For a named slot, `<Outlet name="@x" />` is equivalent to
|
|
115
|
+
* `<ParallelOutlet name="@x" />` — both run the same resolution + wrapping
|
|
116
|
+
* pipeline. Convention: use bare `<Outlet />` for default content and
|
|
117
|
+
* `<ParallelOutlet name="@x" />` for named slots.
|
|
118
|
+
*
|
|
114
119
|
* @param name - Optional slot name for parallel/intercept content (must start with @)
|
|
115
120
|
*
|
|
116
121
|
* @example
|
|
@@ -163,6 +168,9 @@ export function Outlet({ name }: { name?: `@${string}` } = {}): ReactNode {
|
|
|
163
168
|
* is wrapped in Suspense with the loading component as fallback.
|
|
164
169
|
* This enables streaming and navigation loading states for parallels.
|
|
165
170
|
*
|
|
171
|
+
* Equivalent to `<Outlet name="@x" />` for a named slot; ParallelOutlet
|
|
172
|
+
* requires `name` and is named-slot-only, which reads clearer at the call site.
|
|
173
|
+
*
|
|
166
174
|
* @param name - The slot name (must start with @, e.g., "@modal", "@sidebar")
|
|
167
175
|
*
|
|
168
176
|
* @example
|
|
@@ -186,10 +194,9 @@ export function ParallelOutlet({ name }: { name: `@${string}` }): ReactNode {
|
|
|
186
194
|
}
|
|
187
195
|
|
|
188
196
|
// OutletProvider is defined in outlet-provider.tsx to break a circular
|
|
189
|
-
// dependency between client.tsx and route-content-wrapper.tsx.
|
|
190
|
-
//
|
|
191
|
-
// and
|
|
192
|
-
export { OutletProvider };
|
|
197
|
+
// dependency between client.tsx and route-content-wrapper.tsx. It is imported
|
|
198
|
+
// at the top of this file for local use in Outlet/ParallelOutlet only; it is an
|
|
199
|
+
// internal component and is intentionally not part of the public ./client API.
|
|
193
200
|
|
|
194
201
|
/**
|
|
195
202
|
* Hook to access outlet content programmatically
|
|
@@ -210,10 +217,10 @@ export function useOutlet(): ReactNode {
|
|
|
210
217
|
return context?.content ?? null;
|
|
211
218
|
}
|
|
212
219
|
|
|
213
|
-
// Loader hooks - re-exported from dedicated file
|
|
214
220
|
export {
|
|
215
221
|
useLoader,
|
|
216
222
|
useFetchLoader,
|
|
223
|
+
useRefreshLoaders,
|
|
217
224
|
type LoadFunction,
|
|
218
225
|
type UseLoaderResult,
|
|
219
226
|
type UseFetchLoaderResult,
|
|
@@ -328,12 +335,6 @@ export class ErrorBoundary extends Component<
|
|
|
328
335
|
}
|
|
329
336
|
}
|
|
330
337
|
|
|
331
|
-
// ============================================================================
|
|
332
|
-
// Re-exports from browser/react for convenience
|
|
333
|
-
// These are the most commonly used client-side navigation utilities
|
|
334
|
-
// ============================================================================
|
|
335
|
-
|
|
336
|
-
// Navigation hooks
|
|
337
338
|
export { useNavigation } from "./browser/react/use-navigation.js";
|
|
338
339
|
export { useRouter } from "./browser/react/use-router.js";
|
|
339
340
|
export { usePathname } from "./browser/react/use-pathname.js";
|
|
@@ -343,64 +344,55 @@ export type {
|
|
|
343
344
|
RouterInstance,
|
|
344
345
|
RouterNavigateOptions,
|
|
345
346
|
ReadonlyURLSearchParams,
|
|
347
|
+
ActionState,
|
|
348
|
+
ActionLifecycleState,
|
|
346
349
|
} from "./browser/types.js";
|
|
347
350
|
|
|
348
|
-
// Action state tracking hook
|
|
349
351
|
export {
|
|
350
352
|
useAction,
|
|
351
353
|
type ServerActionFunction,
|
|
352
354
|
} from "./browser/react/use-action.js";
|
|
353
355
|
|
|
354
|
-
// Segments state hook
|
|
355
356
|
export {
|
|
356
357
|
useSegments,
|
|
357
358
|
type SegmentsState,
|
|
358
359
|
} from "./browser/react/use-segments.js";
|
|
359
360
|
|
|
360
|
-
// Client cache controls hook
|
|
361
|
-
export {
|
|
362
|
-
useClientCache,
|
|
363
|
-
type ClientCacheControls,
|
|
364
|
-
} from "./browser/react/use-client-cache.js";
|
|
365
|
-
|
|
366
|
-
// Provider
|
|
367
361
|
export {
|
|
368
362
|
NavigationProvider,
|
|
369
363
|
type NavigationProviderProps,
|
|
370
364
|
} from "./browser/react/NavigationProvider.js";
|
|
371
365
|
|
|
372
|
-
// Link component
|
|
373
366
|
export {
|
|
374
367
|
Link,
|
|
375
368
|
type LinkProps,
|
|
376
369
|
type PrefetchStrategy,
|
|
377
370
|
type StateOrGetter,
|
|
371
|
+
type LinkState,
|
|
378
372
|
} from "./browser/react/Link.js";
|
|
379
373
|
|
|
380
|
-
// Link status hook
|
|
381
374
|
export {
|
|
382
375
|
useLinkStatus,
|
|
383
376
|
type LinkStatus,
|
|
384
377
|
} from "./browser/react/use-link-status.js";
|
|
385
378
|
|
|
386
|
-
// Scroll restoration
|
|
387
379
|
export {
|
|
388
380
|
ScrollRestoration,
|
|
389
381
|
useScrollRestoration,
|
|
390
382
|
type ScrollRestorationProps,
|
|
391
383
|
} from "./browser/react/ScrollRestoration.js";
|
|
392
384
|
|
|
393
|
-
// Handle data hook (client-side only — createHandle/isHandle are server APIs from the root export)
|
|
394
385
|
export { type Handle } from "./handle.js";
|
|
395
386
|
export { useHandle } from "./browser/react/use-handle.js";
|
|
387
|
+
// Type a deferred-aware consumer narrows: an accumulated entry may be a Promise
|
|
388
|
+
// (a `ctx.use(Handle).defer()` slot) until it resolves.
|
|
389
|
+
export type { DeferredHandleEntry } from "./defer.js";
|
|
396
390
|
|
|
397
|
-
// Built-in handles
|
|
398
391
|
export { Meta } from "./handles/meta.js";
|
|
399
392
|
export { MetaTags } from "./handles/MetaTags.js";
|
|
400
393
|
export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
|
|
401
394
|
export { Breadcrumbs, type BreadcrumbItem } from "./handles/breadcrumbs.js";
|
|
402
395
|
|
|
403
|
-
// Location state - type-safe navigation state
|
|
404
396
|
export {
|
|
405
397
|
createLocationState,
|
|
406
398
|
useLocationState,
|
|
@@ -409,47 +401,19 @@ export {
|
|
|
409
401
|
type LocationStateOptions,
|
|
410
402
|
} from "./browser/react/location-state.js";
|
|
411
403
|
|
|
412
|
-
//
|
|
413
|
-
export {
|
|
414
|
-
href,
|
|
415
|
-
type ValidPaths,
|
|
416
|
-
type PatternToPath,
|
|
417
|
-
type PathResponse,
|
|
418
|
-
} from "./href-client.js";
|
|
404
|
+
// Ambient Rango.Path / Rango.PathResponse types (declared in href-client.ts)
|
|
405
|
+
export { href, type PatternToPath } from "./href-client.js";
|
|
419
406
|
|
|
420
|
-
//
|
|
421
|
-
export type {
|
|
407
|
+
// RFC 9457 error type for JSON response routes
|
|
408
|
+
export type { ProblemDetails } from "./urls.js";
|
|
422
409
|
|
|
423
|
-
/**
|
|
424
|
-
* Type guard for checking if a response envelope contains an error.
|
|
425
|
-
*
|
|
426
|
-
* @example
|
|
427
|
-
* ```typescript
|
|
428
|
-
* const result: ResponseEnvelope<Product> = await fetch(url).then(r => r.json());
|
|
429
|
-
* if (isResponseError(result)) {
|
|
430
|
-
* console.log(result.error.message, result.error.code);
|
|
431
|
-
* return;
|
|
432
|
-
* }
|
|
433
|
-
* result.data // fully typed as Product
|
|
434
|
-
* ```
|
|
435
|
-
*/
|
|
436
|
-
export function isResponseError<T>(
|
|
437
|
-
result: import("./urls.js").ResponseEnvelope<T>,
|
|
438
|
-
): result is import("./urls.js").ResponseEnvelope<T> & {
|
|
439
|
-
error: import("./urls.js").ResponseError;
|
|
440
|
-
} {
|
|
441
|
-
return result.error !== undefined;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// Mount context for include() scoped components
|
|
445
410
|
export { useMount } from "./browser/react/use-mount.js";
|
|
446
411
|
export { MountContext } from "./browser/react/mount-context.js";
|
|
447
412
|
|
|
448
|
-
// Mount-aware href hook - auto-prefixes paths with include() mount
|
|
449
413
|
export { useHref } from "./browser/react/use-href.js";
|
|
450
414
|
|
|
451
|
-
|
|
452
|
-
|
|
415
|
+
export { useReverse } from "./browser/react/use-reverse.js";
|
|
416
|
+
|
|
417
|
+
export type { ScopedReverseFunction, LocalReverseFunction } from "./reverse.js";
|
|
453
418
|
|
|
454
|
-
// Loader definition type - for typing loader props in client components
|
|
455
419
|
export type { LoaderDefinition } from "./types.js";
|
package/src/component-utils.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { ComponentType } from "react";
|
|
8
|
+
import { isUnderTestRunner } from "./runtime-env.js";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Symbol used by React to mark client component references.
|
|
@@ -48,11 +49,21 @@ export function isClientComponent(
|
|
|
48
49
|
*
|
|
49
50
|
* @param component - The component to check
|
|
50
51
|
* @param name - Name to use in error message (e.g., "document")
|
|
52
|
+
* @param opts.allowServerInTest - When true AND running under a test runner
|
|
53
|
+
* (`isUnderTestRunner()`), relax ONLY the "use client" requirement: a server
|
|
54
|
+
* component is accepted. The plugin's "use client" transform does not run in a
|
|
55
|
+
* bare unit test, so a real exported `document` (almost every app sets one) has
|
|
56
|
+
* no client marker and would otherwise throw at `createRouter`, blocking
|
|
57
|
+
* `dispatch`/`assertGeneratedRoutesMatch` against the real router. The document
|
|
58
|
+
* reference is irrelevant to those (no Flight render). The "not a JSX element"
|
|
59
|
+
* guard still fires, and a real dev/build still throws (mirrors the runtime
|
|
60
|
+
* fallback-id gating in handle.ts/loader.ts).
|
|
51
61
|
* @throws Error if the component is not a client component
|
|
52
62
|
*/
|
|
53
63
|
export function assertClientComponent(
|
|
54
64
|
component: ComponentType<unknown> | unknown,
|
|
55
65
|
name: string,
|
|
66
|
+
opts?: { allowServerInTest?: boolean },
|
|
56
67
|
): asserts component is ComponentType<unknown> {
|
|
57
68
|
if (typeof component !== "function") {
|
|
58
69
|
throw new Error(
|
|
@@ -62,6 +73,14 @@ export function assertClientComponent(
|
|
|
62
73
|
);
|
|
63
74
|
}
|
|
64
75
|
|
|
76
|
+
// Under a test runner the "use client" transform did not run, so a real
|
|
77
|
+
// server-rendered `document` has no client marker; accept it (the reference is
|
|
78
|
+
// never serialized in dispatch/route-map checks). Outside a test runner this
|
|
79
|
+
// still throws — the build-time safety net is preserved.
|
|
80
|
+
if (opts?.allowServerInTest && isUnderTestRunner()) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
65
84
|
if (!isClientComponent(component)) {
|
|
66
85
|
throw new Error(
|
|
67
86
|
`${name} must be a client component with "use client" directive at the top of the file. ` +
|
package/src/context-var.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* interface PaginationData { current: number; total: number }
|
|
13
13
|
* export const Pagination = createVar<PaginationData>();
|
|
14
14
|
*
|
|
15
|
-
* // Non-cacheable var — throws
|
|
15
|
+
* // Non-cacheable var — ctx.get(User) throws inside a cache() boundary
|
|
16
16
|
* export const User = createVar<UserData>({ cache: false });
|
|
17
17
|
*
|
|
18
18
|
* // handler
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
export interface ContextVar<T> {
|
|
27
27
|
readonly __brand: "context-var";
|
|
28
28
|
readonly key: symbol;
|
|
29
|
-
/** When false,
|
|
29
|
+
/** When false, ctx.get(var) throws inside a cache() boundary. */
|
|
30
30
|
readonly cache: boolean;
|
|
31
31
|
/** Phantom field to carry the type parameter. Never set at runtime. */
|
|
32
32
|
readonly __type?: T;
|
|
@@ -35,9 +35,9 @@ export interface ContextVar<T> {
|
|
|
35
35
|
export interface ContextVarOptions {
|
|
36
36
|
/**
|
|
37
37
|
* When false, marks this variable as non-cacheable.
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
38
|
+
* Reading this var with ctx.get() inside a cache() boundary throws. Use for
|
|
39
|
+
* inherently request-specific data (user sessions, auth tokens, etc.) that
|
|
40
|
+
* must never be baked into cached segments.
|
|
41
41
|
*
|
|
42
42
|
* @default true
|
|
43
43
|
*/
|
|
@@ -70,6 +70,18 @@ export function isContextVar(value: unknown): value is ContextVar<unknown> {
|
|
|
70
70
|
);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Does a variables object hold any entries? Counts both string keys and the
|
|
75
|
+
* symbol-keyed entries (context vars are stored under symbols), so an object
|
|
76
|
+
* carrying only symbol-keyed vars is still reported as non-empty.
|
|
77
|
+
*/
|
|
78
|
+
export function hasContextVars(variables: object): boolean {
|
|
79
|
+
return (
|
|
80
|
+
Object.keys(variables).length > 0 ||
|
|
81
|
+
Object.getOwnPropertySymbols(variables).length > 0
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
73
85
|
/**
|
|
74
86
|
* Symbol used as a Set stored on the variables object to track
|
|
75
87
|
* which keys hold non-cacheable values (from write-level { cache: false }).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { isLoaderDataResult } from "./types.js";
|
|
3
|
+
|
|
4
|
+
// Shared by segment-system (server) and LoaderResolver (client) so the
|
|
5
|
+
// legacy/ok/error-fallback/throw decode of resolved loader values lives once.
|
|
6
|
+
// Last failing loader wins errorFallback; an error without a fallback throws.
|
|
7
|
+
export function decodeLoaderResults(
|
|
8
|
+
resolvedData: any[],
|
|
9
|
+
loaderIds: string[],
|
|
10
|
+
): { loaderData: Record<string, any>; errorFallback: ReactNode } {
|
|
11
|
+
const loaderData: Record<string, any> = {};
|
|
12
|
+
let errorFallback: ReactNode = null;
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < loaderIds.length; i++) {
|
|
15
|
+
const id = loaderIds[i];
|
|
16
|
+
const result = resolvedData[i];
|
|
17
|
+
|
|
18
|
+
if (!isLoaderDataResult(result)) {
|
|
19
|
+
loaderData[id] = result;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (result.ok) {
|
|
24
|
+
loaderData[id] = result.data;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (result.fallback) {
|
|
29
|
+
errorFallback = result.fallback;
|
|
30
|
+
} else {
|
|
31
|
+
throw new Error(result.error.message);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { loaderData, errorFallback };
|
|
36
|
+
}
|
package/src/defer.ts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deferred handle values — "decide synchronously, resolve late".
|
|
3
|
+
*
|
|
4
|
+
* A handle is pushed from code that holds `ctx` (a route/layout handler), so the
|
|
5
|
+
* decision to push lands before the handles stream seals. But the value often
|
|
6
|
+
* isn't known there — it may come from a deep async component far from the
|
|
7
|
+
* handler. `ctx.use(Handle).defer()` reserves the handle's slot now (synchronous,
|
|
8
|
+
* so ordering and the pre-seal timing hold) and returns a resolver with the SAME
|
|
9
|
+
* signature as the push: you call it later, anywhere in the render, with the same
|
|
10
|
+
* value you would have passed to the push.
|
|
11
|
+
*
|
|
12
|
+
* const breadcrumb = ctx.use(Breadcrumbs); // (item) => void & .defer()
|
|
13
|
+
* const resolve = breadcrumb.defer({ timeoutMs: 5000, else: null });
|
|
14
|
+
* // deep async component, far from ctx:
|
|
15
|
+
* resolve({ label, href, content }); // identical call, just deferred
|
|
16
|
+
*
|
|
17
|
+
* Under the hood the reserved slot is a Promise the renderer `use()`s; RSC Flight
|
|
18
|
+
* streams it as a late row, so a deferred-aware consumer reading the handle
|
|
19
|
+
* (`useHandle`) sees that entry as a `Promise` until it resolves (see
|
|
20
|
+
* {@link DeferredHandleEntry}). The hazard that guards against bugs: a deferred
|
|
21
|
+
* slot whose resolver is never called would keep the Flight stream — and the HTTP
|
|
22
|
+
* response — open forever. So a deferred auto-resolves to `else` after `timeoutMs`
|
|
23
|
+
* (default {@link DEFAULT_DEFER_TIMEOUT_MS}) if the resolver is never called,
|
|
24
|
+
* degrading gracefully (and warning in dev) instead of hanging the request.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** Default auto-resolve window. Long enough for genuine deep async work, short
|
|
28
|
+
* enough that a forgotten resolve does not hang the response indefinitely. */
|
|
29
|
+
export const DEFAULT_DEFER_TIMEOUT_MS = 10_000;
|
|
30
|
+
|
|
31
|
+
/** Options for `ctx.use(Handle).defer()`. */
|
|
32
|
+
export interface DeferOptions<TData> {
|
|
33
|
+
/**
|
|
34
|
+
* Auto-resolve to `else` after this many ms if the resolver is never called,
|
|
35
|
+
* so a forgotten resolve cannot hold the Flight stream — and thus the HTTP
|
|
36
|
+
* response — open. Defaults to {@link DEFAULT_DEFER_TIMEOUT_MS}. `0` or
|
|
37
|
+
* `Infinity` disable the timeout intentionally (not recommended on a request
|
|
38
|
+
* path). Any other non-finite or negative value is treated as a mistake and
|
|
39
|
+
* falls back to the default rather than silently disabling the safety net.
|
|
40
|
+
* Named `timeoutMs` to match the router's `*Ms` duration convention.
|
|
41
|
+
*/
|
|
42
|
+
timeoutMs?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Value the slot resolves to if the timeout fires before the resolver is
|
|
45
|
+
* called. Defaults to `undefined` (the deferred item is skipped/empty). For
|
|
46
|
+
* renderable handle content, `null` is the usual graceful fallback, so the
|
|
47
|
+
* type admits `null` even when `TData` does not.
|
|
48
|
+
*/
|
|
49
|
+
else?: TData | null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The call signature shared by a handle push and the resolver returned by
|
|
54
|
+
* `.defer()`: a concrete value, a `Promise` of the value (Flight streams it as a
|
|
55
|
+
* late row), or a thunk returning a `Promise` (called immediately).
|
|
56
|
+
*/
|
|
57
|
+
export type HandlePushFn<TData> = (
|
|
58
|
+
data: TData | Promise<TData> | (() => Promise<TData>),
|
|
59
|
+
) => void;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The push function returned by `ctx.use(Handle)`. Call it to push a value now,
|
|
63
|
+
* or call `.defer()` to reserve the slot now and resolve the value later (e.g.
|
|
64
|
+
* from a deep async component) with a timeout safety net.
|
|
65
|
+
*/
|
|
66
|
+
export type HandlePush<TData> = HandlePushFn<TData> & {
|
|
67
|
+
/**
|
|
68
|
+
* Reserve this handle's slot synchronously and return a resolver that is
|
|
69
|
+
* push-equal: it takes the same argument shapes as the push (value, Promise, or
|
|
70
|
+
* thunk) and behaves identically. Two things the resolver adds over a direct
|
|
71
|
+
* push: a timeout (if the resolver is never called, the slot auto-resolves to
|
|
72
|
+
* `options.else` after `options.timeoutMs`; calling the resolver cancels it),
|
|
73
|
+
* and — on the action/revalidation path only — a thunk it runs does NOT
|
|
74
|
+
* re-enter the deadlock-guard push-callback scope a direct push thunk gets,
|
|
75
|
+
* because a deferred resolver fires after the handler phase has closed.
|
|
76
|
+
*
|
|
77
|
+
* The reserved slot appears in the accumulated handle data as a pending
|
|
78
|
+
* `Promise` until it resolves (see {@link DeferredHandleEntry}); a
|
|
79
|
+
* deferred-aware consumer narrows thenable entries (`use()`/`await` + null
|
|
80
|
+
* check) before dereferencing.
|
|
81
|
+
*/
|
|
82
|
+
defer(options?: DeferOptions<TData>): HandlePushFn<TData>;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* A handle entry a deferred-aware consumer may read from `useHandle`: either a
|
|
87
|
+
* resolved value, or a pending `Promise` that resolves to the value, to `else`,
|
|
88
|
+
* or (when no `else` was given) `undefined` on timeout. Reading code should treat
|
|
89
|
+
* thenable entries as such and narrow before dereferencing.
|
|
90
|
+
*/
|
|
91
|
+
export type DeferredHandleEntry<TData> =
|
|
92
|
+
| TData
|
|
93
|
+
| Promise<TData | null | undefined>;
|
|
94
|
+
|
|
95
|
+
// Internal: a timeout-bounded { promise, resolve }. Not part of the public API
|
|
96
|
+
// (the public surface is `ctx.use(Handle).defer()`); exported for `withDefer`
|
|
97
|
+
// and unit tests only. Resolves to `T`, the `else` fallback, or `undefined`.
|
|
98
|
+
export function createDeferred<T>(options?: {
|
|
99
|
+
timeoutMs?: number;
|
|
100
|
+
fallback?: T | null;
|
|
101
|
+
}): {
|
|
102
|
+
promise: Promise<T | null | undefined>;
|
|
103
|
+
resolve: (value: T | null | undefined) => void;
|
|
104
|
+
} {
|
|
105
|
+
let resolveInner!: (value: T | null | undefined) => void;
|
|
106
|
+
let settled = false;
|
|
107
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
108
|
+
|
|
109
|
+
const promise = new Promise<T | null | undefined>((resolve) => {
|
|
110
|
+
resolveInner = resolve;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const finish = (value: T | null | undefined): void => {
|
|
114
|
+
if (settled) return;
|
|
115
|
+
settled = true;
|
|
116
|
+
if (timer !== undefined) {
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
timer = undefined;
|
|
119
|
+
}
|
|
120
|
+
resolveInner(value);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// 0 and Infinity are documented intentional disables. Any other non-finite or
|
|
124
|
+
// negative value (NaN, -1, a bad parsed config/env) is a mistake — fall back to
|
|
125
|
+
// the default rather than SILENTLY disabling the safety net, which would let a
|
|
126
|
+
// forgotten resolve hang the Flight stream and the response forever.
|
|
127
|
+
const requested = options?.timeoutMs ?? DEFAULT_DEFER_TIMEOUT_MS;
|
|
128
|
+
let ms: number;
|
|
129
|
+
if (requested === 0 || requested === Infinity) {
|
|
130
|
+
ms = requested;
|
|
131
|
+
} else if (Number.isFinite(requested) && requested > 0) {
|
|
132
|
+
ms = requested;
|
|
133
|
+
} else {
|
|
134
|
+
if (process.env.NODE_ENV !== "production") {
|
|
135
|
+
console.warn(
|
|
136
|
+
`[rango] defer(): invalid timeout ${String(requested)}; using the ` +
|
|
137
|
+
`${DEFAULT_DEFER_TIMEOUT_MS}ms default so the safety net stays on. ` +
|
|
138
|
+
`Use 0 or Infinity to disable the timeout intentionally.`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
ms = DEFAULT_DEFER_TIMEOUT_MS;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (ms > 0 && ms !== Infinity) {
|
|
145
|
+
timer = setTimeout(() => {
|
|
146
|
+
if (process.env.NODE_ENV !== "production") {
|
|
147
|
+
console.warn(
|
|
148
|
+
`[rango] A deferred handle value was not resolved within ${ms}ms; ` +
|
|
149
|
+
`resolving to the fallback so the response can flush. Call the ` +
|
|
150
|
+
`resolver from the component that produces the value, or raise timeoutMs.`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
finish(options?.fallback);
|
|
154
|
+
}, ms);
|
|
155
|
+
// Don't let a pending timer alone keep a Node process alive (no-op on workerd).
|
|
156
|
+
(timer as { unref?: () => void }).unref?.();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return { promise, resolve: finish };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Attach `.defer()` to a handle push function. The deferred slot is reserved by
|
|
164
|
+
* pushing the deferred promise through the same push (so ordering, sealing, and
|
|
165
|
+
* Flight streaming all reuse the existing path); the returned resolver settles it.
|
|
166
|
+
*/
|
|
167
|
+
export function withDefer<TData>(push: HandlePushFn<TData>): HandlePush<TData> {
|
|
168
|
+
const handlePush = push as HandlePush<TData>;
|
|
169
|
+
// Safe to mutate push in place: each ctx.use(Handle) call (request-context.ts,
|
|
170
|
+
// loader-resolution.ts) builds a fresh closure, so .defer never leaks across
|
|
171
|
+
// handles or requests.
|
|
172
|
+
handlePush.defer = (options) => {
|
|
173
|
+
const deferred = createDeferred<TData>({
|
|
174
|
+
timeoutMs: options?.timeoutMs,
|
|
175
|
+
fallback: options?.else,
|
|
176
|
+
});
|
|
177
|
+
// Reserve the slot now by pushing the pending promise (the renderer use()s it).
|
|
178
|
+
push(deferred.promise as Promise<TData>);
|
|
179
|
+
// The resolver is push-equal: a thunk is invoked immediately (as push does)
|
|
180
|
+
// and a Promise is adopted by the reserved slot. Calling it settles the slot
|
|
181
|
+
// and cancels the timeout — the timeout only fires if it is never called.
|
|
182
|
+
const resolveSlot = deferred.resolve as (
|
|
183
|
+
value: TData | Promise<TData>,
|
|
184
|
+
) => void;
|
|
185
|
+
return (data) => {
|
|
186
|
+
// The thunk runs without re-entering the push-callback scope a direct push
|
|
187
|
+
// thunk gets on the action/revalidation path (loader-resolution.ts): a
|
|
188
|
+
// deferred resolver fires from a deep component after the handler phase has
|
|
189
|
+
// closed, so there is no live deadlock-guard window to exempt.
|
|
190
|
+
resolveSlot(
|
|
191
|
+
typeof data === "function" ? (data as () => Promise<TData>)() : data,
|
|
192
|
+
);
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
return handlePush;
|
|
196
|
+
}
|
package/src/deps/ssr.ts
CHANGED
package/src/errors.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Custom error classes for
|
|
2
|
+
* Custom error classes for Rango
|
|
3
3
|
*
|
|
4
4
|
* All errors include:
|
|
5
5
|
* - Descriptive names for easy identification
|
|
@@ -27,6 +27,17 @@ export class RouteNotFoundError extends Error {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
// name fallback covers cross-realm errors (Vite dev dupes, RSC serialization)
|
|
31
|
+
// where instanceof fails.
|
|
32
|
+
export function isRouteNotFoundError(
|
|
33
|
+
error: unknown,
|
|
34
|
+
): error is RouteNotFoundError {
|
|
35
|
+
return (
|
|
36
|
+
error instanceof RouteNotFoundError ||
|
|
37
|
+
(error instanceof Error && error.name === "RouteNotFoundError")
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
30
41
|
/**
|
|
31
42
|
* Thrown when data is not found (e.g., product with ID doesn't exist)
|
|
32
43
|
* Use this in handlers/loaders to trigger the nearest notFoundBoundary
|
|
@@ -109,6 +120,24 @@ export class BuildError extends Error {
|
|
|
109
120
|
}
|
|
110
121
|
}
|
|
111
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Thrown when a route-definition DSL helper (route/layout/loader/cache/…) is
|
|
125
|
+
* called outside an active urls()/map() builder, so there is no
|
|
126
|
+
* AsyncLocalStorage build context to attach to. The message names the specific
|
|
127
|
+
* helper and how to fix it; the `cause` records the mechanical reason so the
|
|
128
|
+
* failure mode is identifiable (not conflated with an unrelated throw).
|
|
129
|
+
*/
|
|
130
|
+
export class DslContextError extends Error {
|
|
131
|
+
name = "DslContextError" as const;
|
|
132
|
+
cause?: unknown;
|
|
133
|
+
|
|
134
|
+
constructor(message: string, options?: ErrorOptions) {
|
|
135
|
+
super(message);
|
|
136
|
+
Object.setPrototypeOf(this, DslContextError.prototype);
|
|
137
|
+
this.cause = options?.cause;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
112
141
|
/**
|
|
113
142
|
* Thrown when a network request fails (server unreachable, no internet, etc.)
|
|
114
143
|
* This error triggers the root error boundary with retry capability.
|
|
@@ -196,7 +225,6 @@ export function isNetworkError(error: unknown): boolean {
|
|
|
196
225
|
export class RouterError extends Error {
|
|
197
226
|
name = "RouterError" as const;
|
|
198
227
|
code: string;
|
|
199
|
-
type?: string;
|
|
200
228
|
status: number;
|
|
201
229
|
cause?: unknown;
|
|
202
230
|
|
|
@@ -205,7 +233,6 @@ export class RouterError extends Error {
|
|
|
205
233
|
message: string,
|
|
206
234
|
options?: {
|
|
207
235
|
status?: number;
|
|
208
|
-
type?: string;
|
|
209
236
|
cause?: unknown;
|
|
210
237
|
},
|
|
211
238
|
) {
|
|
@@ -213,7 +240,6 @@ export class RouterError extends Error {
|
|
|
213
240
|
Object.setPrototypeOf(this, RouterError.prototype);
|
|
214
241
|
this.code = code;
|
|
215
242
|
this.status = options?.status ?? 500;
|
|
216
|
-
this.type = options?.type;
|
|
217
243
|
this.cause = options?.cause;
|
|
218
244
|
}
|
|
219
245
|
}
|