@rangojs/router 0.17.0 → 0.19.0
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 +3 -2
- package/dist/types/browser/cookie-name.d.ts +6 -0
- package/dist/types/browser/event-controller.d.ts +3 -3
- package/dist/types/browser/history-state.d.ts +18 -0
- package/dist/types/browser/navigation-transaction.d.ts +20 -2
- package/dist/types/browser/partial-update.d.ts +16 -1
- package/dist/types/browser/prefetch/cache.d.ts +6 -6
- package/dist/types/browser/react/Link.d.ts +19 -9
- package/dist/types/browser/react/context.d.ts +2 -2
- package/dist/types/browser/react/location-state-shared.d.ts +95 -56
- package/dist/types/browser/react/location-state.d.ts +1 -1
- package/dist/types/browser/transition-when.d.ts +80 -0
- package/dist/types/browser/types.d.ts +104 -9
- package/dist/types/build/generate-manifest.d.ts +8 -0
- package/dist/types/cache/background-task.d.ts +24 -2
- package/dist/types/cache/cache-exec-scope.d.ts +31 -0
- package/dist/types/cache/cache-key-utils.d.ts +48 -2
- package/dist/types/cache/cache-policy.d.ts +18 -1
- package/dist/types/cache/cache-runtime.d.ts +7 -0
- package/dist/types/cache/cache-scope.d.ts +192 -26
- package/dist/types/cache/cache-tag.d.ts +116 -11
- package/dist/types/cache/cf/cf-base64.d.ts +6 -0
- package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +224 -72
- package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
- package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +19 -0
- package/dist/types/cache/document-cache.d.ts +10 -0
- package/dist/types/cache/handle-snapshot.d.ts +41 -8
- package/dist/types/cache/index.d.ts +1 -0
- package/dist/types/cache/invalidation-order.d.ts +23 -0
- package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
- package/dist/types/cache/request-tag-mask.d.ts +54 -0
- package/dist/types/cache/shell-memo.d.ts +178 -0
- package/dist/types/cache/shell-snapshot.d.ts +141 -105
- package/dist/types/cache/tag-invalidation.d.ts +54 -3
- package/dist/types/cache/types.d.ts +267 -96
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +123 -18
- package/dist/types/client-urls/navigation.d.ts +28 -2
- package/dist/types/client-urls/server-projection.d.ts +5 -2
- package/dist/types/client-urls/types.d.ts +5 -5
- package/dist/types/client.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/prerender/build-shell-capture.d.ts +11 -4
- package/dist/types/route-definition/helpers-types.d.ts +11 -11
- package/dist/types/route-name.d.ts +7 -0
- package/dist/types/router/error-handling.d.ts +1 -1
- package/dist/types/router/instrument.d.ts +8 -0
- package/dist/types/router/intercept-resolution.d.ts +2 -0
- package/dist/types/router/match-context.d.ts +5 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
- package/dist/types/router/middleware-types.d.ts +6 -1
- package/dist/types/router/router-options.d.ts +4 -1
- package/dist/types/router/segment-resolution/loader-cache.d.ts +58 -12
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +33 -48
- package/dist/types/router/segment-resolution/mask-nested.d.ts +73 -24
- package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
- package/dist/types/router/transition-when.d.ts +24 -10
- package/dist/types/rsc/attach-transition-when.d.ts +18 -0
- package/dist/types/rsc/full-payload.d.ts +21 -0
- package/dist/types/rsc/handler.d.ts +1 -1
- package/dist/types/rsc/response-cache-serve.d.ts +15 -12
- package/dist/types/rsc/shell-build-manifest.d.ts +12 -3
- package/dist/types/rsc/shell-capture-constants.d.ts +116 -12
- package/dist/types/rsc/shell-capture.d.ts +202 -74
- package/dist/types/rsc/shell-serve.d.ts +144 -50
- package/dist/types/rsc/types.d.ts +9 -0
- package/dist/types/segment-system.d.ts +7 -0
- package/dist/types/server/context.d.ts +155 -33
- package/dist/types/server/cookie-store.d.ts +33 -0
- package/dist/types/server/handle-store.d.ts +52 -1
- package/dist/types/server/request-context.d.ts +210 -63
- package/dist/types/server/request-headers.d.ts +44 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/ssr/index.d.ts +9 -0
- package/dist/types/ssr/ssr-root.d.ts +9 -8
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +7 -0
- package/dist/types/testing/index.d.ts +3 -2
- package/dist/types/testing/internal/action-id.d.ts +7 -0
- package/dist/types/testing/location-state-key.d.ts +24 -0
- package/dist/types/testing/render-route.d.ts +17 -1
- package/dist/types/testing/run-loader.d.ts +18 -3
- package/dist/types/testing/run-transition-when.d.ts +44 -69
- package/dist/types/testing/serve-shell-request.d.ts +133 -0
- package/dist/types/testing/shell-status.d.ts +44 -19
- package/dist/types/transition-when-ref.d.ts +79 -0
- package/dist/types/types/cache-types.d.ts +15 -3
- package/dist/types/types/handler-context.d.ts +14 -1
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/segments.d.ts +86 -71
- package/dist/types/urls/path-helper-types.d.ts +3 -4
- package/dist/types/urls/pattern-types.d.ts +21 -18
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
- package/dist/types/vite/router-discovery.d.ts +29 -2
- package/dist/vite/index.js +1005 -105
- package/package.json +1 -1
- package/skills/breadcrumbs/SKILL.md +17 -7
- package/skills/cache-guide/SKILL.md +175 -76
- package/skills/caching/SKILL.md +361 -33
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +12 -7
- package/skills/cloudflare/SKILL.md +15 -0
- package/skills/comparison/references/framework-comparison.md +52 -37
- package/skills/defer-hydration/SKILL.md +50 -30
- package/skills/deployment-caching/SKILL.md +5 -2
- package/skills/document-cache/SKILL.md +32 -2
- package/skills/handler-use/SKILL.md +2 -0
- package/skills/hooks/SKILL.md +3 -1
- package/skills/hooks/handle-and-actions.md +3 -1
- package/skills/hooks/navigation.md +23 -2
- package/skills/hooks/state.md +101 -7
- package/skills/intercept/SKILL.md +29 -12
- package/skills/loader/SKILL.md +197 -44
- package/skills/middleware/SKILL.md +34 -9
- package/skills/migrate-nextjs/SKILL.md +68 -37
- package/skills/migrate-react-router/SKILL.md +2 -1
- package/skills/migrate-react-router/component-migration.md +3 -1
- package/skills/migrate-react-router/data-and-actions.md +3 -1
- package/skills/migrate-react-router/route-mapping.md +7 -4
- package/skills/observability/SKILL.md +65 -0
- package/skills/parallel/SKILL.md +13 -8
- package/skills/ppr/SKILL.md +715 -371
- package/skills/prerender/SKILL.md +30 -17
- package/skills/rango/SKILL.md +72 -34
- package/skills/route/SKILL.md +14 -7
- package/skills/router-setup/SKILL.md +4 -2
- package/skills/scripts/SKILL.md +8 -1
- package/skills/server-actions/SKILL.md +11 -0
- package/skills/shell-manifest/SKILL.md +21 -12
- package/skills/testing/SKILL.md +49 -43
- package/skills/testing/cache-prerender.md +15 -14
- package/skills/testing/client-components.md +45 -3
- package/skills/testing/loader.md +19 -1
- package/skills/testing/middleware.md +1 -0
- package/skills/testing/predicates.md +49 -49
- package/skills/testing/render-handler.md +1 -0
- package/skills/testing/serve-shell-request.md +202 -0
- package/skills/testing/server-actions.md +9 -3
- package/skills/theme/SKILL.md +56 -4
- package/skills/typesafety/env-and-bindings.md +18 -2
- package/skills/use-cache/SKILL.md +91 -23
- package/skills/vercel/SKILL.md +14 -2
- package/skills/view-transitions/SKILL.md +150 -29
- package/src/__augment-tests__/augmented.check.ts +23 -0
- package/src/browser/cookie-name.ts +23 -0
- package/src/browser/event-controller.ts +4 -4
- package/src/browser/history-state.ts +75 -1
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +77 -18
- package/src/browser/navigation-client.ts +4 -3
- package/src/browser/navigation-store.ts +43 -1
- package/src/browser/navigation-transaction.ts +31 -11
- package/src/browser/partial-update.ts +146 -35
- package/src/browser/prefetch/cache.ts +6 -6
- package/src/browser/prefetch/fetch.ts +2 -2
- package/src/browser/react/Link.tsx +38 -33
- package/src/browser/react/NavigationProvider.tsx +2 -2
- package/src/browser/react/context.ts +2 -2
- package/src/browser/react/location-state-shared.ts +241 -49
- package/src/browser/react/location-state.ts +5 -2
- package/src/browser/rsc-router.tsx +9 -0
- package/src/browser/server-action-bridge.ts +84 -15
- package/src/browser/transition-when.ts +205 -0
- package/src/browser/types.ts +124 -10
- package/src/build/generate-manifest.ts +77 -1
- package/src/cache/background-task.ts +55 -2
- package/src/cache/cache-exec-scope.ts +48 -1
- package/src/cache/cache-key-utils.ts +62 -2
- package/src/cache/cache-policy.ts +31 -4
- package/src/cache/cache-runtime.ts +209 -129
- package/src/cache/cache-scope.ts +573 -69
- package/src/cache/cache-tag.ts +475 -19
- package/src/cache/cf/cf-base64.ts +36 -4
- package/src/cache/cf/cf-cache-constants.ts +12 -0
- package/src/cache/cf/cf-cache-store.ts +1094 -376
- package/src/cache/cf/cf-cache-types.ts +32 -4
- package/src/cache/cf/cf-shell-frame.ts +258 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +55 -40
- package/src/cache/document-cache.ts +53 -7
- package/src/cache/handle-snapshot.ts +53 -9
- package/src/cache/index.ts +2 -0
- package/src/cache/invalidation-order.ts +84 -0
- package/src/cache/isolate-tag-memo.ts +308 -0
- package/src/cache/request-tag-mask.ts +147 -0
- package/src/cache/shell-memo.ts +341 -0
- package/src/cache/shell-snapshot.ts +249 -277
- package/src/cache/tag-invalidation.ts +109 -18
- package/src/cache/types.ts +274 -102
- package/src/cache/vercel/vercel-cache-store.ts +561 -74
- package/src/client-urls/client-root.tsx +8 -4
- package/src/client-urls/client-urls.ts +8 -6
- package/src/client-urls/navigation.ts +55 -4
- package/src/client-urls/server-projection.ts +31 -10
- package/src/client-urls/types.ts +12 -5
- package/src/client.tsx +2 -0
- package/src/host/cookie-handler.ts +2 -1
- package/src/index.rsc.ts +3 -0
- package/src/index.ts +3 -0
- package/src/loader-redirect.tsx +7 -3
- package/src/prerender/build-shell-capture.ts +173 -110
- package/src/route-definition/dsl-helpers.ts +6 -3
- package/src/route-definition/helpers-types.ts +11 -11
- package/src/route-name.ts +12 -0
- package/src/router/content-negotiation.ts +4 -1
- package/src/router/error-handling.ts +5 -2
- package/src/router/handler-context.ts +42 -5
- package/src/router/instrument.ts +54 -2
- package/src/router/intercept-resolution.ts +30 -8
- package/src/router/loader-resolution.ts +89 -33
- package/src/router/logging.ts +5 -3
- package/src/router/match-api.ts +89 -42
- package/src/router/match-context.ts +5 -0
- package/src/router/match-handlers.ts +14 -29
- package/src/router/match-middleware/background-revalidation.ts +3 -5
- package/src/router/match-middleware/cache-lookup.ts +52 -19
- package/src/router/match-middleware/cache-store.ts +36 -10
- package/src/router/match-result.ts +1 -0
- package/src/router/middleware-types.ts +6 -1
- package/src/router/middleware.ts +20 -4
- package/src/router/navigation-snapshot.ts +6 -5
- package/src/router/prerender-match.ts +10 -0
- package/src/router/request-classification.ts +7 -4
- package/src/router/router-options.ts +4 -1
- package/src/router/segment-resolution/fresh.ts +18 -13
- package/src/router/segment-resolution/helpers.ts +4 -2
- package/src/router/segment-resolution/loader-cache.ts +366 -70
- package/src/router/segment-resolution/loader-mask.ts +3 -4
- package/src/router/segment-resolution/loader-snapshot.ts +215 -126
- package/src/router/segment-resolution/mask-nested.ts +161 -42
- package/src/router/segment-resolution/revalidation.ts +27 -13
- package/src/router/segment-resolution/view-transition-default.ts +6 -21
- package/src/router/telemetry.ts +6 -3
- package/src/router/transition-when.ts +34 -52
- package/src/router.ts +2 -1
- package/src/rsc/attach-transition-when.ts +47 -0
- package/src/rsc/full-payload.ts +47 -3
- package/src/rsc/handler.ts +24 -10
- package/src/rsc/loader-fetch.ts +2 -1
- package/src/rsc/manifest-init.ts +4 -0
- package/src/rsc/origin-guard.ts +8 -5
- package/src/rsc/progressive-enhancement.ts +10 -32
- package/src/rsc/response-cache-serve.ts +69 -78
- package/src/rsc/response-route-handler.ts +2 -2
- package/src/rsc/rsc-rendering.ts +712 -338
- package/src/rsc/server-action.ts +8 -20
- package/src/rsc/shell-build-manifest.ts +49 -29
- package/src/rsc/shell-capture-constants.ts +182 -12
- package/src/rsc/shell-capture.ts +1126 -612
- package/src/rsc/shell-serve.ts +331 -85
- package/src/rsc/ssr-setup.ts +3 -2
- package/src/rsc/types.ts +9 -0
- package/src/segment-system.tsx +40 -7
- package/src/server/context.ts +328 -72
- package/src/server/cookie-store.ts +184 -133
- package/src/server/handle-store.ts +245 -27
- package/src/server/request-context.ts +364 -100
- package/src/server/request-headers.ts +134 -0
- package/src/server.ts +4 -0
- package/src/ssr/index.tsx +96 -37
- package/src/ssr/ssr-root.tsx +9 -8
- package/src/testing/dispatch.ts +4 -7
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.entry.ts +14 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/internal/action-id.ts +25 -0
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/location-state-key.ts +45 -0
- package/src/testing/render-handler.ts +1 -1
- package/src/testing/render-route.tsx +109 -16
- package/src/testing/run-client-revalidate.ts +3 -19
- package/src/testing/run-loader.ts +67 -5
- package/src/testing/run-transition-when.ts +109 -172
- package/src/testing/serve-shell-request.ts +381 -0
- package/src/testing/shell-status.ts +73 -47
- package/src/theme/ThemeProvider.tsx +7 -6
- package/src/transition-when-ref.ts +220 -0
- package/src/types/cache-types.ts +15 -3
- package/src/types/handler-context.ts +14 -1
- package/src/types/index.ts +3 -0
- package/src/types/segments.ts +94 -91
- package/src/urls/path-helper-types.ts +3 -4
- package/src/urls/pattern-types.ts +21 -18
- package/src/vite/discovery/discover-routers.ts +3 -0
- package/src/vite/discovery/shell-prerender-phase.ts +9 -2
- package/src/vite/discovery/virtual-module-codegen.ts +16 -6
- package/src/vite/plugins/transition-when-hoist.ts +1064 -0
- package/src/vite/rango.ts +2 -0
- package/src/vite/router-discovery.ts +89 -9
- package/dist/types/rsc/transition-gate.d.ts +0 -23
- package/src/rsc/transition-gate.ts +0 -86
package/src/server/context.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ServerRouteLocation } from "../types/segments.js";
|
|
1
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
3
|
import type { ReactNode } from "react";
|
|
3
4
|
import type {
|
|
@@ -13,9 +14,11 @@ import type {
|
|
|
13
14
|
import { invariant, DslContextError } from "../errors";
|
|
14
15
|
import type { DefaultRouteName } from "../types/global-namespace.js";
|
|
15
16
|
import type { ContextVar } from "../context-var.js";
|
|
17
|
+
import { PPR_LANE_HINT } from "../rsc/shell-capture-constants.js";
|
|
16
18
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
endIdentityExempt,
|
|
20
|
+
isInsideCacheExecScope,
|
|
21
|
+
isInsideIdentityExempt,
|
|
19
22
|
} from "../cache/cache-exec-scope.js";
|
|
20
23
|
|
|
21
24
|
// ============================================================================
|
|
@@ -134,6 +137,15 @@ export type LoaderEntry = {
|
|
|
134
137
|
* appears on navigation-lane entries.
|
|
135
138
|
*/
|
|
136
139
|
awaitBeforeFlush?: true;
|
|
140
|
+
/**
|
|
141
|
+
* loader(Def, { ssr: false }) on every DSL evaluation, document and
|
|
142
|
+
* navigation: the loader's PPR bake lane (loader-cache.ts
|
|
143
|
+
* resolveLoaderData). The bake/seed key rides on it, so a client
|
|
144
|
+
* navigation's shell replay pins exactly the loaders a document HIT pins,
|
|
145
|
+
* whatever the entry's loading(). awaitBeforeFlush cannot carry that: a
|
|
146
|
+
* navigation evaluation never has it.
|
|
147
|
+
*/
|
|
148
|
+
bake?: true;
|
|
137
149
|
};
|
|
138
150
|
|
|
139
151
|
/**
|
|
@@ -153,20 +165,22 @@ export type InterceptSegmentsState = {
|
|
|
153
165
|
* Context passed to intercept selector functions (when())
|
|
154
166
|
* Contains navigation context to determine if interception should occur.
|
|
155
167
|
*
|
|
168
|
+
* `from` / `to` have the shape transition({ when }) sees, without `state`:
|
|
169
|
+
* history state never reaches the server.
|
|
170
|
+
*
|
|
156
171
|
* Note: when() is evaluated during route matching, BEFORE middleware runs.
|
|
157
172
|
* So ctx.get()/ctx.use() are not available, but env (platform bindings) is.
|
|
158
173
|
*
|
|
159
174
|
* @internal This type is an implementation detail and may change without notice.
|
|
160
175
|
*/
|
|
161
176
|
export type InterceptSelectorContext<TEnv = any> = {
|
|
162
|
-
from:
|
|
163
|
-
|
|
164
|
-
|
|
177
|
+
/** Where the navigation comes from: the intercept source while one is open. */
|
|
178
|
+
from: ServerRouteLocation;
|
|
179
|
+
/** The navigation target. */
|
|
180
|
+
to: ServerRouteLocation;
|
|
165
181
|
request: Request; // The HTTP request object
|
|
166
182
|
env: TEnv; // Platform bindings (Cloudflare env, etc.)
|
|
167
183
|
segments: InterceptSegmentsState; // Client's current segments (where navigating FROM)
|
|
168
|
-
fromRouteName?: DefaultRouteName; // Named route being navigated away from (undefined for unnamed routes)
|
|
169
|
-
toRouteName?: DefaultRouteName; // Named route being navigated to (undefined for unnamed routes)
|
|
170
184
|
};
|
|
171
185
|
|
|
172
186
|
/**
|
|
@@ -536,7 +550,8 @@ export const getContext = (): {
|
|
|
536
550
|
// the header guard's middleware exemption depends on the latch dying
|
|
537
551
|
// with the funnel scope (see assertCachedHeaderWriteAllowed).
|
|
538
552
|
},
|
|
539
|
-
|
|
553
|
+
// A funnel (its cache() scope) never runs exempt (runIdentityExempt).
|
|
554
|
+
() => endIdentityExempt(callback),
|
|
540
555
|
);
|
|
541
556
|
},
|
|
542
557
|
run: <T>(
|
|
@@ -846,20 +861,17 @@ const loaderScopeALS: AsyncLocalStorage<{ active: true }> = ((
|
|
|
846
861
|
// Purity-only scope: marks that a loader FUNCTION BODY is executing, regardless
|
|
847
862
|
// of how the loader was invoked (DSL via runInsideLoaderScope, or handler-
|
|
848
863
|
// invoked via ctx.use). Consulted by isInsideCacheScope() to exempt
|
|
849
|
-
// request-scoped reads, by getCurrentLoaderBodyId() for guard-warning
|
|
850
|
-
// attribution
|
|
851
|
-
//
|
|
852
|
-
//
|
|
853
|
-
// gating (which must distinguish DSL from handler-invoked loaders) is
|
|
854
|
-
// unchanged.
|
|
864
|
+
// request-scoped reads, and by getCurrentLoaderBodyId() for guard-warning
|
|
865
|
+
// attribution. It deliberately does NOT affect isInsideLoaderScope(), so
|
|
866
|
+
// rendered()/barrier/deadlock gating (which must distinguish DSL from
|
|
867
|
+
// handler-invoked loaders) is unchanged.
|
|
855
868
|
interface LoaderBodyScope {
|
|
856
869
|
active: true;
|
|
857
870
|
loaderId?: string;
|
|
858
|
-
handlerInvoked?: boolean;
|
|
859
871
|
/** The body scope this one was entered from (a ctx.use(Loader) chain). */
|
|
860
872
|
parent?: LoaderBodyScope;
|
|
861
|
-
/** The
|
|
862
|
-
|
|
873
|
+
/** The tags this execution records (#964, cache-tag.ts "Recorded-tag sets"). */
|
|
874
|
+
tags?: Set<string>;
|
|
863
875
|
}
|
|
864
876
|
const LOADER_BODY_SCOPE_KEY = Symbol.for("rangojs-router:loader-body-scope");
|
|
865
877
|
const loaderBodyScopeALS: AsyncLocalStorage<LoaderBodyScope> = ((
|
|
@@ -868,8 +880,9 @@ const loaderBodyScopeALS: AsyncLocalStorage<LoaderBodyScope> = ((
|
|
|
868
880
|
|
|
869
881
|
/**
|
|
870
882
|
* Check if the current execution is inside a cache() DSL boundary.
|
|
871
|
-
* Returns false inside loader execution
|
|
872
|
-
*
|
|
883
|
+
* Returns false inside loader execution: a route cache() does not store
|
|
884
|
+
* loader values, so non-cacheable reads are safe. A loader bound with its own
|
|
885
|
+
* cache() stores its value; recordLoaderIdentityRead guards that.
|
|
873
886
|
*/
|
|
874
887
|
export function isInsideCacheScope(): boolean {
|
|
875
888
|
if (RangoContext.getStore()?.insideCacheScope !== true) return false;
|
|
@@ -886,44 +899,273 @@ export function isInsideCacheScope(): boolean {
|
|
|
886
899
|
}
|
|
887
900
|
|
|
888
901
|
/**
|
|
889
|
-
*
|
|
890
|
-
*
|
|
891
|
-
*
|
|
902
|
+
* What a refused identity read says (guardIdentityRead): its `verb`, and per
|
|
903
|
+
* refusing scope the text after "<surface> cannot be <verb> ...": why the
|
|
904
|
+
* value must not reach that scope, and what to do instead. `fix.warning` is
|
|
905
|
+
* the fix the capture's refusal warning gives (shell-capture.ts
|
|
906
|
+
* refuseOnCaptureGuard).
|
|
907
|
+
*/
|
|
908
|
+
export interface IdentityReadWording {
|
|
909
|
+
/** "called" for a function (cookies(), ctx.get()), "read" for a property (the theme getters). */
|
|
910
|
+
verb: LoaderIdentityReadVerb;
|
|
911
|
+
fix: {
|
|
912
|
+
useCache: string;
|
|
913
|
+
cacheScope: string;
|
|
914
|
+
capture: string;
|
|
915
|
+
warning: string;
|
|
916
|
+
};
|
|
917
|
+
/**
|
|
918
|
+
* False when the read is recorded later, by the returned view's read
|
|
919
|
+
* methods (cookies(), headers()): a view taken outside a loader and read
|
|
920
|
+
* inside one still counts, and a cookie write alone is not a read.
|
|
921
|
+
*/
|
|
922
|
+
record?: false;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* The one guard every request-identity read goes through: cookies(),
|
|
927
|
+
* headers(), the theme reads (cookie-store.ts readGuardedTheme), a
|
|
928
|
+
* non-cacheable ctx.get() (assertNonCacheableReadAllowed), and the raw reads
|
|
929
|
+
* `ctx.request.headers` and `getRequestContext().cookie()` / `.cookies()`
|
|
930
|
+
* (cookie-store.ts guardRequestHeaders, guardRawCookieRead; #976). Each surface keeps
|
|
931
|
+
* its own wording; the ladder and its exemptions are shared, so the surfaces
|
|
932
|
+
* refuse in exactly the same places:
|
|
892
933
|
*
|
|
893
|
-
*
|
|
894
|
-
*
|
|
895
|
-
*
|
|
896
|
-
*
|
|
897
|
-
*
|
|
898
|
-
*
|
|
899
|
-
*
|
|
934
|
+
* 0. A cache's own key(), store keyGenerator, condition() and tags(), and
|
|
935
|
+
* onError, read freely and record nothing (runIdentityExempt,
|
|
936
|
+
* cache/cache-exec-scope.ts): the value picks or labels the entry, or is
|
|
937
|
+
* only observed; it is never rendered. A cached body, loader body or
|
|
938
|
+
* funnel they start is guarded again.
|
|
939
|
+
* 1. A PPR shell capture (`ctx` is the capture's derived context,
|
|
940
|
+
* `_shellCaptureRun`) trips first: the capture context is flagged (so a
|
|
941
|
+
* caught throw still refuses the capture) and the read throws. The shell is
|
|
942
|
+
* shared per host+URL, and every HIT replays what the capture read. There
|
|
943
|
+
* is no loader-body exemption here: a bake-lane loader and a loader a
|
|
944
|
+
* handler awaits both bake.
|
|
945
|
+
* 2. A "use cache" body throws: the key does not include the value, so the
|
|
946
|
+
* first caller's would be stored and served to later callers. That holds
|
|
947
|
+
* for a loader body entered inside the cached function too (`await
|
|
948
|
+
* ctx.use(Loader)` there): its value is part of what the function returns.
|
|
949
|
+
* Before, a non-cacheable ctx.get() there was exempt while cookies() threw,
|
|
950
|
+
* and the entry stored the first request's value.
|
|
951
|
+
* 3. A cache() boundary throws, except inside a loader body
|
|
952
|
+
* (isInsideCacheScope): a route cache() never stores loader values.
|
|
953
|
+
* 4. The read is allowed, and recorded on the current loader execution for a
|
|
954
|
+
* loader cache() fill without key() (#972, recordLoaderIdentityRead)
|
|
955
|
+
* unless `wording.record` defers it to the returned view.
|
|
956
|
+
*
|
|
957
|
+
* Outside these scopes every read is allowed, a live loader's included. The
|
|
958
|
+
* response directives (invalidateClientCache(), keepClientCache()) record
|
|
959
|
+
* nothing and are not captured reads: they take refuseInCacheScope alone.
|
|
900
960
|
*/
|
|
901
|
-
export function
|
|
902
|
-
|
|
961
|
+
export function guardIdentityRead(
|
|
962
|
+
ctx: unknown,
|
|
963
|
+
surface: string,
|
|
964
|
+
wording: IdentityReadWording,
|
|
903
965
|
): void {
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
loaderBodyScopeALS.getStore()?.execScope !== execScope
|
|
908
|
-
) {
|
|
909
|
-
const name = typeof keyOrVar === "string" ? ` "${keyOrVar}"` : "";
|
|
966
|
+
if (isInsideIdentityExempt()) return;
|
|
967
|
+
const { verb, fix } = wording;
|
|
968
|
+
if (tripShellCaptureGuard(ctx, surface, fix.warning)) {
|
|
910
969
|
throw new Error(
|
|
911
|
-
|
|
912
|
-
`
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
970
|
+
`${surface} cannot be ${verb} while capturing a shared shell ` +
|
|
971
|
+
`(ppr shell capture). ${fix.capture}`,
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
refuseInCacheScope(surface, wording);
|
|
975
|
+
if (wording.record !== false) recordLoaderIdentityRead(surface, verb);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* Steps 2 and 3 of guardIdentityRead: throw when a "use cache" body or a
|
|
980
|
+
* cache() boundary would store what `surface` produces.
|
|
981
|
+
*/
|
|
982
|
+
export function refuseInCacheScope(
|
|
983
|
+
surface: string,
|
|
984
|
+
{ verb, fix }: IdentityReadWording,
|
|
985
|
+
): void {
|
|
986
|
+
if (isInsideCacheExecScope()) {
|
|
987
|
+
throw new Error(
|
|
988
|
+
`${surface} cannot be ${verb} inside a "use cache" function. ${fix.useCache}`,
|
|
916
989
|
);
|
|
917
990
|
}
|
|
918
991
|
if (isInsideCacheScope()) {
|
|
919
992
|
throw new Error(
|
|
920
|
-
|
|
921
|
-
`The variable was created with { cache: false } or set with { cache: false }, ` +
|
|
922
|
-
`and its value would be stale on cache hit. Move the read outside the cached scope.`,
|
|
993
|
+
`${surface} cannot be ${verb} inside a cache() boundary. ${fix.cacheScope}`,
|
|
923
994
|
);
|
|
924
995
|
}
|
|
925
996
|
}
|
|
926
997
|
|
|
998
|
+
/**
|
|
999
|
+
* The capture refusal for a request-scoped read (cookies(), headers(), a
|
|
1000
|
+
* { cache: false } variable): the message after the surface, and the fix the
|
|
1001
|
+
* refusal warning gives. `what` names the data that would leak.
|
|
1002
|
+
*/
|
|
1003
|
+
export function requestReadCaptureFix(
|
|
1004
|
+
what: string,
|
|
1005
|
+
): Pick<IdentityReadWording["fix"], "capture" | "warning"> {
|
|
1006
|
+
return {
|
|
1007
|
+
capture:
|
|
1008
|
+
`The captured shell is served to every user of this URL, so ` +
|
|
1009
|
+
`request-scoped data read here would leak one user's ${what} to ` +
|
|
1010
|
+
`others. Read it inside a loader without ssr: false and consume it ` +
|
|
1011
|
+
`with useLoader, e.g. createLoader(async () => ` +
|
|
1012
|
+
`getUser(cookies().get("session")?.value)). ${PPR_LANE_HINT}`,
|
|
1013
|
+
warning:
|
|
1014
|
+
"Read it in a loader without ssr: false and consume it with useLoader " +
|
|
1015
|
+
"under loading() or an inline <Suspense> (a live hole). A promise the " +
|
|
1016
|
+
"handler passes or pushes does not help: the capture waits for it.",
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* True when `ctx` is the active capture render: the derived request context
|
|
1022
|
+
* shell-capture.ts builds (`_shellCaptureRun`), which only the capture sets,
|
|
1023
|
+
* so the foreground render reads identity normally to serve the real user.
|
|
1024
|
+
* On true the capture context is flagged with the read (`surface`, e.g.
|
|
1025
|
+
* "cookies()", "ctx.theme"), the fix the refusal warning gives, and the loader
|
|
1026
|
+
* body (if any) that made the read, and the caller throws.
|
|
1027
|
+
*/
|
|
1028
|
+
function tripShellCaptureGuard(
|
|
1029
|
+
ctx: unknown,
|
|
1030
|
+
surface: string,
|
|
1031
|
+
fix: string,
|
|
1032
|
+
): boolean {
|
|
1033
|
+
if (
|
|
1034
|
+
ctx === null ||
|
|
1035
|
+
typeof ctx !== "object" ||
|
|
1036
|
+
(ctx as { _shellCaptureRun?: unknown })._shellCaptureRun !== true
|
|
1037
|
+
) {
|
|
1038
|
+
return false;
|
|
1039
|
+
}
|
|
1040
|
+
// Record WHICH loader body (if any) made the read, so the refusal warning
|
|
1041
|
+
// can name the real source instead of hardcoding a lane (issue #672).
|
|
1042
|
+
const flagged = ctx as {
|
|
1043
|
+
_shellCaptureGuardTripped?: { surface: string; fix: string };
|
|
1044
|
+
_shellCaptureGuardTrippedLoaderId?: string;
|
|
1045
|
+
};
|
|
1046
|
+
flagged._shellCaptureGuardTripped = { surface, fix };
|
|
1047
|
+
flagged._shellCaptureGuardTrippedLoaderId = getCurrentLoaderBodyId();
|
|
1048
|
+
return true;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
const NON_CACHEABLE_READ: IdentityReadWording = {
|
|
1052
|
+
verb: "called",
|
|
1053
|
+
fix: {
|
|
1054
|
+
useCache:
|
|
1055
|
+
"The variable was created with { cache: false } or set with " +
|
|
1056
|
+
"{ cache: false }, and the cache key does not include its value, so " +
|
|
1057
|
+
"the first caller's value would be served to later callers. Read it " +
|
|
1058
|
+
"before calling the cached function and pass the value in as an " +
|
|
1059
|
+
"argument so it becomes part of the cache key.",
|
|
1060
|
+
cacheScope:
|
|
1061
|
+
"The variable was created with { cache: false } or set with " +
|
|
1062
|
+
"{ cache: false }, and its value would be stale on cache hit. Move the " +
|
|
1063
|
+
"read outside the cached scope.",
|
|
1064
|
+
...requestReadCaptureFix("per-request variables"),
|
|
1065
|
+
},
|
|
1066
|
+
};
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* Read guard for a non-cacheable variable (`createVar({ cache: false })` or a
|
|
1070
|
+
* `ctx.set(..., { cache: false })` write), through guardIdentityRead.
|
|
1071
|
+
* Callers check isNonCacheable() first so ordinary reads never reach the
|
|
1072
|
+
* scope lookups. `requestCtx` is the ambient request context, whose capture
|
|
1073
|
+
* flag the guard reads.
|
|
1074
|
+
*/
|
|
1075
|
+
export function assertNonCacheableReadAllowed(
|
|
1076
|
+
keyOrVar: string | ContextVar<unknown>,
|
|
1077
|
+
requestCtx?: unknown,
|
|
1078
|
+
): void {
|
|
1079
|
+
const name = typeof keyOrVar === "string" ? ` "${keyOrVar}"` : "";
|
|
1080
|
+
guardIdentityRead(
|
|
1081
|
+
requestCtx,
|
|
1082
|
+
`ctx.get() for a non-cacheable variable${name}`,
|
|
1083
|
+
NON_CACHEABLE_READ,
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
/** How the identity error words a read: a function call or a property read. */
|
|
1088
|
+
export type LoaderIdentityReadVerb = "called" | "read";
|
|
1089
|
+
|
|
1090
|
+
/** A request-identity read a loader execution made (recordLoaderIdentityRead). */
|
|
1091
|
+
export interface LoaderIdentityRead {
|
|
1092
|
+
/**
|
|
1093
|
+
* The read as the error names it: "cookies()", "headers()",
|
|
1094
|
+
* `ctx.get() for a non-cacheable variable "x"`, "ctx.theme" or
|
|
1095
|
+
* "getRequestContext().theme".
|
|
1096
|
+
*/
|
|
1097
|
+
surface: string;
|
|
1098
|
+
/** "called" for a function, "read" for a property (the theme getters). */
|
|
1099
|
+
verb: LoaderIdentityReadVerb;
|
|
1100
|
+
/** The loader body that made the read. */
|
|
1101
|
+
bodyId: string | undefined;
|
|
1102
|
+
/**
|
|
1103
|
+
* The cached loader whose value carries a read another loader made
|
|
1104
|
+
* (loader-cache.ts identity mark): its readers read it through `via`.
|
|
1105
|
+
*/
|
|
1106
|
+
via?: string;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/**
|
|
1110
|
+
* Where cache-tag.ts installs its recorder at module init (same key there):
|
|
1111
|
+
* it owns the per-execution recorded sets, and importing it here would be a
|
|
1112
|
+
* cycle.
|
|
1113
|
+
*/
|
|
1114
|
+
const IDENTITY_READ_RECORDER_KEY = Symbol.for(
|
|
1115
|
+
"rangojs-router:identity-read-recorder",
|
|
1116
|
+
);
|
|
1117
|
+
|
|
1118
|
+
/**
|
|
1119
|
+
* Record a request-identity read (cookies(), headers(), a non-cacheable
|
|
1120
|
+
* ctx.get()) on the current loader execution (#972).
|
|
1121
|
+
*
|
|
1122
|
+
* isInsideCacheScope() exempts loader bodies because a route cache() never
|
|
1123
|
+
* stores their values. A loader bound with its own cache() does: with no key()
|
|
1124
|
+
* and no store keyGenerator its entry is keyed by loader, host, path and
|
|
1125
|
+
* params only, and an enclosing route cache() key does not partition it
|
|
1126
|
+
* (#974). A cookies() read there stored the first visitor's session and served
|
|
1127
|
+
* it to everyone for the TTL. The read is recorded on the execution, not
|
|
1128
|
+
* thrown at the call: a reader that starts the loader before its binding does
|
|
1129
|
+
* runs it outside any fill, and the binding's MISS then reuses that run. The
|
|
1130
|
+
* fill checks what its execution recorded, through the same links as its tags
|
|
1131
|
+
* (cache-tag.ts recordedIdentityRead), so both orders fail the same way.
|
|
1132
|
+
* Response directives do not record: a key cannot make a skipped body's side
|
|
1133
|
+
* effect reach a HIT.
|
|
1134
|
+
*/
|
|
1135
|
+
export function recordLoaderIdentityRead(
|
|
1136
|
+
surface: string,
|
|
1137
|
+
verb: LoaderIdentityReadVerb = "called",
|
|
1138
|
+
): void {
|
|
1139
|
+
if (isInsideIdentityExempt()) return;
|
|
1140
|
+
const recorder = (globalThis as Record<symbol, unknown>)[
|
|
1141
|
+
IDENTITY_READ_RECORDER_KEY
|
|
1142
|
+
] as ((surface: string, verb: LoaderIdentityReadVerb) => void) | undefined;
|
|
1143
|
+
recorder?.(surface, verb);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
/** The error a loader cache() fill with no declared key fails with (#972). */
|
|
1147
|
+
export function loaderCacheIdentityError(
|
|
1148
|
+
read: LoaderIdentityRead,
|
|
1149
|
+
cachedLoaderId: string,
|
|
1150
|
+
): Error {
|
|
1151
|
+
const filling = `while filling its own cache() entry with no key()`;
|
|
1152
|
+
const where =
|
|
1153
|
+
read.via !== undefined && read.via !== cachedLoaderId
|
|
1154
|
+
? `inside loader "${read.bodyId}", which loader "${cachedLoaderId}" reads through another loader ("${read.via}") ${filling}`
|
|
1155
|
+
: read.bodyId !== undefined && read.bodyId !== cachedLoaderId
|
|
1156
|
+
? `inside loader "${read.bodyId}", which loader "${cachedLoaderId}" reads ${filling}`
|
|
1157
|
+
: `inside loader "${cachedLoaderId}", whose own cache() has no key()`;
|
|
1158
|
+
return new Error(
|
|
1159
|
+
`${read.surface} cannot be ${read.verb} ${where}. ` +
|
|
1160
|
+
`The entry is keyed by loader, host, path and params only, so it is ` +
|
|
1161
|
+
`shared across users: request-scoped data (cookies, headers, ` +
|
|
1162
|
+
`non-cacheable variables) read here would be stored from one request ` +
|
|
1163
|
+
`and served to everyone. Add a key that includes the value, or drop ` +
|
|
1164
|
+
`the loader's cache():\n\n` +
|
|
1165
|
+
` cache({ ttl: 60, key: (ctx) => \`session:\${cookies().get("session")?.value}\` })`,
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
927
1169
|
/**
|
|
928
1170
|
* Check if the current execution is inside a DSL loader scope
|
|
929
1171
|
* (wrapped by runInsideLoaderScope). Used by rendered() barrier
|
|
@@ -952,7 +1194,7 @@ export function latchCachedHeaderScope(
|
|
|
952
1194
|
/** True inside ANY loader execution — DSL loader scope or a loader body
|
|
953
1195
|
* (however invoked). The "loaders always re-run fresh" exemptions key off
|
|
954
1196
|
* this. */
|
|
955
|
-
function isInsideAnyLoaderScope(): boolean {
|
|
1197
|
+
export function isInsideAnyLoaderScope(): boolean {
|
|
956
1198
|
return (
|
|
957
1199
|
loaderScopeALS.getStore()?.active === true ||
|
|
958
1200
|
loaderBodyScopeALS.getStore()?.active === true
|
|
@@ -1097,32 +1339,41 @@ export function runInsideLoaderScope<T>(fn: () => T): T {
|
|
|
1097
1339
|
|
|
1098
1340
|
/**
|
|
1099
1341
|
* Run `fn` inside a loader BODY scope. Marks loader-function execution for the
|
|
1100
|
-
* cache
|
|
1342
|
+
* cache() purity guard only (isInsideCacheScope), WITHOUT affecting
|
|
1101
1343
|
* isInsideLoaderScope()/rendered() gating. Applied to every loader body (DSL
|
|
1102
1344
|
* and handler-invoked via ctx.use) so request-scoped reads inside a loader
|
|
1103
|
-
* never trip the cache
|
|
1345
|
+
* never trip the cache() guard — a route cache() never stores loader values.
|
|
1346
|
+
* A "use cache" body the loader was entered in still refuses them
|
|
1347
|
+
* (guardIdentityRead).
|
|
1104
1348
|
*/
|
|
1105
1349
|
export function runInsideLoaderBodyScope<T>(
|
|
1106
1350
|
fn: () => T,
|
|
1107
1351
|
loaderId?: string,
|
|
1108
|
-
|
|
1352
|
+
tags?: Set<string>,
|
|
1109
1353
|
): T {
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1354
|
+
// A loader a key() starts with ctx.use() runs guarded (runIdentityExempt).
|
|
1355
|
+
return endIdentityExempt(() =>
|
|
1356
|
+
loaderBodyScopeALS.run(
|
|
1357
|
+
{
|
|
1358
|
+
active: true,
|
|
1359
|
+
loaderId,
|
|
1360
|
+
parent: loaderBodyScopeALS.getStore(),
|
|
1361
|
+
tags,
|
|
1362
|
+
},
|
|
1363
|
+
fn,
|
|
1364
|
+
),
|
|
1119
1365
|
);
|
|
1120
1366
|
}
|
|
1121
1367
|
|
|
1368
|
+
/** The innermost loader body's execution tag set (runInsideLoaderBodyScope). */
|
|
1369
|
+
export function getLoaderBodyTags(): Set<string> | undefined {
|
|
1370
|
+
return loaderBodyScopeALS.getStore()?.tags;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1122
1373
|
/**
|
|
1123
1374
|
* The $$id of the loader whose body is currently executing, or undefined
|
|
1124
1375
|
* outside any loader body. Used by the shell-capture identity guard
|
|
1125
|
-
* (
|
|
1376
|
+
* (guardIdentityRead) so its refusal warning can name the loader that read
|
|
1126
1377
|
* cookies()/headers() instead of blaming a lane it cannot see — the old
|
|
1127
1378
|
* hardcoded "bake-lane loader" text misled a live-lane debugging session
|
|
1128
1379
|
* (issue #672, secondary).
|
|
@@ -1131,6 +1382,24 @@ export function getCurrentLoaderBodyId(): string | undefined {
|
|
|
1131
1382
|
return loaderBodyScopeALS.getStore()?.loaderId;
|
|
1132
1383
|
}
|
|
1133
1384
|
|
|
1385
|
+
/**
|
|
1386
|
+
* The innermost loader body running here, or around it through the
|
|
1387
|
+
* ctx.use(Loader) chain, whose id `match` accepts (HandleStore.push, the
|
|
1388
|
+
* shell capture's push funnel). A body `stop` accepts, and `match` does not,
|
|
1389
|
+
* ends the walk: nothing around it is returned.
|
|
1390
|
+
*/
|
|
1391
|
+
export function findEnclosingLoaderBody(
|
|
1392
|
+
match: (loaderId: string) => boolean,
|
|
1393
|
+
stop?: (loaderId: string) => boolean,
|
|
1394
|
+
): string | undefined {
|
|
1395
|
+
for (let s = loaderBodyScopeALS.getStore(); s; s = s.parent) {
|
|
1396
|
+
if (s.loaderId === undefined) continue;
|
|
1397
|
+
if (match(s.loaderId)) return s.loaderId;
|
|
1398
|
+
if (stop?.(s.loaderId)) return undefined;
|
|
1399
|
+
}
|
|
1400
|
+
return undefined;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1134
1403
|
/**
|
|
1135
1404
|
* True while `loaderId`'s body, or a loader it awaits via ctx.use at any
|
|
1136
1405
|
* depth, is executing. The loader-level cache records these pushes
|
|
@@ -1144,19 +1413,6 @@ export function isInsideLoaderBody(loaderId: string): boolean {
|
|
|
1144
1413
|
return false;
|
|
1145
1414
|
}
|
|
1146
1415
|
|
|
1147
|
-
/**
|
|
1148
|
-
* True while a HANDLER-invoked loader body (`await ctx.use(Loader)` from a
|
|
1149
|
-
* handler, not the DSL segment funnel) is executing. The consumption-lane
|
|
1150
|
-
* rule keys off this: handler consumption yields a BAKED copy in every shared
|
|
1151
|
-
* artifact — cache(), "use cache", and the PPR shell — so the shell-capture
|
|
1152
|
-
* identity guard (cookie-store.ts) permits cookies()/headers() here, exactly
|
|
1153
|
-
* like the cache-purity guards do. DSL segment loaders (live lane masked at
|
|
1154
|
-
* capture, bake lane guarded) never set the flag.
|
|
1155
|
-
*/
|
|
1156
|
-
export function isInsideHandlerInvokedLoaderBody(): boolean {
|
|
1157
|
-
return loaderBodyScopeALS.getStore()?.handlerInvoked === true;
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
1416
|
// Scope for handle PUSH CALLBACKS (push(() => ...), including async ones).
|
|
1161
1417
|
// A push callback's value is stored as-is; if it is a promise it is NOT tracked
|
|
1162
1418
|
// by handleStore.settled and does not block segment resolution, so a
|