@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.140
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 +17 -0
- package/README.md +432 -7
- package/dist/bin/rango.js +2073 -213
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +7258 -2714
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +140 -67
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +329 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +487 -0
- package/skills/caching/SKILL.md +357 -25
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +246 -0
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +16 -10
- package/skills/document-cache/SKILL.md +87 -62
- package/skills/fonts/SKILL.md +6 -4
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +557 -79
- package/skills/host-router/SKILL.md +320 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +207 -15
- package/skills/layout/SKILL.md +146 -6
- package/skills/links/SKILL.md +304 -25
- package/skills/loader/SKILL.md +616 -54
- package/skills/middleware/SKILL.md +217 -37
- package/skills/migrate-nextjs/SKILL.md +611 -0
- package/skills/migrate-react-router/SKILL.md +927 -0
- package/skills/mime-routes/SKILL.md +42 -11
- package/skills/observability/SKILL.md +194 -0
- package/skills/parallel/SKILL.md +284 -3
- package/skills/ppr/SKILL.md +426 -0
- package/skills/prerender/SKILL.md +437 -52
- package/skills/rango/SKILL.md +369 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +263 -121
- package/skills/route/SKILL.md +350 -21
- package/skills/router-setup/SKILL.md +246 -33
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +126 -222
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +131 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +85 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +532 -103
- package/skills/use-cache/SKILL.md +367 -0
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +337 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +77 -44
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +114 -0
- 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/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +293 -202
- package/src/browser/history-state.ts +101 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +11 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +385 -576
- package/src/browser/navigation-client.ts +245 -75
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +184 -118
- package/src/browser/navigation-transaction.ts +247 -0
- package/src/browser/network-error-handler.ts +88 -0
- package/src/browser/partial-update.ts +412 -364
- package/src/browser/prefetch/cache.ts +359 -0
- package/src/browser/prefetch/fetch.ts +452 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +209 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +194 -0
- package/src/browser/react/Link.tsx +275 -68
- package/src/browser/react/NavigationProvider.tsx +265 -109
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +70 -0
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +272 -60
- package/src/browser/react/location-state.ts +90 -20
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +35 -66
- package/src/browser/react/use-handle.ts +39 -126
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +39 -13
- package/src/browser/react/use-navigation.ts +53 -69
- package/src/browser/react/use-params.ts +75 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +98 -0
- package/src/browser/react/use-search-params.ts +51 -0
- package/src/browser/react/use-segments.ts +72 -99
- package/src/browser/response-adapter.ts +164 -0
- package/src/browser/rsc-router.tsx +300 -72
- package/src/browser/scroll-restoration.ts +138 -50
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +17 -1
- package/src/browser/server-action-bridge.ts +668 -613
- package/src/browser/types.ts +223 -51
- package/src/browser/validate-redirect-origin.ts +56 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +252 -161
- package/src/build/generate-route-types.ts +41 -1038
- package/src/build/index.ts +12 -7
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +225 -42
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +105 -0
- package/src/build/route-types/codegen.ts +113 -0
- package/src/build/route-types/include-resolution.ts +812 -0
- package/src/build/route-types/param-extraction.ts +51 -0
- package/src/build/route-types/per-module-writer.ts +144 -0
- package/src/build/route-types/router-processing.ts +695 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +223 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +60 -0
- package/src/cache/cache-policy.ts +199 -0
- package/src/cache/cache-runtime.ts +525 -0
- package/src/cache/cache-scope.ts +298 -332
- package/src/cache/cache-tag.ts +103 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +2500 -158
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +17 -17
- package/src/cache/document-cache.ts +199 -92
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +111 -0
- package/src/cache/index.ts +29 -35
- package/src/cache/memory-segment-store.ts +363 -30
- package/src/cache/profile-registry.ts +88 -0
- package/src/cache/read-through-swr.ts +178 -0
- package/src/cache/segment-codec.ts +248 -0
- package/src/cache/shell-cache.ts +386 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +156 -211
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1102 -0
- package/src/client.rsc.tsx +43 -21
- package/src/client.tsx +131 -347
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +23 -4
- package/src/components/DefaultDocument.tsx +13 -3
- package/src/context-var.ts +168 -0
- package/src/debug.ts +19 -9
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +106 -10
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +110 -35
- package/src/handles/MetaTags.tsx +83 -59
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +93 -0
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +44 -53
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +20 -65
- package/src/host/errors.ts +21 -30
- package/src/host/index.ts +13 -9
- package/src/host/pattern-matcher.ts +50 -79
- package/src/host/router.ts +151 -121
- package/src/host/testing.ts +45 -32
- package/src/host/types.ts +52 -11
- package/src/host/utils.ts +2 -2
- package/src/href-client.ts +192 -57
- package/src/index.rsc.ts +177 -35
- package/src/index.ts +255 -71
- package/src/internal-debug.ts +9 -2
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +31 -99
- package/src/loader.ts +30 -12
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +41 -0
- package/src/prerender/param-hash.ts +16 -14
- package/src/prerender/store.ts +121 -21
- package/src/prerender.ts +460 -26
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +62 -0
- package/src/reverse.ts +198 -128
- package/src/root-error-boundary.tsx +42 -48
- package/src/route-content-wrapper.tsx +22 -77
- package/src/route-definition/dsl-helpers.ts +1116 -0
- package/src/route-definition/helper-factories.ts +88 -0
- package/src/route-definition/helpers-types.ts +505 -0
- package/src/route-definition/index.ts +54 -0
- package/src/route-definition/redirect.ts +134 -0
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-definition.ts +1 -1481
- package/src/route-map-builder.ts +82 -144
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +71 -45
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +263 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +54 -27
- package/src/router/find-match.ts +245 -0
- package/src/router/handler-context.ts +377 -125
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +59 -28
- package/src/router/lazy-includes.ts +254 -0
- package/src/router/loader-resolution.ts +421 -157
- package/src/router/logging.ts +106 -6
- package/src/router/manifest.ts +131 -57
- package/src/router/match-api.ts +167 -246
- package/src/router/match-context.ts +4 -24
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +117 -93
- package/src/router/match-middleware/cache-lookup.ts +297 -150
- package/src/router/match-middleware/cache-store.ts +123 -51
- package/src/router/match-middleware/intercept-resolution.ts +44 -43
- package/src/router/match-middleware/segment-resolution.ts +64 -22
- package/src/router/match-pipelines.ts +11 -87
- package/src/router/match-result.ts +121 -50
- package/src/router/metrics.ts +219 -28
- package/src/router/middleware-types.ts +93 -0
- package/src/router/middleware.ts +505 -441
- package/src/router/navigation-snapshot.ts +133 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +311 -142
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +547 -0
- package/src/router/preview-match.ts +102 -0
- package/src/router/request-classification.ts +278 -0
- package/src/router/revalidation.ts +203 -62
- package/src/router/route-snapshot.ts +246 -0
- package/src/router/router-context.ts +45 -48
- package/src/router/router-interfaces.ts +554 -0
- package/src/router/router-options.ts +779 -0
- package/src/router/router-registry.ts +21 -0
- package/src/router/segment-resolution/fresh.ts +772 -0
- package/src/router/segment-resolution/helpers.ts +348 -0
- package/src/router/segment-resolution/loader-cache.ts +250 -0
- package/src/router/segment-resolution/loader-mask.ts +44 -0
- package/src/router/segment-resolution/revalidation.ts +1331 -0
- package/src/router/segment-resolution/static-store.ts +81 -0
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +56 -0
- package/src/router/segment-resolution.ts +25 -1354
- package/src/router/segment-wrappers.ts +292 -0
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +75 -0
- package/src/router/telemetry-otel.ts +261 -0
- package/src/router/telemetry.ts +377 -0
- package/src/router/timeout.ts +128 -0
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +240 -61
- package/src/router/types.ts +23 -70
- package/src/router/url-params.ts +57 -0
- package/src/router.ts +781 -2378
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +46 -0
- package/src/rsc/handler.ts +905 -1142
- package/src/rsc/helpers.ts +275 -19
- package/src/rsc/index.ts +2 -25
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +305 -0
- package/src/rsc/manifest-init.ts +77 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +155 -0
- package/src/rsc/progressive-enhancement.ts +502 -0
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +104 -0
- package/src/rsc/response-route-handler.ts +257 -0
- package/src/rsc/rsc-rendering.ts +337 -0
- package/src/rsc/runtime-warnings.ts +55 -0
- package/src/rsc/server-action.ts +522 -0
- package/src/rsc/shell-capture.ts +439 -0
- package/src/rsc/ssr-setup.ts +144 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +95 -12
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +99 -82
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +349 -134
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +452 -85
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +310 -0
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +123 -42
- package/src/server/live.ts +130 -0
- package/src/server/loader-registry.ts +51 -100
- package/src/server/request-context.ts +842 -157
- package/src/server.ts +15 -8
- package/src/ssr/index.tsx +412 -136
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +45 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +46 -0
- package/src/testing/dispatch.ts +701 -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 +257 -0
- package/src/testing/generated-routes.ts +199 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +371 -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 +357 -0
- package/src/testing/render-route.tsx +584 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/run-transition-when.ts +164 -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 +40 -72
- package/src/theme/ThemeScript.tsx +12 -14
- package/src/theme/constants.ts +57 -15
- package/src/theme/index.ts +3 -20
- package/src/theme/theme-context.ts +5 -35
- package/src/theme/theme-script.ts +43 -39
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +123 -0
- package/src/types/cache-types.ts +207 -0
- package/src/types/error-types.ts +132 -0
- package/src/types/global-namespace.ts +113 -0
- package/src/types/handler-context.ts +839 -0
- package/src/types/index.ts +81 -0
- package/src/types/loader-types.ts +212 -0
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +138 -0
- package/src/types/route-entry.ts +114 -0
- package/src/types/segments.ts +271 -0
- package/src/types.ts +1 -1795
- package/src/urls/include-helper.ts +162 -0
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +43 -0
- package/src/urls/path-helper-types.ts +413 -0
- package/src/urls/path-helper.ts +275 -0
- package/src/urls/pattern-types.ts +124 -0
- package/src/urls/response-types.ts +109 -0
- package/src/urls/type-extraction.ts +316 -0
- package/src/urls/urls-function.ts +80 -0
- package/src/urls.ts +1 -1341
- package/src/use-loader.tsx +406 -141
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +182 -0
- package/src/vite/discovery/discover-routers.ts +389 -0
- package/src/vite/discovery/discovery-errors.ts +255 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +467 -0
- package/src/vite/discovery/route-types-writer.ts +214 -0
- package/src/vite/discovery/self-gen-tracking.ts +73 -0
- package/src/vite/discovery/state.ts +161 -0
- package/src/vite/discovery/virtual-module-codegen.ts +183 -0
- package/src/vite/index.ts +23 -2255
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +303 -0
- package/src/vite/plugins/cjs-to-esm.ts +90 -0
- package/src/vite/plugins/client-ref-dedup.ts +120 -0
- package/src/vite/plugins/client-ref-hashing.ts +118 -0
- 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/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
- package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
- package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +805 -0
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +313 -0
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +94 -0
- package/src/vite/plugins/version-plugin.ts +263 -0
- package/src/vite/plugins/virtual-entries.ts +234 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +560 -0
- package/src/vite/router-discovery.ts +1638 -0
- package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +132 -0
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +15 -0
- package/src/vite/utils/package-resolution.ts +89 -0
- package/src/vite/utils/prerender-utils.ts +249 -0
- package/src/vite/utils/shared-utils.ts +269 -0
- package/CLAUDE.md +0 -43
- package/dist/vite/index.named-routes.gen.ts +0 -103
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/react/use-client-cache.ts +0 -56
- package/src/browser/request-controller.ts +0 -164
- package/src/browser/shallow.ts +0 -35
- package/src/cache/memory-store.ts +0 -253
- package/src/handles/index.ts +0 -6
- package/src/href-context.ts +0 -33
- package/src/network-error-thrower.tsx +0 -21
- package/src/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-internal-ids.ts +0 -1167
- package/src/vite/package-resolution.ts +0 -125
- package/src/vite/virtual-entries.ts +0 -114
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
package/src/use-loader.tsx
CHANGED
|
@@ -1,129 +1,327 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
isValidElement,
|
|
5
|
+
startTransition,
|
|
6
|
+
useCallback,
|
|
7
|
+
useContext,
|
|
8
|
+
useEffect,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
useState,
|
|
12
|
+
type ReactNode,
|
|
13
|
+
} from "react";
|
|
4
14
|
import { OutletContext, type OutletContextValue } from "./outlet-context.js";
|
|
15
|
+
import { loaderStore, type LoaderEntry } from "./loader-store.js";
|
|
5
16
|
import type { LoaderDefinition, LoadOptions } from "./types.js";
|
|
6
17
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
18
|
+
function isShareableGet(options: LoadOptions | undefined): boolean {
|
|
19
|
+
if (!options) return true;
|
|
20
|
+
if (options.method && options.method !== "GET") return false;
|
|
21
|
+
if ("body" in options && (options as { body?: unknown }).body !== undefined) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isPlainRefetch(options: LoadOptions | undefined): boolean {
|
|
28
|
+
if (!isShareableGet(options)) return false;
|
|
29
|
+
if (options?.params && Object.keys(options.params).length > 0) return false;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let privateGroupBucketSeq = 0;
|
|
34
|
+
|
|
35
|
+
const NOT_FOUND = Symbol("not-found");
|
|
36
|
+
|
|
37
|
+
function extractContentLoaderData(
|
|
38
|
+
node: ReactNode,
|
|
39
|
+
loaderId: string,
|
|
40
|
+
): unknown | typeof NOT_FOUND {
|
|
41
|
+
if (!isValidElement(node)) return NOT_FOUND;
|
|
42
|
+
const props = node.props as Record<string, any> | undefined;
|
|
43
|
+
if (!props) return NOT_FOUND;
|
|
44
|
+
|
|
45
|
+
// Direct OutletProvider with loaderData
|
|
46
|
+
if (props.loaderData && loaderId in props.loaderData) {
|
|
47
|
+
return props.loaderData[loaderId];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (
|
|
51
|
+
props.loaderIds &&
|
|
52
|
+
Array.isArray(props.loaderIds) &&
|
|
53
|
+
props.loaderDataPromise &&
|
|
54
|
+
!(props.loaderDataPromise instanceof Promise)
|
|
55
|
+
) {
|
|
56
|
+
const idx = (props.loaderIds as string[]).indexOf(loaderId);
|
|
57
|
+
if (idx !== -1) {
|
|
58
|
+
const data = (props.loaderDataPromise as any[])[idx];
|
|
59
|
+
if (data && typeof data === "object" && "ok" in data) {
|
|
60
|
+
return data.ok ? data.data : NOT_FOUND;
|
|
61
|
+
}
|
|
62
|
+
return data;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (props.children) return extractContentLoaderData(props.children, loaderId);
|
|
67
|
+
return NOT_FOUND;
|
|
68
|
+
}
|
|
69
|
+
|
|
10
70
|
interface LoaderRscPayload<T = unknown> {
|
|
11
71
|
loaderResult: T;
|
|
12
72
|
loaderError?: { message: string; name: string };
|
|
13
73
|
}
|
|
14
74
|
|
|
15
|
-
|
|
16
|
-
* Load function type with form action support
|
|
17
|
-
*/
|
|
18
|
-
export type LoadFunction<T> = ((options?: LoadOptions) => Promise<T>) & {
|
|
19
|
-
/** Form action for progressive enhancement - can be passed to form action prop */
|
|
20
|
-
action: (formData: FormData) => Promise<void>;
|
|
21
|
-
};
|
|
75
|
+
export type LoadFunction<T> = (options?: LoadOptions) => Promise<T>;
|
|
22
76
|
|
|
23
|
-
/**
|
|
24
|
-
* Result type for useLoader hook (strict - data is required)
|
|
25
|
-
*/
|
|
26
77
|
export interface UseLoaderResult<T> {
|
|
27
|
-
/** The loaded data - guaranteed to exist when loader is registered on route */
|
|
28
78
|
data: T;
|
|
29
|
-
/** True while a load() is in progress */
|
|
30
79
|
isLoading: boolean;
|
|
31
|
-
/** Error from the most recent load attempt, null if successful */
|
|
32
80
|
error: Error | null;
|
|
33
|
-
/** Function to trigger a fetch (only works if loader is fetchable) */
|
|
34
81
|
load: LoadFunction<T>;
|
|
35
|
-
/** Alias for load */
|
|
36
82
|
refetch: LoadFunction<T>;
|
|
37
83
|
}
|
|
38
84
|
|
|
39
|
-
/**
|
|
40
|
-
* Result type for useFetchLoader hook (flexible - data is optional)
|
|
41
|
-
*/
|
|
42
85
|
export interface UseFetchLoaderResult<T> {
|
|
43
|
-
/** The loaded data - may be undefined if not yet fetched or not in context */
|
|
44
86
|
data: T | undefined;
|
|
45
|
-
/** True while a load() is in progress */
|
|
46
87
|
isLoading: boolean;
|
|
47
|
-
/** Error from the most recent load attempt, null if successful */
|
|
48
88
|
error: Error | null;
|
|
49
|
-
/** Function to trigger a fetch (only works if loader is fetchable) */
|
|
50
89
|
load: LoadFunction<T>;
|
|
51
|
-
/** Alias for load */
|
|
52
90
|
refetch: LoadFunction<T>;
|
|
53
91
|
}
|
|
54
92
|
|
|
55
|
-
/**
|
|
56
|
-
* Options for useLoader hook
|
|
57
|
-
*/
|
|
58
93
|
export interface UseLoaderOptions {
|
|
59
|
-
/**
|
|
60
|
-
* If true (default), errors from load() will be thrown to the nearest error boundary.
|
|
61
|
-
* If false, errors are only captured in the `error` state.
|
|
62
|
-
* @default true
|
|
63
|
-
*/
|
|
64
94
|
throwOnError?: boolean;
|
|
95
|
+
key?: string;
|
|
96
|
+
refreshGroup?: string | string[];
|
|
65
97
|
}
|
|
66
98
|
|
|
67
|
-
/**
|
|
68
|
-
* Internal hook implementation shared by useLoader and useFetchLoader
|
|
69
|
-
*/
|
|
70
99
|
function useLoaderInternal<T>(
|
|
71
100
|
loader: LoaderDefinition<T>,
|
|
72
|
-
options?: UseLoaderOptions
|
|
101
|
+
options?: UseLoaderOptions,
|
|
73
102
|
): UseFetchLoaderResult<T> {
|
|
74
103
|
const context = useContext(OutletContext);
|
|
75
104
|
|
|
76
|
-
|
|
77
|
-
|
|
105
|
+
const { contextData, hasContextData } = useMemo((): {
|
|
106
|
+
contextData: T | undefined;
|
|
107
|
+
hasContextData: boolean;
|
|
108
|
+
} => {
|
|
78
109
|
let current: OutletContextValue | null | undefined = context;
|
|
79
110
|
while (current) {
|
|
80
111
|
if (current.loaderData && loader.$$id in current.loaderData) {
|
|
81
|
-
return
|
|
112
|
+
return {
|
|
113
|
+
contextData: current.loaderData[loader.$$id] as T,
|
|
114
|
+
hasContextData: true,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const contentData = extractContentLoaderData(
|
|
118
|
+
current.content,
|
|
119
|
+
loader.$$id,
|
|
120
|
+
);
|
|
121
|
+
if (contentData !== NOT_FOUND) {
|
|
122
|
+
return { contextData: contentData as T, hasContextData: true };
|
|
82
123
|
}
|
|
83
124
|
current = current.parent;
|
|
84
125
|
}
|
|
85
|
-
return undefined;
|
|
126
|
+
return { contextData: undefined, hasContextData: false };
|
|
86
127
|
}, [context, loader.$$id]);
|
|
87
128
|
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
129
|
+
const loaderId = loader.$$id;
|
|
130
|
+
const key = options?.key;
|
|
131
|
+
const refreshGroupOption = options?.refreshGroup;
|
|
132
|
+
const groupKey =
|
|
133
|
+
refreshGroupOption === undefined
|
|
134
|
+
? ""
|
|
135
|
+
: JSON.stringify(
|
|
136
|
+
typeof refreshGroupOption === "string"
|
|
137
|
+
? [refreshGroupOption]
|
|
138
|
+
: [...new Set(refreshGroupOption)].sort(),
|
|
139
|
+
);
|
|
140
|
+
const groupList = useMemo<string[]>(
|
|
141
|
+
() => (groupKey === "" ? [] : (JSON.parse(groupKey) as string[])),
|
|
142
|
+
[groupKey],
|
|
143
|
+
);
|
|
144
|
+
const hasGroups = groupList.length > 0;
|
|
145
|
+
const privateBucketIdRef = useRef<string | null>(null);
|
|
146
|
+
if (hasGroups && key === undefined && privateBucketIdRef.current === null) {
|
|
147
|
+
privateBucketIdRef.current = `__rg${privateGroupBucketSeq++}`;
|
|
148
|
+
}
|
|
149
|
+
const effectiveKey =
|
|
150
|
+
key ?? (hasGroups ? privateBucketIdRef.current! : undefined);
|
|
151
|
+
const bucketKey =
|
|
152
|
+
effectiveKey === undefined ? loaderId : `${loaderId}::${effectiveKey}`;
|
|
153
|
+
|
|
154
|
+
const groupRefetch = useCallback(async (): Promise<void> => {
|
|
155
|
+
if (!loaderId) return;
|
|
156
|
+
const requestId = loaderStore.reserveRequestId(bucketKey);
|
|
157
|
+
loaderStore.beginRequest(bucketKey, requestId);
|
|
158
|
+
try {
|
|
159
|
+
const url = new URL(window.location.href);
|
|
160
|
+
url.searchParams.set("_rsc_loader", loaderId);
|
|
161
|
+
const response = fetch(url.toString(), {
|
|
162
|
+
method: "GET",
|
|
163
|
+
headers: { Accept: "text/x-component" },
|
|
164
|
+
});
|
|
165
|
+
const { createFromFetch } = await import("./deps/browser.js");
|
|
166
|
+
const payload = await createFromFetch<LoaderRscPayload<T>>(response);
|
|
167
|
+
if (payload.loaderError) {
|
|
168
|
+
throw new Error(payload.loaderError.message);
|
|
169
|
+
}
|
|
170
|
+
loaderStore.finishData(bucketKey, requestId, payload.loaderResult);
|
|
171
|
+
} catch (e) {
|
|
172
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
173
|
+
loaderStore.finishError(bucketKey, requestId, err);
|
|
174
|
+
throw err;
|
|
175
|
+
} finally {
|
|
176
|
+
loaderStore.setLoading(bucketKey, requestId, false);
|
|
177
|
+
}
|
|
178
|
+
}, [loaderId, bucketKey]);
|
|
179
|
+
|
|
180
|
+
const [sharedState, setSharedState] = useState<{
|
|
181
|
+
bucketKey: string;
|
|
182
|
+
snapshot: LoaderEntry;
|
|
183
|
+
}>(() => ({
|
|
184
|
+
bucketKey,
|
|
185
|
+
snapshot: loaderStore.getSnapshot(bucketKey),
|
|
186
|
+
}));
|
|
187
|
+
const sharedSnapshot =
|
|
188
|
+
sharedState.bucketKey === bucketKey
|
|
189
|
+
? sharedState.snapshot
|
|
190
|
+
: loaderStore.getSnapshot(bucketKey);
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
const initial = loaderStore.getSnapshot(bucketKey);
|
|
193
|
+
if (initial !== sharedSnapshot) {
|
|
194
|
+
startTransition(() => {
|
|
195
|
+
setSharedState({ bucketKey, snapshot: initial });
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
// ephemeral: a reader with no route context has no route-context reset
|
|
199
|
+
// trigger, so its keyed bucket is reference-counted by the store. A
|
|
200
|
+
// route-registered reader makes the bucket sticky (reset via clearFamily).
|
|
201
|
+
return loaderStore.subscribe(
|
|
202
|
+
bucketKey,
|
|
203
|
+
() => {
|
|
204
|
+
const next = loaderStore.getSnapshot(bucketKey);
|
|
205
|
+
startTransition(() => {
|
|
206
|
+
setSharedState({ bucketKey, snapshot: next });
|
|
207
|
+
});
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
loaderId,
|
|
211
|
+
ephemeral: !hasContextData,
|
|
212
|
+
group: hasGroups ? groupList : undefined,
|
|
213
|
+
refetch: hasGroups ? groupRefetch : undefined,
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- intentional:
|
|
217
|
+
// sharedSnapshot is captured for the one-shot init sync; we don't want
|
|
218
|
+
// to re-subscribe on every snapshot change. bucketKey, hasContextData,
|
|
219
|
+
// groupKey, and groupRefetch are the only inputs that require a fresh
|
|
220
|
+
// subscription (groupList is memoized on groupKey; groupRefetch is stable
|
|
221
|
+
// per bucketKey).
|
|
222
|
+
}, [bucketKey, hasContextData, groupKey, groupRefetch]);
|
|
223
|
+
|
|
224
|
+
// Local state holds the result of:
|
|
225
|
+
// - parameterized / mutation `load()` calls (load({ params }), POST,
|
|
226
|
+
// etc.) — stay scoped so concurrent same-loader different-params
|
|
227
|
+
// fetches don't clobber each other through the shared store;
|
|
228
|
+
// - any `load()` made by hooks that are NOT in route context (i.e.
|
|
229
|
+
// useFetchLoader of an unregistered loader) — keeping those local
|
|
230
|
+
// prevents two unrelated components from accidentally sharing data
|
|
231
|
+
// through the global store just because they reference the same
|
|
232
|
+
// loader id.
|
|
233
|
+
// `has` distinguishes a committed local result (including `null`/`undefined`)
|
|
234
|
+
// from "no local load yet", so a load() that resolves to a falsy value is not
|
|
235
|
+
// discarded in favor of the shared snapshot or the seeded context.
|
|
236
|
+
const [localFetchedData, setLocalFetchedData] = useState<{
|
|
237
|
+
has: boolean;
|
|
238
|
+
value: T | undefined;
|
|
239
|
+
}>({ has: false, value: undefined });
|
|
240
|
+
const [localIsLoading, setLocalIsLoading] = useState(false);
|
|
241
|
+
const [localError, setLocalError] = useState<Error | null>(null);
|
|
242
|
+
|
|
243
|
+
// Local request id, mirrors the per-hook gating the previous
|
|
244
|
+
// implementation provided. Two quick parameterized loads from the same
|
|
245
|
+
// hook (e.g. load({ params: { q: "a" } }) then load({ params: { q: "b" } }))
|
|
246
|
+
// can resolve out of order — only the latest must commit.
|
|
247
|
+
const localRequestIdRef = useRef(0);
|
|
248
|
+
|
|
249
|
+
// Tracks the request id of the most recent SHARED load() this hook
|
|
250
|
+
// initiated. The render-throw rule below uses it to scope the throw
|
|
251
|
+
// to the originating hook only — sibling readers see the error in
|
|
252
|
+
// `error` but don't blow up their own boundaries.
|
|
253
|
+
const lastSharedRequestIdRef = useRef<number | null>(null);
|
|
254
|
+
|
|
255
|
+
// Reset on navigation. clear() bumps the entry's latest request id so
|
|
256
|
+
// any pre-navigation load() promise that resolves later fails its gate
|
|
257
|
+
// and is dropped — fixes the race where a stale fetch overwrites the
|
|
258
|
+
// new route's context.
|
|
96
259
|
const prevContextDataRef = useRef(contextData);
|
|
97
260
|
useEffect(() => {
|
|
98
261
|
if (prevContextDataRef.current !== contextData) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
262
|
+
setLocalFetchedData({ has: false, value: undefined });
|
|
263
|
+
setLocalIsLoading(false);
|
|
264
|
+
setLocalError(null);
|
|
265
|
+
lastSharedRequestIdRef.current = null;
|
|
266
|
+
// Reset every sticky bucket of this loader (keyed or not). Ephemeral
|
|
267
|
+
// (unregistered keyed) buckets are left to their refcount lifecycle.
|
|
268
|
+
loaderStore.clearFamily(loaderId);
|
|
102
269
|
prevContextDataRef.current = contextData;
|
|
103
270
|
}
|
|
104
|
-
}, [contextData]);
|
|
105
|
-
|
|
106
|
-
//
|
|
107
|
-
|
|
271
|
+
}, [contextData, loaderId]);
|
|
272
|
+
|
|
273
|
+
// Read priority: a committed parameterized load() result overrides the shared
|
|
274
|
+
// snapshot; a committed shared snapshot overrides the server-seeded context.
|
|
275
|
+
// `has`/`hasValue` gate each level so a committed falsy value is not skipped.
|
|
276
|
+
const data = localFetchedData.has
|
|
277
|
+
? localFetchedData.value
|
|
278
|
+
: sharedSnapshot.hasValue
|
|
279
|
+
? (sharedSnapshot.value as T | undefined)
|
|
280
|
+
: contextData;
|
|
281
|
+
const isLoading = localIsLoading || sharedSnapshot.isLoading;
|
|
282
|
+
const error = localError ?? sharedSnapshot.error;
|
|
108
283
|
|
|
109
284
|
const throwOnError = options?.throwOnError ?? true;
|
|
110
285
|
|
|
111
|
-
|
|
286
|
+
const loaderIdRef = useRef(loaderId);
|
|
287
|
+
loaderIdRef.current = loaderId;
|
|
288
|
+
const bucketKeyRef = useRef(bucketKey);
|
|
289
|
+
bucketKeyRef.current = bucketKey;
|
|
290
|
+
const dataRef = useRef(data);
|
|
291
|
+
dataRef.current = data;
|
|
292
|
+
const hasContextDataRef = useRef(hasContextData);
|
|
293
|
+
hasContextDataRef.current = hasContextData;
|
|
294
|
+
|
|
112
295
|
const load = useCallback(
|
|
113
296
|
async (loadOptions?: LoadOptions): Promise<T> => {
|
|
114
|
-
|
|
115
|
-
if (!
|
|
297
|
+
const id = loaderIdRef.current;
|
|
298
|
+
if (!id) {
|
|
116
299
|
throw new Error(
|
|
117
|
-
`Loader is missing $$id. Make sure the exposeLoaderId Vite plugin is enabled
|
|
300
|
+
`Loader is missing $$id. Make sure the exposeLoaderId Vite plugin is enabled.`,
|
|
118
301
|
);
|
|
119
302
|
}
|
|
120
303
|
|
|
121
|
-
|
|
122
|
-
|
|
304
|
+
const bucket = bucketKeyRef.current;
|
|
305
|
+
const hasDedicatedBucket = bucket !== id;
|
|
306
|
+
|
|
307
|
+
const shared = hasDedicatedBucket
|
|
308
|
+
? isShareableGet(loadOptions)
|
|
309
|
+
: isPlainRefetch(loadOptions) && hasContextDataRef.current;
|
|
310
|
+
let sharedRequestId = -1;
|
|
311
|
+
let localRequestId = -1;
|
|
312
|
+
if (shared) {
|
|
313
|
+
sharedRequestId = loaderStore.reserveRequestId(bucket);
|
|
314
|
+
lastSharedRequestIdRef.current = sharedRequestId;
|
|
315
|
+
loaderStore.beginRequest(bucket, sharedRequestId);
|
|
316
|
+
} else {
|
|
317
|
+
localRequestId = ++localRequestIdRef.current;
|
|
318
|
+
setLocalIsLoading(true);
|
|
319
|
+
setLocalError(null);
|
|
320
|
+
}
|
|
123
321
|
|
|
124
322
|
try {
|
|
125
|
-
const url = new URL(window.location.
|
|
126
|
-
url.searchParams.set("_rsc_loader",
|
|
323
|
+
const url = new URL(window.location.href);
|
|
324
|
+
url.searchParams.set("_rsc_loader", id);
|
|
127
325
|
|
|
128
326
|
const method = loadOptions?.method ?? "GET";
|
|
129
327
|
const isBodyMethod = method !== "GET";
|
|
@@ -131,29 +329,54 @@ function useLoaderInternal<T>(
|
|
|
131
329
|
let fetchOptions: RequestInit;
|
|
132
330
|
|
|
133
331
|
if (isBodyMethod) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
332
|
+
const bodyValue =
|
|
333
|
+
"body" in (loadOptions ?? {})
|
|
334
|
+
? (loadOptions as any).body
|
|
335
|
+
: undefined;
|
|
336
|
+
const hasParams =
|
|
337
|
+
loadOptions?.params && Object.keys(loadOptions.params).length > 0;
|
|
338
|
+
|
|
339
|
+
if (bodyValue instanceof FormData) {
|
|
340
|
+
if (hasParams) {
|
|
341
|
+
bodyValue.set(
|
|
342
|
+
"_rsc_loader_params",
|
|
343
|
+
JSON.stringify(loadOptions!.params),
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
fetchOptions = {
|
|
347
|
+
method,
|
|
348
|
+
headers: { Accept: "text/x-component" },
|
|
349
|
+
body: bodyValue,
|
|
350
|
+
};
|
|
351
|
+
} else {
|
|
352
|
+
const bodyPayload: {
|
|
353
|
+
params?: Record<string, string>;
|
|
354
|
+
body?: unknown;
|
|
355
|
+
} = {};
|
|
356
|
+
if (hasParams) {
|
|
357
|
+
bodyPayload.params = loadOptions!.params;
|
|
358
|
+
}
|
|
359
|
+
if (bodyValue !== undefined) {
|
|
360
|
+
bodyPayload.body = bodyValue;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
fetchOptions = {
|
|
364
|
+
method,
|
|
365
|
+
headers: {
|
|
366
|
+
Accept: "text/x-component",
|
|
367
|
+
"Content-Type": "application/json",
|
|
368
|
+
},
|
|
369
|
+
body: JSON.stringify(bodyPayload),
|
|
370
|
+
};
|
|
138
371
|
}
|
|
139
|
-
if ("body" in (loadOptions ?? {}) && (loadOptions as any).body !== undefined) {
|
|
140
|
-
bodyPayload.body = (loadOptions as any).body;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
fetchOptions = {
|
|
144
|
-
method,
|
|
145
|
-
headers: {
|
|
146
|
-
Accept: "text/x-component",
|
|
147
|
-
"Content-Type": "application/json",
|
|
148
|
-
},
|
|
149
|
-
body: JSON.stringify(bodyPayload),
|
|
150
|
-
};
|
|
151
372
|
} else {
|
|
152
|
-
|
|
153
|
-
|
|
373
|
+
if (
|
|
374
|
+
loadOptions?.params &&
|
|
375
|
+
Object.keys(loadOptions.params).length > 0
|
|
376
|
+
) {
|
|
154
377
|
url.searchParams.set(
|
|
155
378
|
"_rsc_loader_params",
|
|
156
|
-
JSON.stringify(loadOptions.params)
|
|
379
|
+
JSON.stringify(loadOptions.params),
|
|
157
380
|
);
|
|
158
381
|
}
|
|
159
382
|
|
|
@@ -175,73 +398,53 @@ function useLoaderInternal<T>(
|
|
|
175
398
|
}
|
|
176
399
|
|
|
177
400
|
const result = payload.loaderResult;
|
|
178
|
-
|
|
401
|
+
if (shared) {
|
|
402
|
+
loaderStore.finishData(bucket, sharedRequestId, result);
|
|
403
|
+
} else if (localRequestId === localRequestIdRef.current) {
|
|
404
|
+
startTransition(() => {
|
|
405
|
+
setLocalFetchedData({ has: true, value: result });
|
|
406
|
+
setLocalIsLoading(false);
|
|
407
|
+
});
|
|
408
|
+
}
|
|
179
409
|
return result;
|
|
180
410
|
} catch (e) {
|
|
181
411
|
const err = e instanceof Error ? e : new Error(String(e));
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
412
|
+
if (shared) {
|
|
413
|
+
loaderStore.finishError(bucket, sharedRequestId, err);
|
|
414
|
+
} else if (localRequestId === localRequestIdRef.current) {
|
|
415
|
+
setLocalError(err);
|
|
416
|
+
setLocalIsLoading(false);
|
|
185
417
|
}
|
|
186
|
-
// When throwOnError is false, return the current data (previous successful
|
|
187
|
-
// value or undefined). Caller should check error state for error handling.
|
|
188
|
-
return data as T;
|
|
189
|
-
} finally {
|
|
190
|
-
setIsLoading(false);
|
|
191
|
-
}
|
|
192
|
-
},
|
|
193
|
-
[throwOnError]
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
// Form action for progressive enhancement
|
|
197
|
-
// This wrapper is for useFetchLoader's load.action - it manages state internally
|
|
198
|
-
// and doesn't use React's useActionState. For true PE, use loader.action directly
|
|
199
|
-
// with useActionState.
|
|
200
|
-
const action = useCallback(
|
|
201
|
-
async (formData: FormData): Promise<void> => {
|
|
202
|
-
if (!loader.action) {
|
|
203
|
-
throw new Error(
|
|
204
|
-
`Loader "${loader.$$id}" does not have an action. ` +
|
|
205
|
-
`Make sure the loader is created with fetchable: true.`
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
setIsLoading(true);
|
|
210
|
-
setError(null);
|
|
211
|
-
|
|
212
|
-
try {
|
|
213
|
-
// Pass null as prevState - this wrapper manages state internally
|
|
214
|
-
const result = await loader.action(null, formData);
|
|
215
|
-
setFetchedData(result);
|
|
216
|
-
} catch (e) {
|
|
217
|
-
const err = e instanceof Error ? e : new Error(String(e));
|
|
218
|
-
setError(err);
|
|
219
418
|
if (throwOnError) {
|
|
220
419
|
throw err;
|
|
221
420
|
}
|
|
421
|
+
return dataRef.current as T;
|
|
222
422
|
} finally {
|
|
223
|
-
|
|
423
|
+
if (shared) {
|
|
424
|
+
loaderStore.setLoading(bucket, sharedRequestId, false);
|
|
425
|
+
}
|
|
224
426
|
}
|
|
225
427
|
},
|
|
226
|
-
[throwOnError]
|
|
428
|
+
[throwOnError],
|
|
227
429
|
);
|
|
228
430
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
431
|
+
if (throwOnError) {
|
|
432
|
+
if (localError) throw localError;
|
|
433
|
+
if (
|
|
434
|
+
sharedSnapshot.error &&
|
|
435
|
+
lastSharedRequestIdRef.current !== null &&
|
|
436
|
+
sharedSnapshot.requestId === lastSharedRequestIdRef.current
|
|
437
|
+
) {
|
|
438
|
+
throw sharedSnapshot.error;
|
|
439
|
+
}
|
|
237
440
|
}
|
|
238
441
|
|
|
239
442
|
return {
|
|
240
443
|
data,
|
|
241
444
|
isLoading,
|
|
242
445
|
error,
|
|
243
|
-
load
|
|
244
|
-
refetch:
|
|
446
|
+
load,
|
|
447
|
+
refetch: load,
|
|
245
448
|
};
|
|
246
449
|
}
|
|
247
450
|
|
|
@@ -276,8 +479,8 @@ function useLoaderInternal<T>(
|
|
|
276
479
|
*/
|
|
277
480
|
export function useLoader<T>(
|
|
278
481
|
loader: LoaderDefinition<T>,
|
|
279
|
-
options?: UseLoaderOptions
|
|
280
|
-
): UseLoaderResult<T
|
|
482
|
+
options?: UseLoaderOptions,
|
|
483
|
+
): UseLoaderResult<Rango.FlightSerialize<T>> {
|
|
281
484
|
const result = useLoaderInternal(loader, options);
|
|
282
485
|
|
|
283
486
|
// Strict mode: throw if data is not in context
|
|
@@ -285,11 +488,11 @@ export function useLoader<T>(
|
|
|
285
488
|
throw new Error(
|
|
286
489
|
`useLoader: Loader "${loader.$$id}" data not found in context. ` +
|
|
287
490
|
`Make sure the loader is registered on the route with loader(). ` +
|
|
288
|
-
`If you need on-demand fetching, use useFetchLoader() instead
|
|
491
|
+
`If you need on-demand fetching, use useFetchLoader() instead.`,
|
|
289
492
|
);
|
|
290
493
|
}
|
|
291
494
|
|
|
292
|
-
return result as UseLoaderResult<T
|
|
495
|
+
return result as UseLoaderResult<Rango.FlightSerialize<T>>;
|
|
293
496
|
}
|
|
294
497
|
|
|
295
498
|
/**
|
|
@@ -340,7 +543,69 @@ export function useLoader<T>(
|
|
|
340
543
|
*/
|
|
341
544
|
export function useFetchLoader<T>(
|
|
342
545
|
loader: LoaderDefinition<T>,
|
|
343
|
-
options?: UseLoaderOptions
|
|
344
|
-
): UseFetchLoaderResult<T
|
|
345
|
-
return useLoaderInternal(loader, options)
|
|
546
|
+
options?: UseLoaderOptions,
|
|
547
|
+
): UseFetchLoaderResult<Rango.FlightSerialize<T>> {
|
|
548
|
+
return useLoaderInternal(loader, options) as UseFetchLoaderResult<
|
|
549
|
+
Rango.FlightSerialize<T>
|
|
550
|
+
>;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Get a stable function that refreshes loaders by cross-loader group tag.
|
|
555
|
+
*
|
|
556
|
+
* The returned `refresh(groups)` takes one group name or an array of names and
|
|
557
|
+
* re-runs every currently-mounted read tagged with ANY of them, with a plain GET
|
|
558
|
+
* against the current route URL. This is the cross-loader counterpart to the
|
|
559
|
+
* single-loader `key`: use it to refresh a set of DIFFERENT loaders together
|
|
560
|
+
* (e.g. profile + orders after an account switch). Members are tagged via
|
|
561
|
+
* `useLoader(Loader, { refreshGroup })` / `useFetchLoader(Loader, { refreshGroup })`,
|
|
562
|
+
* where `refreshGroup` is one name or several.
|
|
563
|
+
*
|
|
564
|
+
* Passing the group(s) to the returned function rather than to the hook lets a
|
|
565
|
+
* single `useRefreshLoaders()` instance refresh different groups depending on
|
|
566
|
+
* context, and lets one call refresh several groups at once — their members are
|
|
567
|
+
* unioned and deduped, so a loader tagged into two of the named groups is fetched
|
|
568
|
+
* exactly once.
|
|
569
|
+
*
|
|
570
|
+
* Group refresh never render-throws: a failing member surfaces its error via
|
|
571
|
+
* that read's `error` state, and the returned promise rejects with an
|
|
572
|
+
* `AggregateError` of the failures so the caller can handle them at the await
|
|
573
|
+
* site. Each loader is refreshed in place — no params, no body, no mutations.
|
|
574
|
+
*
|
|
575
|
+
* @example
|
|
576
|
+
* ```tsx
|
|
577
|
+
* "use client";
|
|
578
|
+
* import { useLoader, useRefreshLoaders } from "rsc-router/client";
|
|
579
|
+
*
|
|
580
|
+
* function Profile() {
|
|
581
|
+
* const { data } = useLoader(ProfileLoader, { key: userId, refreshGroup: "account" });
|
|
582
|
+
* return <span>{data.name}</span>;
|
|
583
|
+
* }
|
|
584
|
+
* function Orders() {
|
|
585
|
+
* // Tagged into two groups: refreshed by "account" (the whole set) or "orders".
|
|
586
|
+
* const { data } = useLoader(OrdersLoader, {
|
|
587
|
+
* key: userId,
|
|
588
|
+
* refreshGroup: ["account", "orders"],
|
|
589
|
+
* });
|
|
590
|
+
* return <span>{data.count} orders</span>;
|
|
591
|
+
* }
|
|
592
|
+
* function RefreshButtons() {
|
|
593
|
+
* const refresh = useRefreshLoaders();
|
|
594
|
+
* return (
|
|
595
|
+
* <>
|
|
596
|
+
* <button onClick={() => refresh("account")}>Refresh account</button>
|
|
597
|
+
* <button onClick={() => refresh("orders")}>Refresh orders</button>
|
|
598
|
+
* <button onClick={() => refresh(["account", "orders"])}>Refresh both</button>
|
|
599
|
+
* </>
|
|
600
|
+
* );
|
|
601
|
+
* }
|
|
602
|
+
* ```
|
|
603
|
+
*/
|
|
604
|
+
export function useRefreshLoaders(): (
|
|
605
|
+
groups: string | string[],
|
|
606
|
+
) => Promise<void> {
|
|
607
|
+
return useCallback(
|
|
608
|
+
(groups: string | string[]) => loaderStore.refreshGroups(groups),
|
|
609
|
+
[],
|
|
610
|
+
);
|
|
346
611
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel preset integration surface for @rangojs/router.
|
|
3
|
+
*
|
|
4
|
+
* Imported via `@rangojs/router/vercel`. Vercel-only helpers live here so
|
|
5
|
+
* Node/other-platform consumers never pull them in. The Vercel cache store
|
|
6
|
+
* (`VercelCacheStore`) is exported from `@rangojs/router/cache`, mirroring the
|
|
7
|
+
* Cloudflare split where the store lives in `/cache` and the tracing helper in
|
|
8
|
+
* the platform subpath.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { createVercelTracing, type VercelTracingOptions } from "./tracing.js";
|