@rangojs/router 0.0.0-experimental.97 → 0.0.0-experimental.98914650
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -9
- package/dist/bin/rango.js +157 -63
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +1584 -639
- package/package.json +71 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +263 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +3 -1
- package/skills/hooks/SKILL.md +235 -28
- package/skills/host-router/SKILL.md +122 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +29 -5
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +173 -17
- package/skills/loader/SKILL.md +170 -23
- package/skills/middleware/SKILL.md +16 -10
- package/skills/migrate-nextjs/SKILL.md +38 -16
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +11 -7
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +250 -25
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +114 -47
- package/skills/route/SKILL.md +42 -5
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +78 -42
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +316 -26
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/vercel/SKILL.md +107 -0
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +37 -143
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +30 -59
- package/src/browser/navigation-client.ts +96 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +32 -82
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +60 -127
- package/src/browser/prefetch/cache.ts +82 -72
- package/src/browser/prefetch/fetch.ts +108 -33
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +41 -48
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +11 -11
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +20 -5
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +70 -34
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +168 -44
- package/src/browser/types.ts +36 -21
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +89 -10
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +122 -22
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +134 -32
- package/src/cache/cache-scope.ts +100 -74
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2255 -238
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +61 -20
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +22 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +25 -61
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +17 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +31 -23
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +63 -9
- package/src/index.ts +64 -9
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +65 -40
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +244 -281
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +40 -17
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -15
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +4 -41
- package/src/router/intercept-resolution.ts +14 -19
- package/src/router/lazy-includes.ts +9 -46
- package/src/router/loader-resolution.ts +91 -46
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +57 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +150 -271
- package/src/router/match-middleware/cache-store.ts +3 -33
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +31 -80
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +5 -112
- package/src/router/middleware.ts +118 -133
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +62 -67
- package/src/router/prerender-match.ts +99 -63
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +50 -56
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +68 -35
- package/src/router/router-options.ts +55 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +44 -63
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +40 -37
- package/src/router/segment-resolution/revalidation.ts +203 -285
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +87 -48
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +80 -41
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +83 -78
- package/src/rsc/helpers.ts +93 -5
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +12 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +76 -62
- package/src/rsc/rsc-rendering.ts +41 -60
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +62 -67
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +10 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +199 -142
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +150 -51
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +165 -87
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +97 -22
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +18 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +19 -5
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +95 -82
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +26 -34
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/state.ts +39 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +185 -10
- package/src/vite/plugins/cjs-to-esm.ts +3 -18
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +12 -11
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +4 -75
- package/src/vite/plugins/expose-id-utils.ts +3 -54
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +57 -67
- package/src/vite/plugins/performance-tracks.ts +9 -16
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +26 -49
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +2 -32
- package/src/vite/plugins/version-plugin.ts +32 -23
- package/src/vite/plugins/virtual-entries.ts +35 -17
- package/src/vite/rango.ts +148 -115
- package/src/vite/router-discovery.ts +220 -68
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -34
- package/src/vite/utils/shared-utils.ts +95 -43
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -61,6 +61,27 @@ export function buildHistoryState(
|
|
|
61
61
|
return Object.keys(result).length > 0 ? result : null;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Stamp an `idx` on the next history entry's state and call push/replaceState.
|
|
66
|
+
* Push increments the current idx; replace keeps it. Initial entry idx is 0.
|
|
67
|
+
* Used by useRouter().back() to detect "first entry in this session" without
|
|
68
|
+
* relying on the Navigation API.
|
|
69
|
+
*/
|
|
70
|
+
export function pushHistoryWithIdx(
|
|
71
|
+
state: Record<string, unknown> | null,
|
|
72
|
+
url: string,
|
|
73
|
+
replace: boolean,
|
|
74
|
+
): void {
|
|
75
|
+
const oldIdx = (window.history.state as { idx?: number } | null)?.idx ?? 0;
|
|
76
|
+
const newIdx = replace ? oldIdx : oldIdx + 1;
|
|
77
|
+
const finalState = { ...(state ?? {}), idx: newIdx };
|
|
78
|
+
if (replace) {
|
|
79
|
+
window.history.replaceState(finalState, "", url);
|
|
80
|
+
} else {
|
|
81
|
+
window.history.pushState(finalState, "", url);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
64
85
|
/**
|
|
65
86
|
* Merge server-set location state into the current history entry.
|
|
66
87
|
* Replaces the current history state and dispatches notification event
|
package/src/browser/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// ============================================================================
|
|
2
|
-
// Browser Module - Browser entry point for
|
|
2
|
+
// Browser Module - Browser entry point for Rango
|
|
3
3
|
// ============================================================================
|
|
4
4
|
//
|
|
5
5
|
// Usage:
|
|
6
|
-
// import { initBrowserApp,
|
|
6
|
+
// import { initBrowserApp, Rango } from "rsc-router/browser";
|
|
7
7
|
//
|
|
8
8
|
// For React components (Link, useNavigation, etc.):
|
|
9
9
|
// import { Link, useNavigation, useAction, href } from "rsc-router/client";
|
|
@@ -13,6 +13,6 @@
|
|
|
13
13
|
// Browser app initialization
|
|
14
14
|
export {
|
|
15
15
|
initBrowserApp,
|
|
16
|
-
|
|
16
|
+
Rango,
|
|
17
17
|
type InitBrowserAppOptions,
|
|
18
18
|
} from "./rsc-router.js";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client seat of `invalidateClientCache()` (the `default` export condition).
|
|
3
|
+
*
|
|
4
|
+
* Makes the current client behave as if a server action had just completed:
|
|
5
|
+
* the history cache is marked stale (SWR), the prefetch map is flushed, the
|
|
6
|
+
* state rotates, and sibling tabs are broadcast to — the same
|
|
7
|
+
* `markCacheAsStaleAndBroadcast()` path the server-action bridge uses. This is
|
|
8
|
+
* the gentler mark-stale (not hard-clear) behavior, so Back renders the cached
|
|
9
|
+
* entry instantly and revalidates.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { getRegisteredStore } from "./navigation-store-handle.js";
|
|
13
|
+
import { clearPrefetchCache } from "./prefetch/cache.js";
|
|
14
|
+
|
|
15
|
+
export function invalidateClientCache(): void {
|
|
16
|
+
if (typeof document === "undefined") {
|
|
17
|
+
// SSR pass of a client component also resolves the default condition. A
|
|
18
|
+
// render-time call must not take down the page; no-op with a dev warning.
|
|
19
|
+
if (process.env.NODE_ENV !== "production") {
|
|
20
|
+
console.warn(
|
|
21
|
+
"[rango] invalidateClientCache() was called during a server render; " +
|
|
22
|
+
"it is a no-op outside the browser.",
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const store = getRegisteredStore();
|
|
29
|
+
if (store) {
|
|
30
|
+
store.markCacheAsStaleAndBroadcast();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Pre-boot: no store registered yet. clearPrefetchCache() (which rotates the
|
|
35
|
+
// state) is complete at this point — there is no history cache to mark and no
|
|
36
|
+
// sibling state worth broadcasting.
|
|
37
|
+
clearPrefetchCache();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Client no-op for `keepClientCache()`. It is a server action directive (the
|
|
42
|
+
* `react-server` condition sets a response header the action bridge reads);
|
|
43
|
+
* there is nothing to suppress from the client side.
|
|
44
|
+
*/
|
|
45
|
+
export function keepClientCache(): void {
|
|
46
|
+
if (process.env.NODE_ENV !== "production") {
|
|
47
|
+
console.warn(
|
|
48
|
+
"[rango] keepClientCache() has no effect on the client; it is a server " +
|
|
49
|
+
"action directive. Call it from inside a server action.",
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -5,15 +5,15 @@ import type {
|
|
|
5
5
|
ResolvedSegment,
|
|
6
6
|
} from "./types.js";
|
|
7
7
|
import { setAppVersion } from "./app-version.js";
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
8
|
+
import { isActionFenceActive } from "./action-fence.js";
|
|
9
|
+
import { getRangoState } from "./rango-state.js";
|
|
10
10
|
import * as React from "react";
|
|
11
11
|
import { startTransition } from "react";
|
|
12
12
|
import {
|
|
13
13
|
createNavigationTransaction,
|
|
14
14
|
resolveNavigationState,
|
|
15
15
|
} from "./navigation-transaction.js";
|
|
16
|
-
import { buildHistoryState } from "./history-state.js";
|
|
16
|
+
import { buildHistoryState, pushHistoryWithIdx } from "./history-state.js";
|
|
17
17
|
import {
|
|
18
18
|
handleNavigationStart,
|
|
19
19
|
handleNavigationEnd,
|
|
@@ -50,13 +50,8 @@ export { createNavigationTransaction };
|
|
|
50
50
|
*/
|
|
51
51
|
export interface NavigationBridgeConfigWithController extends NavigationBridgeConfig {
|
|
52
52
|
eventController: EventController;
|
|
53
|
-
/** RSC version from initial payload metadata
|
|
53
|
+
/** RSC version from initial payload metadata. */
|
|
54
54
|
version?: string;
|
|
55
|
-
/**
|
|
56
|
-
* Live app-shell ref. When supplied, the bridge reads version/basename
|
|
57
|
-
* from this ref so cross-app navigations propagate correctly.
|
|
58
|
-
*/
|
|
59
|
-
appShellRef?: AppShellRef;
|
|
60
55
|
}
|
|
61
56
|
|
|
62
57
|
/**
|
|
@@ -75,46 +70,9 @@ export interface NavigationBridgeConfigWithController extends NavigationBridgeCo
|
|
|
75
70
|
export function createNavigationBridge(
|
|
76
71
|
config: NavigationBridgeConfigWithController,
|
|
77
72
|
): NavigationBridge {
|
|
78
|
-
const {
|
|
79
|
-
store,
|
|
80
|
-
client,
|
|
81
|
-
eventController,
|
|
82
|
-
onUpdate,
|
|
83
|
-
renderSegments,
|
|
84
|
-
appShellRef,
|
|
85
|
-
} = config;
|
|
73
|
+
const { store, client, eventController, onUpdate, renderSegments } = config;
|
|
86
74
|
let version = config.version;
|
|
87
75
|
|
|
88
|
-
/**
|
|
89
|
-
* Replace the active app-shell snapshot atomically. Called by the partial
|
|
90
|
-
* updater when a response's routerId indicates the navigation crossed
|
|
91
|
-
* into a different app. Runs the local-only side-effects tied to
|
|
92
|
-
* app-shell fields (app version, rango-state namespace) so the new app
|
|
93
|
-
* owns them after the swap. Theme, warmup, and prefetch TTL are
|
|
94
|
-
* document-lifetime and are NOT touched here.
|
|
95
|
-
*/
|
|
96
|
-
function applyAppShell(next: AppShell): void {
|
|
97
|
-
if (appShellRef) {
|
|
98
|
-
appShellRef.update(next);
|
|
99
|
-
}
|
|
100
|
-
if (next.version !== undefined) {
|
|
101
|
-
version = next.version;
|
|
102
|
-
setAppVersion(next.version);
|
|
103
|
-
// Use the local-only setter — initRangoState writes the shared
|
|
104
|
-
// localStorage key and fires a storage event in other tabs still in
|
|
105
|
-
// the old app. setRangoStateLocal only mutates this tab's in-memory
|
|
106
|
-
// cache and rebinds it to the target app's routerId-scoped key,
|
|
107
|
-
// preserving the "local-only, no broadcast/rotation" contract for
|
|
108
|
-
// smooth app-switch transitions.
|
|
109
|
-
setRangoStateLocal(next.version, next.routerId);
|
|
110
|
-
}
|
|
111
|
-
// Cross-app: prior cache entries belong to a different app's segments.
|
|
112
|
-
// Drop them locally only — do NOT broadcast invalidation or rotate the
|
|
113
|
-
// shared X-Rango-State token, since other tabs still in the old app are
|
|
114
|
-
// unaffected by this tab's transition.
|
|
115
|
-
store.clearHistoryCacheLocal();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
76
|
// Create shared partial updater
|
|
119
77
|
const fetchPartialUpdate = createPartialUpdater({
|
|
120
78
|
store,
|
|
@@ -122,7 +80,6 @@ export function createNavigationBridge(
|
|
|
122
80
|
onUpdate,
|
|
123
81
|
renderSegments,
|
|
124
82
|
getVersion: () => version,
|
|
125
|
-
applyAppShell,
|
|
126
83
|
});
|
|
127
84
|
|
|
128
85
|
return {
|
|
@@ -204,11 +161,7 @@ export function createNavigationBridge(
|
|
|
204
161
|
},
|
|
205
162
|
{},
|
|
206
163
|
);
|
|
207
|
-
|
|
208
|
-
window.history.replaceState(historyState, "", url);
|
|
209
|
-
} else {
|
|
210
|
-
window.history.pushState(historyState, "", url);
|
|
211
|
-
}
|
|
164
|
+
pushHistoryWithIdx(historyState, url, options?.replace ?? false);
|
|
212
165
|
|
|
213
166
|
// Ensure new history entry has a scroll restoration key
|
|
214
167
|
ensureHistoryKey();
|
|
@@ -495,11 +448,22 @@ export function createNavigationBridge(
|
|
|
495
448
|
// Helper to check if streaming is in progress
|
|
496
449
|
const isStreaming = () => eventController.getState().isStreaming;
|
|
497
450
|
|
|
451
|
+
// Surface any external rotation of the rango state cookie (a server
|
|
452
|
+
// Set-Cookie, a sibling tab, a cookie clear) BEFORE reading the stale bit.
|
|
453
|
+
// The divergence observer only runs inside getRangoState() — fetch-time —
|
|
454
|
+
// so a popstate-first interaction would otherwise serve a pre-mutation
|
|
455
|
+
// page as fresh and never fetch to trigger the observer. Reading here lets
|
|
456
|
+
// the observer mark the history cache stale so getCachedSegments sees it.
|
|
457
|
+
getRangoState();
|
|
458
|
+
|
|
498
459
|
// Check if we can restore from history cache
|
|
499
460
|
const cached = store.getCachedSegments(historyKey);
|
|
500
461
|
const cachedSegments = cached?.segments;
|
|
501
462
|
const cachedHandleData = cached?.handleData;
|
|
502
|
-
|
|
463
|
+
// While an action is in flight the fence persists no stale flag, so OR it
|
|
464
|
+
// in here: a popstate during the flight serves the cached entry AND
|
|
465
|
+
// revalidates (SWR) instead of serving it as fresh.
|
|
466
|
+
const isStale = (cached?.stale ?? false) || isActionFenceActive();
|
|
503
467
|
|
|
504
468
|
if (cachedSegments && cachedSegments.length > 0) {
|
|
505
469
|
// Update store to point to this history entry
|
|
@@ -541,7 +505,14 @@ export function createNavigationBridge(
|
|
|
541
505
|
},
|
|
542
506
|
scroll: { restore: true, isStreaming },
|
|
543
507
|
};
|
|
544
|
-
|
|
508
|
+
// Intercept-driven popstate (entering OR leaving an intercept) only
|
|
509
|
+
// mutates the parallel slot; the main outlet shows the same content.
|
|
510
|
+
// Skip startViewTransition in those cases — same rationale as the
|
|
511
|
+
// intercept guard in partial-update.ts's hasTransition computation.
|
|
512
|
+
const hasTransition =
|
|
513
|
+
!isIntercept &&
|
|
514
|
+
!isLeavingIntercept &&
|
|
515
|
+
cachedSegments.some((s) => s.transition);
|
|
545
516
|
if (hasTransition) {
|
|
546
517
|
startTransition(() => {
|
|
547
518
|
if (addTransitionType) {
|
|
@@ -704,15 +675,15 @@ export function createNavigationBridge(
|
|
|
704
675
|
};
|
|
705
676
|
},
|
|
706
677
|
|
|
678
|
+
getVersion(): string | undefined {
|
|
679
|
+
return version;
|
|
680
|
+
},
|
|
681
|
+
|
|
707
682
|
updateVersion(newVersion: string): void {
|
|
708
683
|
version = newVersion;
|
|
709
684
|
setAppVersion(newVersion);
|
|
710
685
|
store.clearHistoryCache();
|
|
711
686
|
},
|
|
712
|
-
|
|
713
|
-
updateAppShell(next: AppShell): void {
|
|
714
|
-
applyAppShell(next);
|
|
715
|
-
},
|
|
716
687
|
};
|
|
717
688
|
}
|
|
718
689
|
|
|
@@ -12,16 +12,20 @@ import {
|
|
|
12
12
|
startBrowserTransaction,
|
|
13
13
|
} from "./logging.js";
|
|
14
14
|
import { getRangoState } from "./rango-state.js";
|
|
15
|
+
import { isActionFenceActive } from "./action-fence.js";
|
|
15
16
|
import {
|
|
16
17
|
extractRscHeaderUrl,
|
|
17
18
|
emptyResponse,
|
|
19
|
+
handleReloadHeader,
|
|
18
20
|
teeWithCompletion,
|
|
21
|
+
isForeignRouterId,
|
|
19
22
|
} from "./response-adapter.js";
|
|
20
23
|
import {
|
|
21
24
|
buildPrefetchKey,
|
|
22
25
|
buildSourceKey,
|
|
23
26
|
consumeInflightPrefetch,
|
|
24
27
|
consumePrefetch,
|
|
28
|
+
type DecodedPrefetch,
|
|
25
29
|
} from "./prefetch/cache.js";
|
|
26
30
|
|
|
27
31
|
/**
|
|
@@ -105,31 +109,38 @@ export function createNavigationClient(
|
|
|
105
109
|
// server-action invalidation) auto-invalidates both scopes.
|
|
106
110
|
// Skip cache for stale revalidation (needs fresh data), HMR (needs
|
|
107
111
|
// fresh modules), and intercept contexts (source-dependent responses).
|
|
108
|
-
|
|
112
|
+
// Suspend prefetch consumption while an action is in flight: a queued
|
|
113
|
+
// prefetch holds pre-mutation data and must not be served until the
|
|
114
|
+
// action's response decides whether anything changed.
|
|
115
|
+
const canUsePrefetch =
|
|
116
|
+
!staleRevalidation &&
|
|
117
|
+
!hmr &&
|
|
118
|
+
!interceptSourceUrl &&
|
|
119
|
+
!isActionFenceActive();
|
|
109
120
|
const rangoState = getRangoState();
|
|
110
121
|
const wildcardKey = buildPrefetchKey(rangoState, fetchUrl);
|
|
111
122
|
const cacheKey = buildSourceKey(rangoState, previousUrl, fetchUrl);
|
|
112
123
|
|
|
113
|
-
let
|
|
124
|
+
let cachedEntry: DecodedPrefetch | null = null;
|
|
114
125
|
let hitKey: string | null = null;
|
|
115
126
|
if (canUsePrefetch) {
|
|
116
|
-
|
|
117
|
-
if (
|
|
127
|
+
cachedEntry = consumePrefetch(cacheKey);
|
|
128
|
+
if (cachedEntry) {
|
|
118
129
|
hitKey = cacheKey;
|
|
119
130
|
} else {
|
|
120
|
-
|
|
121
|
-
if (
|
|
131
|
+
cachedEntry = consumePrefetch(wildcardKey);
|
|
132
|
+
if (cachedEntry) hitKey = wildcardKey;
|
|
122
133
|
}
|
|
123
134
|
}
|
|
124
135
|
|
|
125
|
-
let
|
|
126
|
-
if (canUsePrefetch && !
|
|
127
|
-
|
|
128
|
-
if (
|
|
136
|
+
let inflightEntryPromise: Promise<DecodedPrefetch | null> | null = null;
|
|
137
|
+
if (canUsePrefetch && !cachedEntry) {
|
|
138
|
+
inflightEntryPromise = consumeInflightPrefetch(cacheKey);
|
|
139
|
+
if (inflightEntryPromise) {
|
|
129
140
|
hitKey = cacheKey;
|
|
130
141
|
} else {
|
|
131
|
-
|
|
132
|
-
if (
|
|
142
|
+
inflightEntryPromise = consumeInflightPrefetch(wildcardKey);
|
|
143
|
+
if (inflightEntryPromise) hitKey = wildcardKey;
|
|
133
144
|
}
|
|
134
145
|
}
|
|
135
146
|
// Track when the stream completes
|
|
@@ -148,21 +159,17 @@ export function createNavigationClient(
|
|
|
148
159
|
source: string,
|
|
149
160
|
): Response | Promise<Response> => {
|
|
150
161
|
// Version mismatch — server wants a full page reload
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
window.location.href = reload.url;
|
|
163
|
-
// Block further processing — page is reloading
|
|
164
|
-
return new Promise<Response>(() => {});
|
|
165
|
-
}
|
|
162
|
+
const reloadResult = handleReloadHeader(response, {
|
|
163
|
+
onBlocked: resolveStreamComplete,
|
|
164
|
+
onReload: (url) => {
|
|
165
|
+
if (tx) {
|
|
166
|
+
browserDebugLog(tx, `version mismatch, reloading (${source})`, {
|
|
167
|
+
reloadUrl: url,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
if (reloadResult) return reloadResult;
|
|
166
173
|
|
|
167
174
|
// Server-side redirect without state: the server returned 204 with
|
|
168
175
|
// X-RSC-Redirect instead of a 3xx (which fetch would auto-follow
|
|
@@ -183,6 +190,19 @@ export function createNavigationClient(
|
|
|
183
190
|
throw new ServerRedirect(redirect.url, undefined);
|
|
184
191
|
}
|
|
185
192
|
|
|
193
|
+
// Integrity check (pre-decode): refuse a foreign app's content response
|
|
194
|
+
// before createFromFetch imports its chunks. Ordered AFTER the reload
|
|
195
|
+
// and redirect handlers — control responses are never stamped with
|
|
196
|
+
// X-RSC-Router-Id, so they are steered first and never reach here.
|
|
197
|
+
if (isForeignRouterId(response, routerId)) {
|
|
198
|
+
if (tx) {
|
|
199
|
+
browserDebugLog(tx, `router id mismatch, reloading (${source})`);
|
|
200
|
+
}
|
|
201
|
+
resolveStreamComplete();
|
|
202
|
+
window.location.href = targetUrl;
|
|
203
|
+
return new Promise<Response>(() => {});
|
|
204
|
+
}
|
|
205
|
+
|
|
186
206
|
return response;
|
|
187
207
|
};
|
|
188
208
|
|
|
@@ -195,6 +215,11 @@ export function createNavigationClient(
|
|
|
195
215
|
}
|
|
196
216
|
|
|
197
217
|
return fetch(fetchUrl, {
|
|
218
|
+
// During an action's flight the state is not rotated, so the old
|
|
219
|
+
// X-Rango-State still matches the Vary-keyed HTTP-cache entry; bypass
|
|
220
|
+
// it so a genuine mid-action navigation fetches fresh instead of being
|
|
221
|
+
// served the stale prefetched bytes.
|
|
222
|
+
...(isActionFenceActive() && { cache: "no-store" as RequestCache }),
|
|
198
223
|
headers: {
|
|
199
224
|
"X-RSC-Router-Client-Path": previousUrl,
|
|
200
225
|
"X-Rango-State": getRangoState(),
|
|
@@ -220,29 +245,32 @@ export function createNavigationClient(
|
|
|
220
245
|
});
|
|
221
246
|
};
|
|
222
247
|
|
|
223
|
-
|
|
248
|
+
// A warm prefetch hit returns its eagerly-decoded payload directly: the
|
|
249
|
+
// route's chunks were imported during the prefetch, so this click runs
|
|
250
|
+
// no decode and no network. Only the fresh path runs createFromFetch and
|
|
251
|
+
// resolves the local streamComplete (via doFreshFetch's teeWithCompletion
|
|
252
|
+
// and the control-header short-circuits in validateRscHeaders).
|
|
253
|
+
const freshResult = (): {
|
|
254
|
+
payload: Promise<RscPayload>;
|
|
255
|
+
streamComplete: Promise<void>;
|
|
256
|
+
} => ({
|
|
257
|
+
payload: deps.createFromFetch<RscPayload>(doFreshFetch()),
|
|
258
|
+
streamComplete,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
let payloadPromise: Promise<RscPayload>;
|
|
262
|
+
let streamCompletePromise: Promise<void>;
|
|
224
263
|
|
|
225
|
-
if (
|
|
264
|
+
if (cachedEntry) {
|
|
226
265
|
if (tx) {
|
|
227
|
-
browserDebugLog(tx, "prefetch cache hit", {
|
|
266
|
+
browserDebugLog(tx, "prefetch cache hit (warm)", {
|
|
228
267
|
key: hitKey,
|
|
229
268
|
wildcard: hitKey === wildcardKey,
|
|
230
269
|
});
|
|
231
270
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return teeWithCompletion(
|
|
237
|
-
validated,
|
|
238
|
-
() => {
|
|
239
|
-
if (tx) browserDebugLog(tx, "stream complete (from cache)");
|
|
240
|
-
resolveStreamComplete();
|
|
241
|
-
},
|
|
242
|
-
signal,
|
|
243
|
-
);
|
|
244
|
-
});
|
|
245
|
-
} else if (inflightResponsePromise) {
|
|
271
|
+
payloadPromise = cachedEntry.payload;
|
|
272
|
+
streamCompletePromise = cachedEntry.streamComplete;
|
|
273
|
+
} else if (inflightEntryPromise) {
|
|
246
274
|
if (tx) {
|
|
247
275
|
browserDebugLog(tx, "reusing inflight prefetch", {
|
|
248
276
|
key: hitKey,
|
|
@@ -250,51 +278,35 @@ export function createNavigationClient(
|
|
|
250
278
|
});
|
|
251
279
|
}
|
|
252
280
|
const adoptedViaWildcard = hitKey === wildcardKey;
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
return doFreshFetch();
|
|
281
|
+
const entry = await inflightEntryPromise;
|
|
282
|
+
if (!entry) {
|
|
283
|
+
if (tx) {
|
|
284
|
+
browserDebugLog(tx, "inflight prefetch unavailable, refetching");
|
|
259
285
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
//
|
|
264
|
-
// a different source page. Discard and refetch.
|
|
265
|
-
if (
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
browserDebugLog(
|
|
271
|
-
tx,
|
|
272
|
-
"wildcard inflight turned out source-scoped, refetching",
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
return doFreshFetch();
|
|
286
|
+
({ payload: payloadPromise, streamComplete: streamCompletePromise } =
|
|
287
|
+
freshResult());
|
|
288
|
+
} else if (adoptedViaWildcard && entry.scope === "source") {
|
|
289
|
+
// A wildcard-adopted inflight that turned out source-scoped was
|
|
290
|
+
// built for a different source page. Discard and refetch.
|
|
291
|
+
if (tx) {
|
|
292
|
+
browserDebugLog(
|
|
293
|
+
tx,
|
|
294
|
+
"wildcard inflight turned out source-scoped, refetching",
|
|
295
|
+
);
|
|
276
296
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
() => {
|
|
284
|
-
if (tx) {
|
|
285
|
-
browserDebugLog(tx, "stream complete (from inflight prefetch)");
|
|
286
|
-
}
|
|
287
|
-
resolveStreamComplete();
|
|
288
|
-
},
|
|
289
|
-
signal,
|
|
290
|
-
);
|
|
291
|
-
});
|
|
297
|
+
({ payload: payloadPromise, streamComplete: streamCompletePromise } =
|
|
298
|
+
freshResult());
|
|
299
|
+
} else {
|
|
300
|
+
payloadPromise = entry.payload;
|
|
301
|
+
streamCompletePromise = entry.streamComplete;
|
|
302
|
+
}
|
|
292
303
|
} else {
|
|
293
|
-
|
|
304
|
+
({ payload: payloadPromise, streamComplete: streamCompletePromise } =
|
|
305
|
+
freshResult());
|
|
294
306
|
}
|
|
295
307
|
|
|
296
308
|
try {
|
|
297
|
-
const payload = await
|
|
309
|
+
const payload = await payloadPromise;
|
|
298
310
|
|
|
299
311
|
if (tx) {
|
|
300
312
|
browserDebugLog(tx, "response received", {
|
|
@@ -303,7 +315,7 @@ export function createNavigationClient(
|
|
|
303
315
|
diffCount: payload.metadata?.diff?.length ?? 0,
|
|
304
316
|
});
|
|
305
317
|
}
|
|
306
|
-
return { payload, streamComplete };
|
|
318
|
+
return { payload, streamComplete: streamCompletePromise };
|
|
307
319
|
} catch (error) {
|
|
308
320
|
// Convert network-level errors to NetworkError for proper handling
|
|
309
321
|
if (isNetworkError(error)) {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A module-level handle to the active navigation store.
|
|
3
|
+
*
|
|
4
|
+
* The boot path (`rsc-router.tsx`) calls `createNavigationStore()` directly;
|
|
5
|
+
* there is no global store singleton. This handle is the live reference for
|
|
6
|
+
* code that needs the store but does not
|
|
7
|
+
* receive it by argument: the jar-divergence observer (below) and the client
|
|
8
|
+
* seat of `invalidateClientCache()` (added later).
|
|
9
|
+
*
|
|
10
|
+
* Dependency-light on purpose: it imports only `setRangoStateObserver` and the
|
|
11
|
+
* store type, so pulling it into the default root entry does not drag the
|
|
12
|
+
* navigation store into bundles that previously lacked it.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { setRangoStateObserver } from "./rango-state.js";
|
|
16
|
+
import type { NavigationStore } from "./types.js";
|
|
17
|
+
|
|
18
|
+
let registeredStore: NavigationStore | null = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Register the active navigation store at boot, and wire the jar-divergence
|
|
22
|
+
* observer: when a per-request cookie read detects an EXTERNAL rotation (a
|
|
23
|
+
* sibling tab, a server `Set-Cookie`, or a cookie clear), mark this tab's
|
|
24
|
+
* history cache stale. The history cache is not state-keyed, so the value
|
|
25
|
+
* rotation alone does not reach it. No broadcast, no prefetch clear, no
|
|
26
|
+
* re-rotation — the value already changed externally.
|
|
27
|
+
*/
|
|
28
|
+
export function registerNavigationStore(store: NavigationStore): void {
|
|
29
|
+
registeredStore = store;
|
|
30
|
+
setRangoStateObserver(() => {
|
|
31
|
+
registeredStore?.markHistoryCacheStale();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The active navigation store, or null before boot has registered it. */
|
|
36
|
+
export function getRegisteredStore(): NavigationStore | null {
|
|
37
|
+
return registeredStore;
|
|
38
|
+
}
|