@rangojs/router 0.0.0-experimental.19 → 0.0.0-experimental.1c0bdfad
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 +291 -61
- package/dist/bin/rango.js +544 -143
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3744 -1329
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +67 -13
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +312 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +247 -23
- package/skills/caching/SKILL.md +322 -19
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +4 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +282 -60
- package/skills/host-router/SKILL.md +278 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +50 -6
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +297 -31
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +771 -0
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +203 -7
- package/skills/prerender/SKILL.md +155 -111
- package/skills/rango/SKILL.md +251 -23
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +123 -48
- package/skills/route/SKILL.md +104 -9
- package/skills/router-setup/SKILL.md +124 -11
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +125 -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 +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 +123 -0
- package/skills/typesafety/SKILL.md +357 -52
- package/skills/use-cache/SKILL.md +46 -14
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +67 -40
- package/src/bin/rango.ts +18 -0
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +197 -150
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +200 -30
- package/src/browser/navigation-client.ts +217 -58
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +18 -66
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +187 -112
- package/src/browser/prefetch/cache.ts +312 -30
- package/src/browser/prefetch/fetch.ts +344 -47
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +126 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +125 -18
- package/src/browser/react/NavigationProvider.tsx +135 -120
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +23 -69
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +32 -7
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +80 -5
- package/src/browser/rsc-router.tsx +226 -75
- package/src/browser/scroll-restoration.ts +54 -42
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +619 -442
- package/src/browser/types.ts +115 -11
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +7 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +182 -37
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +125 -24
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +392 -106
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +29 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +214 -48
- package/src/cache/cache-scope.ts +236 -89
- 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 +2224 -171
- 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 +11 -17
- package/src/cache/document-cache.ts +89 -27
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +11 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +31 -31
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +9 -17
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +55 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +45 -21
- package/src/client.tsx +120 -336
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +84 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +56 -19
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +95 -0
- package/src/handles/is-thenable.ts +19 -0
- package/src/handles/meta.ts +51 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -5
- package/src/host/pattern-matcher.ts +23 -52
- 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 +79 -29
- package/src/index.ts +149 -65
- package/src/internal-debug.ts +11 -10
- 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/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +63 -26
- package/src/prerender.ts +198 -82
- 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 +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +469 -276
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +113 -37
- package/src/route-definition/index.ts +3 -3
- package/src/route-definition/redirect.ts +53 -12
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -17
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +164 -17
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +45 -22
- package/src/router/handler-context.ts +110 -39
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +50 -24
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +274 -56
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +49 -45
- package/src/router/match-api.ts +121 -205
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +33 -6
- package/src/router/match-middleware/cache-lookup.ts +214 -263
- package/src/router/match-middleware/cache-store.ts +73 -33
- package/src/router/match-middleware/intercept-resolution.ts +8 -28
- package/src/router/match-middleware/segment-resolution.ts +52 -18
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +104 -49
- package/src/router/metrics.ts +217 -26
- package/src/router/middleware-types.ts +24 -110
- package/src/router/middleware.ts +384 -197
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +148 -91
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prerender-match.ts +199 -56
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +144 -74
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +8 -28
- package/src/router/router-interfaces.ts +129 -36
- package/src/router/router-options.ts +185 -23
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +281 -76
- package/src/router/segment-resolution/helpers.ts +116 -31
- package/src/router/segment-resolution/loader-cache.ts +63 -37
- package/src/router/segment-resolution/revalidation.ts +493 -391
- 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 +5 -1
- package/src/router/segment-wrappers.ts +8 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +161 -199
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +180 -58
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +182 -54
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +702 -460
- package/src/rsc/helpers.ts +168 -46
- package/src/rsc/index.ts +2 -25
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +127 -31
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +98 -19
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +99 -189
- package/src/rsc/rsc-rendering.ts +126 -106
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +269 -114
- package/src/rsc/ssr-setup.ts +144 -0
- package/src/rsc/types.ts +34 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +49 -41
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +281 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +317 -63
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +40 -38
- package/src/server/loader-registry.ts +26 -46
- package/src/server/request-context.ts +425 -177
- package/src/server.ts +6 -0
- package/src/ssr/index.tsx +25 -16
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +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 +183 -0
- package/src/testing/index.ts +99 -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 +343 -0
- package/src/testing/render-route.tsx +581 -0
- package/src/testing/run-loader.ts +385 -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 +20 -58
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +3 -19
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +236 -88
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +10 -45
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -19
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +64 -91
- package/src/vite/discovery/discover-routers.ts +147 -88
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +247 -145
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +61 -13
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +10 -3
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +155 -65
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- package/src/vite/plugins/client-ref-dedup.ts +120 -0
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/plugins/expose-action-id.ts +49 -98
- package/src/vite/plugins/expose-id-utils.ts +96 -51
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +553 -317
- 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 +73 -83
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +46 -37
- package/src/vite/plugins/virtual-entries.ts +13 -18
- package/src/vite/rango.ts +241 -287
- package/src/vite/router-discovery.ts +956 -149
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +141 -34
- package/src/vite/utils/shared-utils.ts +92 -42
- package/CLAUDE.md +0 -5
- 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 -6
- package/src/network-error-thrower.tsx +0 -23
- package/src/route-definition/route-function.ts +0 -119
- package/src/router/middleware-cookies.ts +0 -55
package/src/rsc/types.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { ResolvedSegment, SlotState } from "../types.js";
|
|
9
9
|
import type { HandleData } from "../server/handle-store.js";
|
|
10
|
-
import type {
|
|
10
|
+
import type { RangoInternal } from "../router/router-interfaces.js";
|
|
11
11
|
import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -19,10 +19,19 @@ export interface RscPayload {
|
|
|
19
19
|
metadata?: {
|
|
20
20
|
pathname: string;
|
|
21
21
|
segments: ResolvedSegment[];
|
|
22
|
+
/** Router instance ID. When this changes between navigations, the client
|
|
23
|
+
* discards cached segments and does a full tree replacement (app switch). */
|
|
24
|
+
routerId?: string;
|
|
22
25
|
isPartial?: boolean;
|
|
23
26
|
isError?: boolean;
|
|
24
27
|
matched?: string[];
|
|
25
28
|
diff?: string[];
|
|
29
|
+
/**
|
|
30
|
+
* All segment ids re-resolved on the server, including null-component
|
|
31
|
+
* ones excluded from `segments`/`diff`. Drives client-side handle-bucket
|
|
32
|
+
* cleanup. Superset of `diff`. See MatchResult.resolvedIds.
|
|
33
|
+
*/
|
|
34
|
+
resolvedIds?: string[];
|
|
26
35
|
/** Merged route params from the matched route */
|
|
27
36
|
params?: Record<string, string>;
|
|
28
37
|
slots?: Record<string, SlotState>;
|
|
@@ -32,19 +41,35 @@ export interface RscPayload {
|
|
|
32
41
|
handles?: AsyncGenerator<HandleData, void, unknown>;
|
|
33
42
|
/** RSC version string for cache invalidation */
|
|
34
43
|
version?: string;
|
|
44
|
+
/** TTL in milliseconds for the client-side in-memory prefetch cache */
|
|
45
|
+
prefetchCacheTTL?: number;
|
|
46
|
+
/** Server-resolved rango state cookie name; the client reads it verbatim. */
|
|
47
|
+
stateCookieName?: string;
|
|
35
48
|
/** Theme configuration for FOUC prevention */
|
|
36
49
|
themeConfig?: ResolvedThemeConfig | null;
|
|
37
50
|
/** Initial theme from cookie (for SSR hydration) */
|
|
38
51
|
initialTheme?: Theme;
|
|
52
|
+
/** URL prefix for all routes (from createRouter({ basename })). */
|
|
53
|
+
basename?: string;
|
|
39
54
|
/** Whether connection warmup is enabled */
|
|
40
55
|
warmupEnabled?: boolean;
|
|
41
|
-
/**
|
|
42
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Whether the client should hydrate inside React.StrictMode. Carried on
|
|
58
|
+
* the initial full-render payload only; the browser entry reads it once at
|
|
59
|
+
* hydration. Absent on partial (navigation) payloads. Defaults to true on
|
|
60
|
+
* the client when omitted.
|
|
61
|
+
*/
|
|
62
|
+
strictMode?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Server-side redirect with optional state (for partial requests).
|
|
65
|
+
* `external: true` (from redirect(url, { external: true })) tells the client
|
|
66
|
+
* to hard-navigate to an off-host target instead of validating same-origin.
|
|
67
|
+
*/
|
|
68
|
+
redirect?: { url: string; external?: boolean };
|
|
43
69
|
/** Server-set location state to include in history.pushState */
|
|
44
70
|
locationState?: Record<string, unknown>;
|
|
45
71
|
};
|
|
46
72
|
returnValue?: { ok: boolean; data: unknown };
|
|
47
|
-
formState?: unknown;
|
|
48
73
|
}
|
|
49
74
|
|
|
50
75
|
/**
|
|
@@ -61,7 +86,10 @@ export interface RSCDependencies {
|
|
|
61
86
|
*/
|
|
62
87
|
renderToReadableStream: <T>(
|
|
63
88
|
payload: T,
|
|
64
|
-
options?: {
|
|
89
|
+
options?: {
|
|
90
|
+
temporaryReferences?: unknown;
|
|
91
|
+
onError?: (error: unknown) => string | void;
|
|
92
|
+
},
|
|
65
93
|
) => ReadableStream<Uint8Array>;
|
|
66
94
|
|
|
67
95
|
/**
|
|
@@ -169,7 +197,7 @@ export interface CreateRSCHandlerOptions<
|
|
|
169
197
|
/**
|
|
170
198
|
* The RSC router instance
|
|
171
199
|
*/
|
|
172
|
-
router:
|
|
200
|
+
router: RangoInternal<TEnv, TRoutes>;
|
|
173
201
|
|
|
174
202
|
/**
|
|
175
203
|
* RSC dependencies from @vitejs/plugin-rsc/rsc.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Runtime-safe detection of a test runner (Vitest), used to decide whether a
|
|
2
|
+
// create*() call with no plugin-injected $$id may fall back to a synthetic id (a
|
|
3
|
+
// bare test) or must fail loud (dev / a real build).
|
|
4
|
+
//
|
|
5
|
+
// `process` is absent in some target runtimes (the browser, certain edge/worker
|
|
6
|
+
// RSC environments), so probe it through `globalThis` with optional chaining —
|
|
7
|
+
// NEVER a bare `process.env.VITEST`, which would ReferenceError before the
|
|
8
|
+
// intended error is thrown. Unlike `process.env.NODE_ENV` (folded by the app's
|
|
9
|
+
// build `define`), `VITEST` is not folded, so this stays a small runtime check;
|
|
10
|
+
// it lives only on the create*() error path (id missing), which never runs in a
|
|
11
|
+
// correct production build.
|
|
12
|
+
//
|
|
13
|
+
// Vitest sets `VITEST` in every test process — the node project and the
|
|
14
|
+
// react-server forks alike (the RSC project forces NODE_ENV=production, so NODE_ENV
|
|
15
|
+
// cannot distinguish it from a real build; `VITEST` can). A real build never sets it.
|
|
16
|
+
export function isUnderTestRunner(): boolean {
|
|
17
|
+
return !!globalThis.process?.env?.VITEST;
|
|
18
|
+
}
|
package/src/search-params.ts
CHANGED
|
@@ -7,9 +7,16 @@
|
|
|
7
7
|
* URLSearchParams instance.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
import { encodeKV } from "./encode-kv.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Decimal-number grammar for `"number"` search params: optional sign, digits
|
|
14
|
+
* with optional fraction, optional exponent. Deliberately excludes hex (`0x`),
|
|
15
|
+
* `Infinity`, and empty/whitespace so `Number()`'s lenient coercions
|
|
16
|
+
* (`Number("")===0`, `Number("0x10")===16`, `Number("Infinity")===Infinity`)
|
|
17
|
+
* do not slip non-decimal values into typed search.
|
|
18
|
+
*/
|
|
19
|
+
const DECIMAL_NUMBER_RE = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
13
20
|
|
|
14
21
|
/** Supported scalar types for search params (append ? for optional). */
|
|
15
22
|
export type SearchSchemaValue =
|
|
@@ -23,10 +30,6 @@ export type SearchSchemaValue =
|
|
|
23
30
|
/** A search schema maps param names to their type descriptors. */
|
|
24
31
|
export type SearchSchema = Record<string, SearchSchemaValue>;
|
|
25
32
|
|
|
26
|
-
// ============================================================================
|
|
27
|
-
// Type-Level Schema Resolution
|
|
28
|
-
// ============================================================================
|
|
29
|
-
|
|
30
33
|
/** Strip trailing `?` from a schema value to get the base type. */
|
|
31
34
|
type BaseType<T extends string> = T extends `${infer B}?` ? B : T;
|
|
32
35
|
|
|
@@ -55,14 +58,22 @@ type Simplify<T> = { [K in keyof T]: T[K] };
|
|
|
55
58
|
/**
|
|
56
59
|
* Resolve a SearchSchema to its typed object.
|
|
57
60
|
*
|
|
61
|
+
* Both required and optional params resolve to `T | undefined` at the handler
|
|
62
|
+
* level. The required/optional distinction is a consumer-facing contract
|
|
63
|
+
* (e.g., for href() and reverse() autocomplete) — it tells callers which
|
|
64
|
+
* params the route expects, but the handler must still check for undefined
|
|
65
|
+
* since the framework cannot trust the client to send all required params.
|
|
66
|
+
*
|
|
58
67
|
* @example
|
|
59
68
|
* type S = { q: "string"; page: "number?"; sort: "string?" };
|
|
60
69
|
* type R = ResolveSearchSchema<S>;
|
|
61
|
-
* // { q: string; page?: number; sort?: string }
|
|
70
|
+
* // { q: string | undefined; page?: number; sort?: string }
|
|
62
71
|
*/
|
|
63
72
|
export type ResolveSearchSchema<T extends SearchSchema> = Simplify<
|
|
64
73
|
{
|
|
65
|
-
[K in RequiredKeys<T> & string]:
|
|
74
|
+
[K in RequiredKeys<T> & string]:
|
|
75
|
+
| ResolveBaseType<BaseType<T[K]>>
|
|
76
|
+
| undefined;
|
|
66
77
|
} & {
|
|
67
78
|
[K in OptionalKeys<T> & string]?: ResolveBaseType<BaseType<T[K]>>;
|
|
68
79
|
}
|
|
@@ -73,11 +84,11 @@ export type ResolveSearchSchema<T extends SearchSchema> = Simplify<
|
|
|
73
84
|
// ============================================================================
|
|
74
85
|
|
|
75
86
|
/** Resolve the global route map from RegisteredRoutes or GeneratedRouteMap. */
|
|
76
|
-
type GlobalRouteMap = keyof
|
|
77
|
-
? keyof
|
|
87
|
+
type GlobalRouteMap = keyof Rango.RegisteredRoutes extends never
|
|
88
|
+
? keyof Rango.GeneratedRouteMap extends never
|
|
78
89
|
? Record<string, string>
|
|
79
|
-
:
|
|
80
|
-
:
|
|
90
|
+
: Rango.GeneratedRouteMap
|
|
91
|
+
: Rango.RegisteredRoutes;
|
|
81
92
|
|
|
82
93
|
/**
|
|
83
94
|
* Extract the resolved search params type for a named route.
|
|
@@ -155,18 +166,20 @@ type ExtractParamsFromPattern<T extends string> =
|
|
|
155
166
|
: { [K in Param]: string }
|
|
156
167
|
: {};
|
|
157
168
|
|
|
158
|
-
// ============================================================================
|
|
159
|
-
// Runtime Parser
|
|
160
|
-
// ============================================================================
|
|
161
|
-
|
|
162
169
|
/**
|
|
163
170
|
* Parse URLSearchParams into a typed object using the given schema.
|
|
164
171
|
*
|
|
165
172
|
* - `"string"` / `"string?"` - kept as-is
|
|
166
|
-
* - `"number"` / `"number?"` -
|
|
173
|
+
* - `"number"` / `"number?"` - parsed as a finite decimal number. Accepts an
|
|
174
|
+
* optional sign, digits with optional fraction, and optional exponent
|
|
175
|
+
* (e.g. `42`, `-3.5`, `1e3`). Empty/whitespace-only, non-decimal forms
|
|
176
|
+
* (`0x10`), and non-finite (`Infinity`) are treated as missing (omitted),
|
|
177
|
+
* NOT coerced to `0`/`16`/`Infinity`.
|
|
167
178
|
* - `"boolean"` / `"boolean?"` - `"true"` / `"1"` -> true, `"false"` / `"0"` / `""` -> false
|
|
168
179
|
*
|
|
169
|
-
* Missing
|
|
180
|
+
* Missing params (both required and optional) are omitted from the result
|
|
181
|
+
* (undefined). The required/optional distinction is a consumer-facing contract
|
|
182
|
+
* only — the handler must check for undefined.
|
|
170
183
|
*/
|
|
171
184
|
export function parseSearchParams<T extends SearchSchema>(
|
|
172
185
|
searchParams: URLSearchParams,
|
|
@@ -180,25 +193,24 @@ export function parseSearchParams<T extends SearchSchema>(
|
|
|
180
193
|
const raw = searchParams.get(key);
|
|
181
194
|
|
|
182
195
|
if (raw === null) {
|
|
183
|
-
|
|
184
|
-
// Required param missing: use zero value
|
|
185
|
-
if (baseType === "string") result[key] = "";
|
|
186
|
-
else if (baseType === "number") result[key] = 0;
|
|
187
|
-
else if (baseType === "boolean") result[key] = false;
|
|
188
|
-
}
|
|
189
|
-
// Optional params are omitted (undefined)
|
|
196
|
+
// Missing params are omitted (undefined) regardless of required/optional
|
|
190
197
|
continue;
|
|
191
198
|
}
|
|
192
199
|
|
|
193
200
|
if (baseType === "string") {
|
|
194
201
|
result[key] = raw;
|
|
195
202
|
} else if (baseType === "number") {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
203
|
+
// Trim, then require a valid decimal numeral and a finite result.
|
|
204
|
+
// Empty/whitespace, hex (0x10), and Infinity are treated as missing
|
|
205
|
+
// (omitted) — not coerced to 0/16/Infinity by Number()'s lenient rules.
|
|
206
|
+
const trimmed = raw.trim();
|
|
207
|
+
if (trimmed !== "" && DECIMAL_NUMBER_RE.test(trimmed)) {
|
|
208
|
+
const num = Number(trimmed);
|
|
209
|
+
if (Number.isFinite(num)) {
|
|
210
|
+
result[key] = num;
|
|
211
|
+
}
|
|
201
212
|
}
|
|
213
|
+
// Anything else treated as missing (undefined)
|
|
202
214
|
} else if (baseType === "boolean") {
|
|
203
215
|
result[key] = raw === "true" || raw === "1";
|
|
204
216
|
}
|
|
@@ -207,21 +219,17 @@ export function parseSearchParams<T extends SearchSchema>(
|
|
|
207
219
|
return result as ResolveSearchSchema<T>;
|
|
208
220
|
}
|
|
209
221
|
|
|
210
|
-
// ============================================================================
|
|
211
|
-
// Runtime Serializer
|
|
212
|
-
// ============================================================================
|
|
213
|
-
|
|
214
222
|
/**
|
|
215
223
|
* Serialize a typed search params object to a query string (without leading `?`).
|
|
216
|
-
* Skips `undefined` and `null` values.
|
|
224
|
+
* Skips `undefined` and `null` values. Preserves insertion order (no sort).
|
|
217
225
|
*/
|
|
218
226
|
export function serializeSearchParams(params: Record<string, unknown>): string {
|
|
219
|
-
|
|
227
|
+
// Pre-filter null/undefined and coerce values to strings here so encodeKV
|
|
228
|
+
// (which never inspects values) reproduces this call site's exact output.
|
|
229
|
+
const pairs: [string, string][] = [];
|
|
220
230
|
for (const [key, value] of Object.entries(params)) {
|
|
221
231
|
if (value === undefined || value === null) continue;
|
|
222
|
-
|
|
223
|
-
`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`,
|
|
224
|
-
);
|
|
232
|
+
pairs.push([key, String(value)]);
|
|
225
233
|
}
|
|
226
|
-
return
|
|
234
|
+
return encodeKV(pairs);
|
|
227
235
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stable Promise wrappers keyed on the component itself. Objects (React
|
|
5
|
+
* elements, functions, lazy payloads) land in a WeakMap so entries GC when
|
|
6
|
+
* the underlying component is released; primitives (string, number, boolean,
|
|
7
|
+
* null) land in a Map so memoization still applies to text-/null-backed
|
|
8
|
+
* segments like those in partial-update flows. Keeping this cache outside
|
|
9
|
+
* the segment eliminates preservation fields on ResolvedSegment — it survives
|
|
10
|
+
* reconciliation naturally because the component ref is what's stable.
|
|
11
|
+
*
|
|
12
|
+
* Browser-only. On the server each SSR render needs a fresh pending promise
|
|
13
|
+
* so Suspense can emit the loading fallback HTML before content streams. A
|
|
14
|
+
* shared already-resolved promise has `.status === "fulfilled"` attached by
|
|
15
|
+
* React on its first observation — subsequent `use()` calls return
|
|
16
|
+
* synchronously without suspending, so the Suspense fallback never makes it
|
|
17
|
+
* into the initial HTML. Route-definition components share refs across
|
|
18
|
+
* requests, so a global cache would leak tracked state between renders.
|
|
19
|
+
*/
|
|
20
|
+
const IS_BROWSER = typeof window !== "undefined";
|
|
21
|
+
const objectContentCache = IS_BROWSER
|
|
22
|
+
? new WeakMap<object, Promise<ReactNode>>()
|
|
23
|
+
: null;
|
|
24
|
+
const primitiveContentCache = IS_BROWSER
|
|
25
|
+
? new Map<unknown, Promise<ReactNode>>()
|
|
26
|
+
: null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Return a stable Promise wrapping `component`, memoized on the component ref.
|
|
30
|
+
*
|
|
31
|
+
* A fresh `Promise.resolve(component)` each render would suspend for one
|
|
32
|
+
* microtask and briefly commit the loading fallback inside Suspender — the
|
|
33
|
+
* intercept / parallel-slot flicker this indirection prevents. Reusing the
|
|
34
|
+
* same Promise ref keeps React's `use()` in "known fulfilled" state after
|
|
35
|
+
* the first observation.
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export function getMemoizedContentPromise(
|
|
40
|
+
component: ReactNode,
|
|
41
|
+
): Promise<ReactNode> {
|
|
42
|
+
if (component instanceof Promise) {
|
|
43
|
+
return component as Promise<ReactNode>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!objectContentCache || !primitiveContentCache) {
|
|
47
|
+
return Promise.resolve(component);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (component !== null && typeof component === "object") {
|
|
51
|
+
const cached = objectContentCache.get(component);
|
|
52
|
+
if (cached) {
|
|
53
|
+
return cached;
|
|
54
|
+
}
|
|
55
|
+
const promise = Promise.resolve(component);
|
|
56
|
+
objectContentCache.set(component, promise);
|
|
57
|
+
return promise;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const cached = primitiveContentCache.get(component);
|
|
61
|
+
if (cached) {
|
|
62
|
+
return cached;
|
|
63
|
+
}
|
|
64
|
+
const promise = Promise.resolve(component);
|
|
65
|
+
primitiveContentCache.set(component, promise);
|
|
66
|
+
return promise;
|
|
67
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { ResolvedSegment } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cache of aggregate Promise.all results keyed on the first loader's
|
|
5
|
+
* `loaderData` reference. Each entry holds the source refs it was built from
|
|
6
|
+
* plus the resulting Promise/array; lookup scans entries for the matching
|
|
7
|
+
* source array (typically a single entry, since distinct loader groups rarely
|
|
8
|
+
* share a first source). Object first-refs live in a WeakMap (auto-GC);
|
|
9
|
+
* primitive first-refs (strings/numbers/booleans/null) live in a Map so
|
|
10
|
+
* loaders that resolve to primitive data are memoized too. The per-key array
|
|
11
|
+
* is capped (MAX_ENTRIES_PER_KEY, oldest evicted) so a long session under a
|
|
12
|
+
* stable first-ref does not grow it without bound.
|
|
13
|
+
*
|
|
14
|
+
* Keying externally means reconciliation's fresh segment objects no longer
|
|
15
|
+
* drop memoization — the cache survives as long as the underlying loader
|
|
16
|
+
* segments do, and GC collects entries when those loaders are released
|
|
17
|
+
* (object keys only).
|
|
18
|
+
*
|
|
19
|
+
* Browser-only. On the server each SSR render needs a fresh Promise so
|
|
20
|
+
* Suspense can actually suspend and emit the loading fallback HTML before
|
|
21
|
+
* content streams. A shared already-resolved promise has `.status` attached
|
|
22
|
+
* by React on first `use()`; subsequent observations return synchronously
|
|
23
|
+
* and skip the fallback. The zero-loader case is especially prone because
|
|
24
|
+
* every empty-loader site would otherwise share one promise across requests.
|
|
25
|
+
*/
|
|
26
|
+
const IS_BROWSER = typeof window !== "undefined";
|
|
27
|
+
|
|
28
|
+
interface LoaderCacheEntry {
|
|
29
|
+
sources: any[];
|
|
30
|
+
// buildLoaderPromise always returns a Promise, so the cached value is never a
|
|
31
|
+
// bare array. The public getMemoizedLoaderPromise return type stays broader
|
|
32
|
+
// (Promise<any[]> | any[]) to mirror its siblings.
|
|
33
|
+
promise: Promise<any[]>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Cap the per-key entries array. A stable first-ref (e.g. a layout loader whose
|
|
37
|
+
// loaderData object survives reconciliation across navigations) keeps its
|
|
38
|
+
// WeakMap/Map key alive, while a per-route loader whose ref changes each
|
|
39
|
+
// navigation appends a brand-new sources array under that same live key on
|
|
40
|
+
// every navigation. Nothing was ever removed, so the array grew linearly with
|
|
41
|
+
// navigation count, pinning each stale Promise + sources array from GC — a
|
|
42
|
+
// steady client-side leak over a long session. Only the current render's combo
|
|
43
|
+
// needs to stay warm; evict the oldest beyond the cap.
|
|
44
|
+
const MAX_ENTRIES_PER_KEY = 8;
|
|
45
|
+
|
|
46
|
+
const objectLoaderCache = IS_BROWSER
|
|
47
|
+
? new WeakMap<object, LoaderCacheEntry[]>()
|
|
48
|
+
: null;
|
|
49
|
+
const primitiveLoaderCache = IS_BROWSER
|
|
50
|
+
? new Map<unknown, LoaderCacheEntry[]>()
|
|
51
|
+
: null;
|
|
52
|
+
|
|
53
|
+
// In the browser, a single shared empty aggregate is safe (and desirable) —
|
|
54
|
+
// reusing the same resolved promise keeps React's `use()` in a known-fulfilled
|
|
55
|
+
// state across renders. On the server it would leak `.status = "fulfilled"`
|
|
56
|
+
// across requests and skip the Suspense fallback, so we rebuild on each call.
|
|
57
|
+
const SHARED_EMPTY_LOADER_PROMISE: Promise<any[]> | null = IS_BROWSER
|
|
58
|
+
? Promise.resolve([])
|
|
59
|
+
: null;
|
|
60
|
+
|
|
61
|
+
function hasSameReferences(a: any[], b: any[]): boolean {
|
|
62
|
+
if (a.length !== b.length) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
for (let i = 0; i < a.length; i++) {
|
|
66
|
+
if (a[i] !== b[i]) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Build a fresh aggregate Promise.all over the loaders' resolved data refs.
|
|
75
|
+
* Unlike getMemoizedLoaderPromise this never caches, so each call yields a new
|
|
76
|
+
* Promise — correct for sites that await the result immediately (a shared,
|
|
77
|
+
* already-resolved promise would leak React's `.status` across server requests
|
|
78
|
+
* and skip the Suspense fallback).
|
|
79
|
+
*
|
|
80
|
+
* @internal
|
|
81
|
+
*/
|
|
82
|
+
export function buildLoaderPromise(loaders: ResolvedSegment[]): Promise<any[]> {
|
|
83
|
+
if (loaders.length === 0) {
|
|
84
|
+
return Promise.resolve([]);
|
|
85
|
+
}
|
|
86
|
+
return Promise.all(
|
|
87
|
+
loaders.map((loader) =>
|
|
88
|
+
loader.loaderData instanceof Promise
|
|
89
|
+
? loader.loaderData
|
|
90
|
+
: Promise.resolve(loader.loaderData),
|
|
91
|
+
),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isObjectLike(value: unknown): value is object {
|
|
96
|
+
return (
|
|
97
|
+
value !== null && (typeof value === "object" || typeof value === "function")
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Memoize an aggregate Promise.all for a set of loader segments. Reusing the
|
|
103
|
+
* same aggregate across renders — invalidated only when any underlying
|
|
104
|
+
* loader.loaderData ref changes — keeps React's `use()` in "known fulfilled"
|
|
105
|
+
* state and prevents a fresh Promise.all from suspending (and briefly
|
|
106
|
+
* committing the Suspense fallback) on every partial update that doesn't
|
|
107
|
+
* actually change loader data.
|
|
108
|
+
*
|
|
109
|
+
* @internal
|
|
110
|
+
*/
|
|
111
|
+
export function getMemoizedLoaderPromise(
|
|
112
|
+
loaders: ResolvedSegment[],
|
|
113
|
+
): Promise<any[]> | any[] {
|
|
114
|
+
if (loaders.length === 0) {
|
|
115
|
+
return SHARED_EMPTY_LOADER_PROMISE ?? buildLoaderPromise(loaders);
|
|
116
|
+
}
|
|
117
|
+
if (!objectLoaderCache || !primitiveLoaderCache) {
|
|
118
|
+
return buildLoaderPromise(loaders);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const sources = loaders.map((loader) => loader.loaderData);
|
|
122
|
+
const first = sources[0];
|
|
123
|
+
const entries = isObjectLike(first)
|
|
124
|
+
? objectLoaderCache.get(first)
|
|
125
|
+
: primitiveLoaderCache.get(first);
|
|
126
|
+
|
|
127
|
+
if (entries) {
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
if (hasSameReferences(entry.sources, sources)) {
|
|
130
|
+
return entry.promise;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const promise = buildLoaderPromise(loaders);
|
|
136
|
+
const newEntry: LoaderCacheEntry = { sources, promise };
|
|
137
|
+
if (entries) {
|
|
138
|
+
// Bound the array: drop the oldest entry before appending when at the cap.
|
|
139
|
+
if (entries.length >= MAX_ENTRIES_PER_KEY) {
|
|
140
|
+
entries.shift();
|
|
141
|
+
}
|
|
142
|
+
entries.push(newEntry);
|
|
143
|
+
} else if (isObjectLike(first)) {
|
|
144
|
+
objectLoaderCache.set(first, [newEntry]);
|
|
145
|
+
} else {
|
|
146
|
+
primitiveLoaderCache.set(first, [newEntry]);
|
|
147
|
+
}
|
|
148
|
+
return promise;
|
|
149
|
+
}
|