@rangojs/router 0.22.1 → 0.24.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/dist/bin/rango.js +10 -4
- package/dist/testing/vitest.js +31 -1
- package/dist/types/browser/defer.d.ts +48 -0
- package/dist/types/browser/document-revealed.d.ts +17 -0
- package/dist/types/browser/event-controller.d.ts +7 -1
- package/dist/types/browser/merge-segment-loaders.d.ts +5 -2
- package/dist/types/browser/navigation-transaction.d.ts +21 -7
- package/dist/types/browser/partial-update.d.ts +39 -8
- package/dist/types/browser/react/NavigationProvider.d.ts +19 -5
- package/dist/types/browser/react/context.d.ts +3 -2
- package/dist/types/browser/react/filter-segment-order.d.ts +7 -0
- package/dist/types/browser/response-adapter.d.ts +7 -0
- package/dist/types/browser/types.d.ts +24 -10
- package/dist/types/browser/validate-redirect-origin.d.ts +11 -0
- package/dist/types/build/generate-route-types.d.ts +1 -1
- package/dist/types/build/route-types/router-processing.d.ts +1 -1
- package/dist/types/build/route-types/scan-filter.d.ts +22 -2
- package/dist/types/cache/cf/cf-cache-store.d.ts +16 -1
- package/dist/types/cache/cf/cf-cache-types.d.ts +23 -2
- package/dist/types/cache/document-cache-policy.d.ts +24 -0
- package/dist/types/cache/document-cache.d.ts +2 -10
- package/dist/types/client-urls/client-urls.d.ts +1 -1
- package/dist/types/client-urls/hoisted-revalidate.d.ts +13 -0
- package/dist/types/client-urls/navigation.d.ts +0 -37
- package/dist/types/client-urls/revalidate-registry.d.ts +4 -0
- package/dist/types/client-urls/server-projection.d.ts +29 -1
- package/dist/types/client-urls/types.d.ts +34 -69
- package/dist/types/client.d.ts +0 -1
- package/dist/types/context-var.d.ts +12 -0
- package/dist/types/errors.d.ts +15 -0
- package/dist/types/handles/deferred-resolution.d.ts +2 -1
- package/dist/types/handles/pushed-by.d.ts +31 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.rsc.d.ts +2 -0
- package/dist/types/loader-store.d.ts +8 -0
- package/dist/types/prefetch-markers.d.ts +11 -0
- package/dist/types/response-utils.d.ts +7 -0
- package/dist/types/reverse.d.ts +8 -8
- package/dist/types/route-definition/helpers-types.d.ts +27 -0
- package/dist/types/route-definition/rewrite.d.ts +56 -0
- package/dist/types/route-map-builder.d.ts +7 -2
- package/dist/types/router/basename.d.ts +7 -0
- package/dist/types/router/handler-context.d.ts +1 -5
- package/dist/types/router/internal-params.d.ts +8 -0
- package/dist/types/router/is-action.d.ts +2 -1
- package/dist/types/router/middleware-types.d.ts +15 -0
- package/dist/types/router/revalidation.d.ts +1 -2
- package/dist/types/router/route-trie-builder.d.ts +10 -0
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -2
- package/dist/types/router/segment-resolution/mask-nested.d.ts +4 -2
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +264 -0
- package/dist/types/rsc/full-payload.d.ts +11 -0
- package/dist/types/rsc/handler.d.ts +33 -0
- package/dist/types/rsc/helpers.d.ts +8 -0
- package/dist/types/rsc/loader-fetch.d.ts +16 -0
- package/dist/types/rsc/rewrite-passes.d.ts +74 -0
- package/dist/types/rsc/ssr-setup.d.ts +3 -1
- package/dist/types/server/context.d.ts +21 -1
- package/dist/types/server/handle-store.d.ts +9 -1
- package/dist/types/server/request-context.d.ts +52 -2
- package/dist/types/server/rewrite-trail.d.ts +49 -0
- package/dist/types/server/slot-accessors.d.ts +29 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/testing/flight.entry.d.ts +1 -1
- package/dist/types/testing/index.d.ts +4 -4
- package/dist/types/testing/internal/action-id.d.ts +10 -1
- package/dist/types/testing/internal/client-urls-projection.d.ts +16 -0
- package/dist/types/testing/internal/test-action.d.ts +8 -0
- package/dist/types/testing/render-route.d.ts +8 -0
- package/dist/types/testing/run-loader.d.ts +2 -1
- package/dist/types/testing/run-middleware.d.ts +20 -0
- package/dist/types/testing/run-revalidate.d.ts +74 -0
- package/dist/types/testing/run-transition-when.d.ts +2 -3
- package/dist/types/testing/serve-shell-request.d.ts +40 -1
- package/dist/types/testing/vitest.d.ts +11 -0
- package/dist/types/thenable-status.d.ts +6 -0
- package/dist/types/types/boundaries.d.ts +9 -0
- package/dist/types/types/handler-context.d.ts +14 -0
- package/dist/types/types/loader-types.d.ts +30 -0
- package/dist/types/types/request-scope.d.ts +7 -0
- package/dist/types/types/segments.d.ts +21 -4
- package/dist/types/urls/path-helper-types.d.ts +25 -4
- package/dist/types/urls/type-extraction.d.ts +6 -2
- package/dist/types/vite/discovery/discover-routers.d.ts +9 -0
- package/dist/types/vite/discovery/state.d.ts +14 -4
- package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +54 -0
- package/dist/types/vite/plugins/hoist/literal.d.ts +83 -0
- package/dist/types/vite/plugins/hoist/scope.d.ts +40 -0
- package/dist/types/vite/plugins/hoist/source.d.ts +41 -0
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +8 -11
- package/dist/types/vite/router-discovery.d.ts +10 -0
- package/dist/vite/index.js +974 -379
- package/package.json +1 -1
- package/skills/caching/SKILL.md +54 -5
- package/skills/catalog.json +3 -3
- package/skills/client-urls/SKILL.md +197 -24
- package/skills/cloudflare/SKILL.md +14 -9
- package/skills/hooks/SKILL.md +16 -3
- package/skills/hooks/data.md +3 -1
- package/skills/links/SKILL.md +24 -0
- package/skills/loader/SKILL.md +199 -2
- package/skills/middleware/SKILL.md +183 -2
- package/skills/migrate-nextjs/SKILL.md +2 -2
- package/skills/migrate-react-router/SKILL.md +10 -7
- package/skills/migrate-react-router/data-and-actions.md +1 -1
- package/skills/ppr/SKILL.md +22 -0
- package/skills/prerender/SKILL.md +22 -8
- package/skills/rango/SKILL.md +13 -7
- package/skills/route/SKILL.md +139 -0
- package/skills/server-actions/SKILL.md +27 -0
- package/skills/testing/SKILL.md +3 -2
- package/skills/testing/client-components.md +2 -1
- package/skills/testing/middleware.md +31 -10
- package/skills/testing/predicates.md +67 -32
- package/skills/testing/response-routes.md +1 -0
- package/skills/testing/serve-shell-request.md +107 -19
- package/skills/testing/setup.md +2 -2
- package/skills/typesafety/route-types.md +2 -2
- package/skills/use-cache/SKILL.md +4 -2
- package/skills/view-transitions/SKILL.md +26 -0
- package/src/__augment-tests__/augmented.check.ts +20 -0
- package/src/browser/defer.ts +82 -0
- package/src/browser/document-revealed.ts +52 -0
- package/src/browser/event-controller.ts +46 -32
- package/src/browser/merge-segment-loaders.ts +31 -18
- package/src/browser/navigation-bridge.ts +82 -16
- package/src/browser/navigation-client.ts +48 -36
- package/src/browser/navigation-store.ts +13 -0
- package/src/browser/navigation-transaction.ts +25 -8
- package/src/browser/partial-update.ts +554 -146
- package/src/browser/prefetch/fetch.ts +2 -1
- package/src/browser/react/Link.tsx +7 -9
- package/src/browser/react/NavigationProvider.tsx +88 -49
- package/src/browser/react/context.ts +3 -2
- package/src/browser/react/filter-segment-order.ts +13 -2
- package/src/browser/react/use-router.ts +2 -4
- package/src/browser/response-adapter.ts +20 -0
- package/src/browser/rsc-router.tsx +17 -45
- package/src/browser/server-action-bridge.ts +25 -63
- package/src/browser/types.ts +24 -10
- package/src/browser/validate-redirect-origin.ts +16 -0
- package/src/build/generate-route-types.ts +1 -0
- package/src/build/route-types/router-processing.ts +3 -6
- package/src/build/route-types/scan-filter.ts +45 -11
- package/src/cache/cf/cf-cache-store.ts +59 -21
- package/src/cache/cf/cf-cache-types.ts +24 -2
- package/src/cache/document-cache-policy.ts +131 -0
- package/src/cache/document-cache.ts +21 -107
- package/src/client-urls/client-urls.ts +22 -6
- package/src/client-urls/hoisted-revalidate.ts +25 -0
- package/src/client-urls/navigation.ts +0 -96
- package/src/client-urls/revalidate-registry.ts +30 -0
- package/src/client-urls/server-projection.ts +151 -65
- package/src/client-urls/types.ts +39 -71
- package/src/client.tsx +9 -5
- package/src/context-var.ts +32 -2
- package/src/errors.ts +34 -0
- package/src/handles/deferred-resolution.ts +28 -5
- package/src/handles/pushed-by.ts +148 -0
- package/src/index.rsc.ts +8 -0
- package/src/index.ts +7 -0
- package/src/loader-store.ts +18 -7
- package/src/prefetch-markers.ts +13 -0
- package/src/response-utils.ts +19 -0
- package/src/reverse.ts +17 -14
- package/src/route-content-wrapper.tsx +18 -4
- package/src/route-definition/dsl-helpers.ts +27 -4
- package/src/route-definition/helpers-types.ts +27 -1
- package/src/route-definition/redirect.ts +2 -15
- package/src/route-definition/rewrite.ts +146 -0
- package/src/route-map-builder.ts +14 -11
- package/src/router/basename.ts +20 -0
- package/src/router/handler-context.ts +3 -13
- package/src/router/intercept-resolution.ts +35 -16
- package/src/router/internal-params.ts +16 -0
- package/src/router/is-action.ts +2 -1
- package/src/router/loader-resolution.ts +85 -4
- package/src/router/match-api.ts +36 -1
- package/src/router/match-middleware/background-revalidation.ts +16 -4
- package/src/router/match-middleware/cache-lookup.ts +35 -7
- package/src/router/match-middleware/cache-store.ts +15 -1
- package/src/router/match-middleware/intercept-resolution.ts +4 -2
- package/src/router/match-result.ts +14 -2
- package/src/router/middleware-types.ts +16 -0
- package/src/router/middleware.ts +127 -49
- package/src/router/navigation-snapshot.ts +2 -1
- package/src/router/revalidation.ts +1 -1
- package/src/router/route-trie-builder.ts +28 -1
- package/src/router/segment-resolution/fresh.ts +22 -18
- package/src/router/segment-resolution/loader-cache.ts +2 -1
- package/src/router/segment-resolution/loader-mask.ts +5 -3
- package/src/router/segment-resolution/loader-snapshot.ts +18 -4
- package/src/router/segment-resolution/mask-nested.ts +8 -4
- package/src/router/segment-resolution/prefetch-deferral.ts +583 -0
- package/src/router/segment-resolution/revalidation.ts +215 -82
- package/src/rsc/full-payload.ts +37 -5
- package/src/rsc/handler.ts +82 -55
- package/src/rsc/helpers.ts +24 -0
- package/src/rsc/loader-fetch.ts +79 -24
- package/src/rsc/response-cache-serve.ts +18 -2
- package/src/rsc/response-route-handler.ts +4 -7
- package/src/rsc/rewrite-passes.ts +203 -0
- package/src/rsc/rsc-rendering.ts +54 -9
- package/src/rsc/shell-capture.ts +2 -1
- package/src/rsc/ssr-setup.ts +11 -5
- package/src/segment-loader-promise.ts +23 -20
- package/src/segment-system.tsx +60 -26
- package/src/server/context.ts +39 -5
- package/src/server/cookie-parse.ts +36 -12
- package/src/server/cookie-store.ts +7 -3
- package/src/server/handle-store.ts +152 -25
- package/src/server/request-context.ts +175 -64
- package/src/server/request-headers.ts +4 -14
- package/src/server/rewrite-trail.ts +190 -0
- package/src/server/slot-accessors.ts +57 -0
- package/src/server.ts +4 -0
- package/src/ssr/index.tsx +118 -16
- package/src/testing/action-form.ts +2 -11
- package/src/testing/dispatch.ts +138 -67
- package/src/testing/e2e/page-helpers.ts +3 -1
- package/src/testing/flight.entry.ts +1 -0
- package/src/testing/index.ts +7 -3
- package/src/testing/internal/action-id.ts +16 -1
- package/src/testing/internal/client-urls-projection.ts +70 -0
- package/src/testing/internal/test-action.ts +25 -0
- package/src/testing/render-route.tsx +16 -3
- package/src/testing/run-loader.ts +4 -1
- package/src/testing/run-middleware.ts +56 -11
- package/src/testing/run-revalidate.ts +142 -0
- package/src/testing/run-transition-when.ts +7 -8
- package/src/testing/serve-shell-request.ts +131 -8
- package/src/testing/vitest.ts +67 -1
- package/src/thenable-status.ts +11 -0
- package/src/types/boundaries.ts +12 -0
- package/src/types/handler-context.ts +14 -0
- package/src/types/loader-types.ts +30 -0
- package/src/types/request-scope.ts +8 -0
- package/src/types/segments.ts +26 -4
- package/src/urls/path-helper-types.ts +27 -5
- package/src/urls/type-extraction.ts +30 -10
- package/src/use-loader.tsx +18 -13
- package/src/vite/discovery/client-urls-projection.ts +20 -21
- package/src/vite/discovery/discover-routers.ts +94 -26
- package/src/vite/discovery/state.ts +28 -4
- package/src/vite/discovery/virtual-module-codegen.ts +22 -17
- package/src/vite/plugins/client-urls-revalidate-hoist.ts +564 -0
- package/src/vite/plugins/expose-internal-ids.ts +2 -3
- package/src/vite/plugins/hoist/literal.ts +417 -0
- package/src/vite/plugins/hoist/scope.ts +370 -0
- package/src/vite/plugins/hoist/source.ts +84 -0
- package/src/vite/plugins/transition-when-hoist.ts +113 -779
- package/src/vite/rango.ts +2 -0
- package/src/vite/router-discovery.ts +116 -53
- package/dist/types/client-urls/revalidate-chain.d.ts +0 -33
- package/dist/types/client-urls/revalidation-protocol.d.ts +0 -25
- package/dist/types/testing/run-client-revalidate.d.ts +0 -43
- package/src/client-urls/revalidate-chain.ts +0 -83
- package/src/client-urls/revalidation-protocol.ts +0 -56
- package/src/testing/run-client-revalidate.ts +0 -92
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { requestHeaders } from "../server/request-headers.js";
|
|
2
1
|
import * as React from "react";
|
|
3
2
|
import type { SearchSchemaValue } from "../search-params.js";
|
|
4
3
|
import { getLoaderLazy } from "../server/loader-registry.js";
|
|
@@ -7,7 +6,11 @@ import {
|
|
|
7
6
|
getUrlPrefix,
|
|
8
7
|
type InterceptSelectorContext,
|
|
9
8
|
} from "../server/context.js";
|
|
10
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
LoaderDefinition,
|
|
11
|
+
ShouldRevalidateFn,
|
|
12
|
+
TrailingSlashMode,
|
|
13
|
+
} from "../types.js";
|
|
11
14
|
import type {
|
|
12
15
|
PartialPrerenderProps,
|
|
13
16
|
PathOptions,
|
|
@@ -16,11 +19,8 @@ import type {
|
|
|
16
19
|
import type { PathHelpers } from "../urls/path-helper-types.js";
|
|
17
20
|
import type { AllUseItems } from "../route-types.js";
|
|
18
21
|
import { urls } from "../urls/urls-function.js";
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
decodeClientRevalidationDecisions,
|
|
22
|
-
type ClientRevalidationDecisions,
|
|
23
|
-
} from "./revalidation-protocol.js";
|
|
22
|
+
import { isHoistedRevalidateRef } from "./hoisted-revalidate.js";
|
|
23
|
+
import { getClientUrlRevalidator } from "./revalidate-registry.js";
|
|
24
24
|
import {
|
|
25
25
|
ClientUrlsGroupLayout,
|
|
26
26
|
ClientUrlsInterceptLoading,
|
|
@@ -96,6 +96,16 @@ export interface ClientUrlProjectionRoute {
|
|
|
96
96
|
* document renders await them before first flush. Absent (= none) in
|
|
97
97
|
* projections serialized before the option existed. */
|
|
98
98
|
readonly awaitedLoaderIndices?: readonly number[];
|
|
99
|
+
/** Indices into loaderIds of loaders declared loader(Def, { prefetch:
|
|
100
|
+
* false }); materialization passes the option through to the server
|
|
101
|
+
* loader(), so a prefetch of the route defers them. Absent (= none) in
|
|
102
|
+
* projections serialized before the option existed. */
|
|
103
|
+
readonly deferrableLoaderIndices?: readonly number[];
|
|
104
|
+
/** Per loader (aligned with loaderIds), the keys of its hoisted
|
|
105
|
+
* revalidate() predicates in declaration order; materialization attaches
|
|
106
|
+
* the registered predicates (revalidate-registry.ts). Absent when no
|
|
107
|
+
* loader declares one. */
|
|
108
|
+
readonly loaderRevalidate?: readonly (readonly string[])[];
|
|
99
109
|
/** Data-only transition config (never `when`); absent in projections
|
|
100
110
|
* serialized before transition support. */
|
|
101
111
|
readonly transition?: Readonly<ClientTransitionConfig>;
|
|
@@ -268,7 +278,28 @@ function serializeTransition(
|
|
|
268
278
|
return Object.freeze(config) as Readonly<ClientTransitionConfig>;
|
|
269
279
|
}
|
|
270
280
|
|
|
271
|
-
|
|
281
|
+
/** Options for {@link serializeClientUrlPatterns}. */
|
|
282
|
+
export interface SerializeClientUrlPatternsOptions {
|
|
283
|
+
/**
|
|
284
|
+
* The definition was loaded through the rango Vite plugin (route
|
|
285
|
+
* discovery), which moves every revalidate() predicate to the server and
|
|
286
|
+
* leaves a marker: a plain function then is a site the build could not
|
|
287
|
+
* see, and fails loudly.
|
|
288
|
+
*/
|
|
289
|
+
readonly hoisted?: boolean;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function indicesWhere<T>(
|
|
293
|
+
items: readonly T[],
|
|
294
|
+
pred: (item: T) => boolean,
|
|
295
|
+
): number[] {
|
|
296
|
+
return items.flatMap((item, index) => (pred(item) ? [index] : []));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function serializeRoute(
|
|
300
|
+
route: ClientUrlRouteRecord,
|
|
301
|
+
options: SerializeClientUrlPatternsOptions,
|
|
302
|
+
): ClientUrlProjectionRoute {
|
|
272
303
|
const loaderIds = route.loaders.map(({ loader }, index) => {
|
|
273
304
|
if (typeof loader.$$id !== "string" || loader.$$id.length === 0) {
|
|
274
305
|
throw projectionError(
|
|
@@ -279,9 +310,29 @@ function serializeRoute(route: ClientUrlRouteRecord): ClientUrlProjectionRoute {
|
|
|
279
310
|
return loader.$$id;
|
|
280
311
|
});
|
|
281
312
|
|
|
282
|
-
const
|
|
283
|
-
.
|
|
284
|
-
|
|
313
|
+
const loaderRevalidate = route.loaders.map(({ loader, revalidate }) =>
|
|
314
|
+
revalidate.flatMap((entry) => {
|
|
315
|
+
if (isHoistedRevalidateRef(entry)) return [entry.$$rangoRevalidate];
|
|
316
|
+
if (options.hoisted) {
|
|
317
|
+
throw new Error(
|
|
318
|
+
`clientUrls() revalidate() on loader "${loader.$$id}" of route ` +
|
|
319
|
+
`"${route.pattern}" was not hoisted to the server: call the ` +
|
|
320
|
+
"builder's revalidate() helper with an inline function or an " +
|
|
321
|
+
"imported predicate.",
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
return [];
|
|
325
|
+
}),
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
const awaitedLoaderIndices = indicesWhere(
|
|
329
|
+
route.loaders,
|
|
330
|
+
({ ssr }) => ssr === false,
|
|
331
|
+
);
|
|
332
|
+
const deferrableLoaderIndices = indicesWhere(
|
|
333
|
+
route.loaders,
|
|
334
|
+
({ prefetch }) => prefetch === false,
|
|
335
|
+
);
|
|
285
336
|
|
|
286
337
|
const transition = serializeTransition(route);
|
|
287
338
|
return Object.freeze({
|
|
@@ -294,6 +345,16 @@ function serializeRoute(route: ClientUrlRouteRecord): ClientUrlProjectionRoute {
|
|
|
294
345
|
...(awaitedLoaderIndices.length > 0
|
|
295
346
|
? { awaitedLoaderIndices: Object.freeze(awaitedLoaderIndices) }
|
|
296
347
|
: {}),
|
|
348
|
+
...(deferrableLoaderIndices.length > 0
|
|
349
|
+
? { deferrableLoaderIndices: Object.freeze(deferrableLoaderIndices) }
|
|
350
|
+
: {}),
|
|
351
|
+
...(loaderRevalidate.some((keys) => keys.length > 0)
|
|
352
|
+
? {
|
|
353
|
+
loaderRevalidate: Object.freeze(
|
|
354
|
+
loaderRevalidate.map((keys) => Object.freeze(keys)),
|
|
355
|
+
),
|
|
356
|
+
}
|
|
357
|
+
: {}),
|
|
297
358
|
...(transition ? { transition } : {}),
|
|
298
359
|
...(typeof route.transition?.when === "function"
|
|
299
360
|
? { transitionWhen: true as const }
|
|
@@ -325,16 +386,41 @@ function serializeIntercept(
|
|
|
325
386
|
|
|
326
387
|
export function serializeClientUrlPatterns(
|
|
327
388
|
patterns: ClientUrlPatterns,
|
|
389
|
+
options: SerializeClientUrlPatternsOptions = {},
|
|
328
390
|
): ClientUrlProjection {
|
|
329
391
|
return Object.freeze({
|
|
330
392
|
version: 1 as const,
|
|
331
|
-
routes: Object.freeze(
|
|
393
|
+
routes: Object.freeze(
|
|
394
|
+
patterns.routes.map((route) => serializeRoute(route, options)),
|
|
395
|
+
),
|
|
332
396
|
intercepts: Object.freeze(
|
|
333
397
|
(patterns.intercepts ?? []).map(serializeIntercept),
|
|
334
398
|
),
|
|
335
399
|
});
|
|
336
400
|
}
|
|
337
401
|
|
|
402
|
+
/**
|
|
403
|
+
* The projection of a clientUrls() module from its namespace, or undefined
|
|
404
|
+
* when its default export is not a clientUrls() definition. Discovery
|
|
405
|
+
* (vite/discovery/client-urls-projection.ts) and the testing "use client"
|
|
406
|
+
* transform (testing/internal/client-urls-projection.ts) both project a
|
|
407
|
+
* module through it.
|
|
408
|
+
*/
|
|
409
|
+
export function projectClientUrlsModule(
|
|
410
|
+
namespace: Readonly<Record<string, unknown>>,
|
|
411
|
+
options: SerializeClientUrlPatternsOptions = {},
|
|
412
|
+
): ClientUrlProjection | undefined {
|
|
413
|
+
const definition = namespace.default;
|
|
414
|
+
if (
|
|
415
|
+
(typeof definition !== "object" && typeof definition !== "function") ||
|
|
416
|
+
definition === null ||
|
|
417
|
+
(definition as { __brand?: unknown }).__brand !== "client-urls"
|
|
418
|
+
) {
|
|
419
|
+
return undefined;
|
|
420
|
+
}
|
|
421
|
+
return serializeClientUrlPatterns(definition as ClientUrlPatterns, options);
|
|
422
|
+
}
|
|
423
|
+
|
|
338
424
|
export function isClientUrlPatterns(
|
|
339
425
|
value: unknown,
|
|
340
426
|
): value is ClientUrlPatterns {
|
|
@@ -425,53 +511,42 @@ export function clearClientUrlProjections(): void {
|
|
|
425
511
|
}
|
|
426
512
|
|
|
427
513
|
/**
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
Request,
|
|
434
|
-
ClientRevalidationDecisions | null
|
|
435
|
-
>();
|
|
436
|
-
|
|
437
|
-
function clientDecisionsFor(
|
|
438
|
-
request: Request | undefined,
|
|
439
|
-
): ClientRevalidationDecisions | null {
|
|
440
|
-
if (!request) return null;
|
|
441
|
-
if (decisionsByRequest.has(request)) {
|
|
442
|
-
return decisionsByRequest.get(request) ?? null;
|
|
443
|
-
}
|
|
444
|
-
const decoded = decodeClientRevalidationDecisions(
|
|
445
|
-
requestHeaders(request).get(CLIENT_REVALIDATION_HEADER),
|
|
446
|
-
);
|
|
447
|
-
decisionsByRequest.set(request, decoded);
|
|
448
|
-
return decoded;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* The server end of client-run per-loader revalidation: clientUrls()
|
|
453
|
-
* revalidate() predicates EXECUTE in the browser (they are client-module
|
|
454
|
-
* code), and their decisions arrive on the request header. This synthesized
|
|
455
|
-
* predicate — attached to every materialized loader stub — honors a decision
|
|
456
|
-
* addressed to its loader id and otherwise returns the locked default, which
|
|
457
|
-
* is also the behavior for requests that carry no decisions (no-JS, PE,
|
|
458
|
-
* prefetch, document loads). Decisions can only address client-urls stubs;
|
|
459
|
-
* server-tree loaders never see this predicate.
|
|
514
|
+
* The revalidate() predicates of one materialized loader. A definition
|
|
515
|
+
* object (unit tests, the testing primitives) carries its functions. A
|
|
516
|
+
* client reference resolves the projection's content keys in the registry
|
|
517
|
+
* the group's hoisted module filled when it loaded; a key with no
|
|
518
|
+
* registration fails loudly here, at materialization, never at a request.
|
|
460
519
|
*/
|
|
461
|
-
function
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
520
|
+
function loaderRevalidators(
|
|
521
|
+
reference: ClientUrlDefinitionSource,
|
|
522
|
+
route: ClientUrlProjectionRoute,
|
|
523
|
+
loaderIndex: number,
|
|
524
|
+
): ShouldRevalidateFn<any, any>[] {
|
|
525
|
+
const fromRegistry = (key: string): ShouldRevalidateFn<any, any> => {
|
|
526
|
+
if (!getClientUrlRevalidator(key)) {
|
|
527
|
+
const id = isClientUrlReference(reference) ? reference.$$id : "(inline)";
|
|
528
|
+
throw new Error(
|
|
529
|
+
`clientUrls() revalidate() on loader "${route.loaderIds[loaderIndex]}" ` +
|
|
530
|
+
`of route "${route.pattern}" in "${id}" has no server ` +
|
|
531
|
+
`registration (key "${key}"). The rango Vite plugin registers ` +
|
|
532
|
+
"hoisted predicates when the group's module loads in the RSC " +
|
|
533
|
+
"environment; a transform that changed the predicate's syntax " +
|
|
534
|
+
"in one environment only also causes this.",
|
|
535
|
+
);
|
|
472
536
|
}
|
|
473
|
-
|
|
537
|
+
// Looked up per call: a dev re-evaluation of the hoisted module
|
|
538
|
+
// re-registers the key with functions bound to the current imports.
|
|
539
|
+
return (args) => getClientUrlRevalidator(key)!(args);
|
|
474
540
|
};
|
|
541
|
+
if (isClientUrlPatterns(reference)) {
|
|
542
|
+
const record = reference.routes.find(({ id }) => id === route.id);
|
|
543
|
+
return (record?.loaders[loaderIndex]?.revalidate ?? []).map((entry) =>
|
|
544
|
+
isHoistedRevalidateRef(entry)
|
|
545
|
+
? fromRegistry(entry.$$rangoRevalidate)
|
|
546
|
+
: entry,
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
return (route.loaderRevalidate?.[loaderIndex] ?? []).map(fromRegistry);
|
|
475
550
|
}
|
|
476
551
|
|
|
477
552
|
function createLoaderStub(id: string): LoaderDefinition<unknown> {
|
|
@@ -551,15 +626,25 @@ function materializeRouteItems(
|
|
|
551
626
|
}),
|
|
552
627
|
materializedPathOptions(route, clientGroup),
|
|
553
628
|
() => [
|
|
554
|
-
...route.loaderIds.map((id, loaderIndex) =>
|
|
555
|
-
|
|
629
|
+
...route.loaderIds.map((id, loaderIndex) => {
|
|
630
|
+
const revalidators = loaderRevalidators(
|
|
631
|
+
reference,
|
|
632
|
+
route,
|
|
633
|
+
loaderIndex,
|
|
634
|
+
);
|
|
635
|
+
return loader(
|
|
556
636
|
createLoaderStub(id),
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
637
|
+
{
|
|
638
|
+
...(route.awaitedLoaderIndices?.includes(loaderIndex) && {
|
|
639
|
+
ssr: false,
|
|
640
|
+
}),
|
|
641
|
+
...(route.deferrableLoaderIndices?.includes(loaderIndex) && {
|
|
642
|
+
prefetch: false,
|
|
643
|
+
}),
|
|
644
|
+
},
|
|
645
|
+
() => revalidators.map((fn) => revalidate(fn)),
|
|
646
|
+
);
|
|
647
|
+
}),
|
|
563
648
|
...(route.hasLoading
|
|
564
649
|
? [
|
|
565
650
|
loading(
|
|
@@ -703,7 +788,8 @@ export function clientUrlIncludePatterns(
|
|
|
703
788
|
throw new Error(
|
|
704
789
|
`include() could not resolve the server projection for clientUrls() module "${id}". ` +
|
|
705
790
|
'The definition must be the default export of a "use client" module so the ' +
|
|
706
|
-
"Vite discovery pass can project it; in
|
|
791
|
+
"Vite discovery pass can project it; in a Vitest project, rangoUseClientTransform() " +
|
|
792
|
+
"from @rangojs/router/testing/vitest projects it.",
|
|
707
793
|
);
|
|
708
794
|
}
|
|
709
795
|
return materializeRouteItems(source, projection, helpers);
|
package/src/client-urls/types.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { ComponentType, ReactNode } from "react";
|
|
2
2
|
import type {
|
|
3
|
-
IsActionFn,
|
|
4
3
|
LoaderDefinition,
|
|
5
4
|
LoaderOptions,
|
|
5
|
+
ShouldRevalidateFn,
|
|
6
6
|
TransitionConfig,
|
|
7
7
|
} from "../types.js";
|
|
8
|
+
import type { DefaultEnv } from "../types/global-namespace.js";
|
|
9
|
+
import type { HoistedRevalidateRef } from "./hoisted-revalidate.js";
|
|
8
10
|
import type { TrieMatchResult } from "../router/trie-matching.js";
|
|
9
11
|
import type { PathOptions } from "../urls/pattern-types.js";
|
|
10
12
|
import type { SearchSchema } from "../search-params.js";
|
|
@@ -50,69 +52,17 @@ export type ClientLayoutFn = <const TItems extends ClientUrlItems>(
|
|
|
50
52
|
children: () => TItems,
|
|
51
53
|
) => ClientUrlItem & TypedLayoutItem<ExtractRoutes<TItems>>;
|
|
52
54
|
|
|
53
|
-
/**
|
|
54
|
-
* Arguments a clientUrls() revalidate() predicate receives. A client-computable
|
|
55
|
-
* subset of the server ShouldRevalidateFn args — the predicate RUNS IN THE
|
|
56
|
-
* BROWSER (it is declared in a "use client" module and never crosses the
|
|
57
|
-
* projection boundary); only its decision is sent to the server. There is no
|
|
58
|
-
* `context` — no server handler context exists where this executes. `isAction`
|
|
59
|
-
* is the same callable matcher as on the server, not a boolean.
|
|
60
|
-
*/
|
|
61
|
-
export interface ClientRevalidateArgs {
|
|
62
|
-
/** Full URL of the page being navigated away from (current location). */
|
|
63
|
-
readonly currentUrl: URL;
|
|
64
|
-
/** Full URL of the navigation target (equals currentUrl for actions). */
|
|
65
|
-
readonly nextUrl: URL;
|
|
66
|
-
/** Route params of the held client route (definition-local match). */
|
|
67
|
-
readonly currentParams: Record<string, string>;
|
|
68
|
-
/** Route params for the navigation target (definition-local match). */
|
|
69
|
-
readonly nextParams: Record<string, string>;
|
|
70
|
-
/**
|
|
71
|
-
* The current default decision for this loader, computed client-side with
|
|
72
|
-
* the same rules the server applies to the request the decisions ride on:
|
|
73
|
-
* `true` when they ride the action POST itself, otherwise `true` when
|
|
74
|
-
* params/search changed. (An action-triggered refetch GET gets navigation
|
|
75
|
-
* defaults — matching the server — even though `isAction()` is true.)
|
|
76
|
-
* Earlier predicates' `{ defaultShouldRevalidate }` verdicts thread into
|
|
77
|
-
* this value, exactly like the server chain. Return it for default
|
|
78
|
-
* behavior plus your own conditions.
|
|
79
|
-
*/
|
|
80
|
-
readonly defaultShouldRevalidate: boolean;
|
|
81
|
-
/** True when this is a stale history-entry background revalidation. */
|
|
82
|
-
readonly stale: boolean;
|
|
83
|
-
/**
|
|
84
|
-
* Same {@link IsActionFn} the server `revalidate()` predicate receives.
|
|
85
|
-
* In the browser the match is against the action stub's hashed `$$id`
|
|
86
|
-
* (the id the action request carries) — not the RSC file-path `$id`.
|
|
87
|
-
*/
|
|
88
|
-
readonly isAction: IsActionFn;
|
|
89
|
-
/**
|
|
90
|
-
* The triggering server action's id, when this is an action. In the
|
|
91
|
-
* browser this is the hashed `hash#export` form (`$$id`), not the RSC
|
|
92
|
-
* file-path `src/...#export`. Prefer `isAction(ref)` — a substring of
|
|
93
|
-
* `path#export` will not match here in production.
|
|
94
|
-
*/
|
|
95
|
-
readonly actionId?: string;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Client-run per-loader predicate, with the same chain semantics as the
|
|
100
|
-
* server's `revalidate()` (src/router/revalidation.ts): a boolean is a HARD
|
|
101
|
-
* decision that short-circuits the rest of the chain; a
|
|
102
|
-
* `{ defaultShouldRevalidate }` object updates the running suggestion, which
|
|
103
|
-
* later predicates receive as their `defaultShouldRevalidate`; `void` /
|
|
104
|
-
* `null` / `undefined` defers to the current suggestion — so
|
|
105
|
-
* `isAction(CartActions) || undefined` defers to the locked default.
|
|
106
|
-
* Predicates must be synchronous; the object form requires a boolean value.
|
|
107
|
-
*/
|
|
108
|
-
export type ClientRevalidateFn = (
|
|
109
|
-
args: ClientRevalidateArgs,
|
|
110
|
-
) => boolean | { defaultShouldRevalidate: boolean } | null | void;
|
|
111
|
-
|
|
112
55
|
export interface ClientUrlLoaderRecord {
|
|
113
56
|
readonly loader: LoaderDefinition<any, any>;
|
|
114
|
-
/**
|
|
115
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Per-loader revalidate() predicates, run on the server; empty = the
|
|
59
|
+
* router's defaults. Under the rango Vite plugin the browser and SSR copies
|
|
60
|
+
* hold the marker the build left in place of each hoisted predicate.
|
|
61
|
+
*/
|
|
62
|
+
readonly revalidate: readonly (
|
|
63
|
+
| ShouldRevalidateFn<any, DefaultEnv>
|
|
64
|
+
| HoistedRevalidateRef
|
|
65
|
+
)[];
|
|
116
66
|
/**
|
|
117
67
|
* loader(Def, { ssr: false }): document renders await this loader before
|
|
118
68
|
* first flush (see {@link LoaderOptions}). Projected into the server tree,
|
|
@@ -120,6 +70,14 @@ export interface ClientUrlLoaderRecord {
|
|
|
120
70
|
* regardless.
|
|
121
71
|
*/
|
|
122
72
|
readonly ssr?: false;
|
|
73
|
+
/**
|
|
74
|
+
* loader(Def, { prefetch: false }): a prefetch of the route defers this
|
|
75
|
+
* loader and the click that adopts it runs it in its fill request (see
|
|
76
|
+
* {@link LoaderOptions}). Projected onto the materialized stub. Inside
|
|
77
|
+
* intercept() it is not recorded: intercept loaders ignore the flag, as in
|
|
78
|
+
* urls().
|
|
79
|
+
*/
|
|
80
|
+
readonly prefetch?: false;
|
|
123
81
|
}
|
|
124
82
|
|
|
125
83
|
/**
|
|
@@ -172,8 +130,7 @@ export interface ClientUrlHelpers {
|
|
|
172
130
|
readonly layout: ClientLayoutFn;
|
|
173
131
|
/**
|
|
174
132
|
* Attach a projected loader. The optional use callback may contain
|
|
175
|
-
* revalidate() only
|
|
176
|
-
* the function) is sent with the revalidation request.
|
|
133
|
+
* revalidate() only: a per-loader predicate the server runs, as in urls().
|
|
177
134
|
*
|
|
178
135
|
* Pass `{ ssr: false }` — the same knob as loading(fallback, { ssr:
|
|
179
136
|
* false }) — to await this loader before first flush on DOCUMENT requests
|
|
@@ -183,22 +140,33 @@ export interface ClientUrlHelpers {
|
|
|
183
140
|
* route the flag BAKES: the loader executes at shell capture and its
|
|
184
141
|
* settled return freezes into the shell (nested promises stay live
|
|
185
142
|
* holes).
|
|
143
|
+
*
|
|
144
|
+
* Pass `{ prefetch: false }` to keep this loader out of the route's
|
|
145
|
+
* prefetches (see {@link LoaderOptions}): an in-group click still shows
|
|
146
|
+
* the destination at once, commits the adopted prefetch with the other
|
|
147
|
+
* loaders' data, and runs only the flagged loaders in its one fill
|
|
148
|
+
* request. A `layout()` loader with the flag is flagged on every route
|
|
149
|
+
* below it. Inside intercept() the flag is accepted and ignored, as in
|
|
150
|
+
* urls().
|
|
186
151
|
*/
|
|
187
152
|
readonly loader: <TData>(
|
|
188
153
|
definition: LoaderDefinition<TData>,
|
|
189
|
-
optionsOrUse?: LoaderOptions | ClientUrlUse,
|
|
154
|
+
optionsOrUse?: Pick<LoaderOptions, "ssr" | "prefetch"> | ClientUrlUse,
|
|
190
155
|
use?: ClientUrlUse,
|
|
191
156
|
) => ClientUrlItem;
|
|
192
157
|
readonly loading: (component: ReactNode) => ClientUrlItem;
|
|
193
158
|
/**
|
|
194
159
|
* Per-loader revalidation predicate, valid inside a loader() use callback
|
|
195
|
-
* only.
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
160
|
+
* only. It runs ON THE SERVER with the same arguments as a urls() loader's
|
|
161
|
+
* revalidate() (`actionResult`, `formData`, `context`, `isAction`, route
|
|
162
|
+
* names, ...): the build moves it out of this "use client" module. It may
|
|
163
|
+
* reference imports and globals only; a binding of this module, `this`,
|
|
164
|
+
* `import()`, a browser global, an `async` function or an import from a
|
|
165
|
+
* "use client" module fails the build with its file and line.
|
|
200
166
|
*/
|
|
201
|
-
readonly revalidate: (
|
|
167
|
+
readonly revalidate: (
|
|
168
|
+
fn: ShouldRevalidateFn<any, DefaultEnv>,
|
|
169
|
+
) => ClientUrlItem;
|
|
202
170
|
/**
|
|
203
171
|
* Declare an intercept for a named route in THIS definition. The target is
|
|
204
172
|
* dot-local (`.detail`); scoping is module-local — only navigations whose
|
package/src/client.tsx
CHANGED
|
@@ -30,6 +30,7 @@ import { AuditedOutletProvider, OutletProvider } from "./outlet-provider.js";
|
|
|
30
30
|
import { INTERNAL_RANGO_SUSPENSE_AUDIT } from "./internal-suspense-audit.js";
|
|
31
31
|
import { MountContextProvider } from "./browser/react/mount-context.js";
|
|
32
32
|
import { getBoundaryContent } from "./segment-boundary-content.js";
|
|
33
|
+
import { asStream } from "./browser/defer.js";
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
36
|
* Render the content for a named parallel/intercept slot segment.
|
|
@@ -41,6 +42,13 @@ import { getBoundaryContent } from "./segment-boundary-content.js";
|
|
|
41
42
|
function renderSlotContent(segment: ResolvedSegment | null): ReactNode {
|
|
42
43
|
if (!segment) return null;
|
|
43
44
|
|
|
45
|
+
// A slot that streams with no loading() gets a boundary with no fallback
|
|
46
|
+
// around its promise. A deferred slot's hole (`prefetch: false`) is a
|
|
47
|
+
// Defer, not a promise: it is what its fill resolves, and the slot it
|
|
48
|
+
// stands for was awaited, so every later render of the page has the
|
|
49
|
+
// content here with no boundary around it. The hole takes that place, read
|
|
50
|
+
// by React (browser/defer.ts asStream): another element here and the slot
|
|
51
|
+
// is mounted again when the page is next rendered.
|
|
44
52
|
const content: ReactNode =
|
|
45
53
|
segment.loading || segment.component instanceof Promise ? (
|
|
46
54
|
<RouteContentWrapper
|
|
@@ -49,7 +57,7 @@ function renderSlotContent(segment: ResolvedSegment | null): ReactNode {
|
|
|
49
57
|
segmentId={segment.id}
|
|
50
58
|
/>
|
|
51
59
|
) : (
|
|
52
|
-
(segment.component ?? null)
|
|
60
|
+
((asStream(segment.component) as ReactNode) ?? null)
|
|
53
61
|
);
|
|
54
62
|
|
|
55
63
|
const hasOwnLoaders = !!(segment.loaderDataPromise && segment.loaderIds);
|
|
@@ -511,7 +519,3 @@ export type { ScopedReverseFunction, LocalReverseFunction } from "./reverse.js";
|
|
|
511
519
|
|
|
512
520
|
export type { LoaderDefinition } from "./types.js";
|
|
513
521
|
export type { ActionRef, IsActionFn } from "./types.js";
|
|
514
|
-
export type {
|
|
515
|
-
ClientRevalidateArgs,
|
|
516
|
-
ClientRevalidateFn,
|
|
517
|
-
} from "./client-urls/types.js";
|
package/src/context-var.ts
CHANGED
|
@@ -125,8 +125,38 @@ export function contextGet(
|
|
|
125
125
|
return variables[keyOrVar.key];
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
/**
|
|
129
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Keys that must never be used as string variable names, nor as a fetchable
|
|
130
|
+
* loader's request params (rsc/loader-fetch.ts).
|
|
131
|
+
*/
|
|
132
|
+
export const FORBIDDEN_KEYS: ReadonlySet<string> = new Set([
|
|
133
|
+
"__proto__",
|
|
134
|
+
"constructor",
|
|
135
|
+
"prototype",
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* `target[key] = value` as an own data property, `__proto__` included: that
|
|
140
|
+
* assignment would call Object.prototype's setter and replace the target's
|
|
141
|
+
* prototype, which Flight refuses to serialize (#1109). Loader data parsed
|
|
142
|
+
* from JSON, cookie names and the like can carry the key.
|
|
143
|
+
*/
|
|
144
|
+
export function assignOwn(
|
|
145
|
+
target: Record<string, unknown>,
|
|
146
|
+
key: string,
|
|
147
|
+
value: unknown,
|
|
148
|
+
): void {
|
|
149
|
+
if (key === "__proto__") {
|
|
150
|
+
Object.defineProperty(target, key, {
|
|
151
|
+
value,
|
|
152
|
+
writable: true,
|
|
153
|
+
enumerable: true,
|
|
154
|
+
configurable: true,
|
|
155
|
+
});
|
|
156
|
+
} else {
|
|
157
|
+
target[key] = value;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
130
160
|
|
|
131
161
|
export interface ContextSetOptions {
|
|
132
162
|
/**
|
package/src/errors.ts
CHANGED
|
@@ -296,6 +296,25 @@ export function isSkip(value: unknown): value is Skip {
|
|
|
296
296
|
return value instanceof Skip;
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
+
/**
|
|
300
|
+
* A request middleware rewrote (`throw rewrite()`) more than `maxHops` times,
|
|
301
|
+
* or to the URL it was on. Reported through `onError` (phase "routing") and
|
|
302
|
+
* answered with 508. `trail` lists the URLs, the visitor's first.
|
|
303
|
+
*/
|
|
304
|
+
export class RewriteLoopError extends Error {
|
|
305
|
+
name = "RewriteLoopError" as const;
|
|
306
|
+
readonly trail: readonly string[];
|
|
307
|
+
|
|
308
|
+
constructor(trail: readonly string[], maxHops: number) {
|
|
309
|
+
super(
|
|
310
|
+
`Infinite rewrite loop: ${trail.join(" -> ")}. A request is rewritten ` +
|
|
311
|
+
`at most ${maxHops} times, and never to the URL it is on.`,
|
|
312
|
+
);
|
|
313
|
+
Object.setPrototypeOf(this, RewriteLoopError.prototype);
|
|
314
|
+
this.trail = trail;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
299
318
|
/**
|
|
300
319
|
* Thrown by the partial updater when the server responds with a redirect payload
|
|
301
320
|
* that carries location state. Caught by navigate() to re-navigate to the
|
|
@@ -345,6 +364,21 @@ export function invariant(condition: any, message: string): asserts condition {
|
|
|
345
364
|
}
|
|
346
365
|
}
|
|
347
366
|
|
|
367
|
+
/**
|
|
368
|
+
* Rejects a boolean-only DSL option that holds anything else (undefined
|
|
369
|
+
* passes), as an invariant.
|
|
370
|
+
*/
|
|
371
|
+
export function assertBooleanOption(
|
|
372
|
+
value: unknown,
|
|
373
|
+
option: string,
|
|
374
|
+
effect: string,
|
|
375
|
+
): void {
|
|
376
|
+
invariant(
|
|
377
|
+
value === undefined || typeof value === "boolean",
|
|
378
|
+
`${option} must be a boolean (got ${JSON.stringify(value)}). Omit it (or pass true) to ${effect}.`,
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
348
382
|
/**
|
|
349
383
|
* Sanitize errors for production - prevents leaking sensitive information
|
|
350
384
|
*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { HandleData, HandleStore } from "../server/handle-store.js";
|
|
2
2
|
import { isThenable } from "./is-thenable.js";
|
|
3
|
+
import { isPushedByKey, pushedByKey } from "./pushed-by.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Runtime-neutral resolution of deferred (Promise) handle values. Shared by the
|
|
@@ -39,7 +40,8 @@ export function deferredHandleNames(data: HandleData): Set<string> {
|
|
|
39
40
|
* thenables). A rejected deferred is dropped, and a deferred that resolves to
|
|
40
41
|
* `null`/`undefined` is dropped (the `.defer({ else })` skip semantics), so a
|
|
41
42
|
* nullish deferred slot never reaches a collector. Sync (non-thenable) values —
|
|
42
|
-
* including a legitimate sync `null` push — pass through unchanged.
|
|
43
|
+
* including a legitimate sync `null` push — pass through unchanged. A
|
|
44
|
+
* pushedBy record (pushed-by.ts) loses the entries of the dropped values.
|
|
43
45
|
*/
|
|
44
46
|
export async function resolveDeferredHandleValues(
|
|
45
47
|
data: HandleData,
|
|
@@ -47,22 +49,37 @@ export async function resolveDeferredHandleValues(
|
|
|
47
49
|
const out: HandleData = {};
|
|
48
50
|
await Promise.all(
|
|
49
51
|
Object.entries(data).flatMap(([handleName, segments]) => {
|
|
52
|
+
if (isPushedByKey(handleName)) return [];
|
|
50
53
|
out[handleName] = {};
|
|
54
|
+
const byName = pushedByKey(handleName);
|
|
55
|
+
const pushedBy = data[byName];
|
|
51
56
|
return Object.entries(segments).map(async ([segmentId, values]) => {
|
|
52
|
-
|
|
57
|
+
const resolved = await resolveValues(values);
|
|
58
|
+
out[handleName][segmentId] = keptValues(resolved);
|
|
59
|
+
const by = pushedBy?.[segmentId];
|
|
60
|
+
if (by) {
|
|
61
|
+
(out[byName] ??= {})[segmentId] = by.filter(
|
|
62
|
+
(_, i) => resolved[i].keep,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
53
65
|
});
|
|
54
66
|
}),
|
|
55
67
|
);
|
|
56
68
|
return out;
|
|
57
69
|
}
|
|
58
70
|
|
|
71
|
+
interface ResolvedValue {
|
|
72
|
+
keep: boolean;
|
|
73
|
+
value: unknown;
|
|
74
|
+
}
|
|
75
|
+
|
|
59
76
|
// Resolve each entry IN ORDER. A non-thenable passes through verbatim (including
|
|
60
77
|
// a legitimate sync `null`/`undefined` push — the "await iff thenable" contract).
|
|
61
78
|
// A thenable is awaited and dropped when it rejects OR resolves to null/undefined
|
|
62
79
|
// (the `.defer({ else })` skip semantics: a forgotten resolver times out to
|
|
63
80
|
// `else ?? undefined`), so a nullish deferred slot never reaches a collector.
|
|
64
|
-
|
|
65
|
-
|
|
81
|
+
function resolveValues(values: unknown[]): Promise<ResolvedValue[]> {
|
|
82
|
+
return Promise.all(
|
|
66
83
|
values.map(async (v) => {
|
|
67
84
|
if (!isThenable(v)) return { keep: true, value: v };
|
|
68
85
|
try {
|
|
@@ -73,6 +90,9 @@ async function resolveValues(values: unknown[]): Promise<unknown[]> {
|
|
|
73
90
|
}
|
|
74
91
|
}),
|
|
75
92
|
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function keptValues(resolved: ResolvedValue[]): unknown[] {
|
|
76
96
|
return resolved.filter((r) => r.keep).map((r) => r.value);
|
|
77
97
|
}
|
|
78
98
|
|
|
@@ -141,8 +161,11 @@ export function recordedHandleData(
|
|
|
141
161
|
const recorded: HandleData = {};
|
|
142
162
|
for (const segmentId of segmentIds) {
|
|
143
163
|
const kept = handleStore.getDataForSegment(segmentId, true);
|
|
164
|
+
const pushedBy = handleStore.getPushedBy(segmentId, true);
|
|
144
165
|
for (const handleName in kept) {
|
|
145
166
|
(recorded[handleName] ??= {})[segmentId] = kept[handleName];
|
|
167
|
+
const by = pushedBy?.[handleName];
|
|
168
|
+
if (by) (recorded[pushedByKey(handleName)] ??= {})[segmentId] = by;
|
|
146
169
|
}
|
|
147
170
|
}
|
|
148
171
|
return recorded;
|
|
@@ -163,7 +186,7 @@ export async function resolveSegmentHandleValues(
|
|
|
163
186
|
const out: Record<string, unknown[]> = {};
|
|
164
187
|
await Promise.all(
|
|
165
188
|
Object.entries(segHandles).map(async ([handleName, values]) => {
|
|
166
|
-
out[handleName] = await resolveValues(values);
|
|
189
|
+
out[handleName] = keptValues(await resolveValues(values));
|
|
167
190
|
}),
|
|
168
191
|
);
|
|
169
192
|
return out;
|