@rangojs/router 0.15.1 → 0.17.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 +254 -89
- package/dist/testing/vitest.js +252 -0
- package/dist/types/browser/navigation-transaction.d.ts +6 -0
- package/dist/types/browser/partial-update.d.ts +11 -0
- package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
- package/dist/types/browser/rsc-router.d.ts +1 -1
- package/dist/types/browser/scroll-restoration.d.ts +7 -0
- package/dist/types/cache/cache-exec-scope.d.ts +18 -1
- package/dist/types/cache/cache-scope.d.ts +34 -5
- package/dist/types/cache/handle-capture.d.ts +41 -3
- package/dist/types/cache/handle-snapshot.d.ts +53 -13
- package/dist/types/cache/segment-codec.d.ts +10 -3
- package/dist/types/cache/taint.d.ts +1 -1
- package/dist/types/cache/types.d.ts +15 -3
- package/dist/types/client.d.ts +4 -4
- package/dist/types/client.rsc.d.ts +4 -5
- package/dist/types/components/DefaultDocument.d.ts +5 -2
- package/dist/types/context-var.d.ts +6 -5
- package/dist/types/escape-script.d.ts +1 -1
- package/dist/types/handles/MetaTags.d.ts +5 -4
- package/dist/types/handles/Scripts.d.ts +8 -7
- package/dist/types/handles/meta.d.ts +2 -2
- package/dist/types/handles/script.d.ts +17 -16
- package/dist/types/href-client.d.ts +1 -1
- package/dist/types/html.d.ts +12 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +3 -2
- package/dist/types/loader-store.d.ts +52 -0
- package/dist/types/reverse.d.ts +36 -4
- package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
- package/dist/types/route-definition/helpers-types.d.ts +36 -10
- package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
- package/dist/types/route-types.d.ts +3 -1
- package/dist/types/router/debug-manifest.d.ts +3 -2
- package/dist/types/router/error-handling.d.ts +20 -0
- package/dist/types/router/handler-context.d.ts +6 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
- package/dist/types/router/middleware-types.d.ts +3 -2
- package/dist/types/router/router-context.d.ts +3 -1
- package/dist/types/router/router-interfaces.d.ts +24 -2
- package/dist/types/router/router-options.d.ts +8 -5
- package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +37 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +3 -3
- package/dist/types/router/segment-resolution/mask-nested.d.ts +7 -7
- package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
- package/dist/types/router/segment-wrappers.d.ts +1 -1
- package/dist/types/rsc/response-route-handler.d.ts +1 -1
- package/dist/types/rsc/rsc-rendering.d.ts +1 -2
- package/dist/types/rsc/shell-capture-constants.d.ts +8 -0
- package/dist/types/rsc/shell-capture.d.ts +5 -4
- package/dist/types/rsc/ssr-setup.d.ts +6 -6
- package/dist/types/rsc/types.d.ts +10 -0
- package/dist/types/server/context.d.ts +33 -3
- package/dist/types/server/handle-store.d.ts +26 -2
- package/dist/types/server/request-context.d.ts +24 -21
- package/dist/types/ssr/index.d.ts +15 -0
- package/dist/types/static-handler.d.ts +4 -1
- package/dist/types/testing/generated-routes.d.ts +2 -2
- package/dist/types/testing/internal/context.d.ts +1 -1
- package/dist/types/testing/render-route.d.ts +32 -6
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
- package/dist/types/testing/vitest.d.ts +35 -1
- package/dist/types/theme/ThemeScript.d.ts +3 -3
- package/dist/types/thenable-status.d.ts +9 -0
- package/dist/types/types/cache-types.d.ts +14 -10
- package/dist/types/types/handler-context.d.ts +14 -0
- package/dist/types/types/loader-types.d.ts +8 -6
- package/dist/types/urls/response-types.d.ts +3 -12
- package/dist/types/use-loader.d.ts +12 -3
- package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
- package/dist/types/vite/rango.d.ts +1 -1
- package/dist/vite/index.js +884 -768
- package/package.json +4 -4
- package/skills/api-client/SKILL.md +22 -10
- package/skills/breadcrumbs/SKILL.md +65 -22
- package/skills/bundle-analysis/SKILL.md +22 -22
- package/skills/cache-guide/SKILL.md +207 -97
- package/skills/caching/SKILL.md +295 -93
- package/skills/catalog.json +16 -16
- package/skills/client-urls/SKILL.md +53 -31
- package/skills/cloudflare/SKILL.md +72 -20
- package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
- package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
- package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
- package/skills/comparison/SKILL.md +5 -0
- package/skills/comparison/references/framework-comparison.md +39 -33
- package/skills/composability/SKILL.md +38 -29
- package/skills/css/SKILL.md +10 -13
- package/skills/debug-manifest/SKILL.md +74 -31
- package/skills/defer-hydration/SKILL.md +2 -1
- package/skills/deployment-caching/SKILL.md +6 -1
- package/skills/document-cache/SKILL.md +92 -28
- package/skills/fonts/SKILL.md +16 -8
- package/skills/handler-use/SKILL.md +53 -17
- package/skills/hooks/SKILL.md +28 -20
- package/skills/hooks/data.md +43 -1
- package/skills/hooks/handle-and-actions.md +31 -16
- package/skills/hooks/navigation.md +39 -10
- package/skills/hooks/outlets.md +13 -6
- package/skills/hooks/state.md +37 -15
- package/skills/hooks/urls.md +7 -5
- package/skills/host-router/SKILL.md +64 -12
- package/skills/i18n/SKILL.md +60 -34
- package/skills/intercept/SKILL.md +161 -26
- package/skills/layout/SKILL.md +106 -50
- package/skills/links/SKILL.md +51 -19
- package/skills/loader/SKILL.md +242 -106
- package/skills/middleware/SKILL.md +119 -23
- package/skills/migrate-nextjs/SKILL.md +100 -45
- package/skills/migrate-nextjs/backend-host-swap.md +5 -2
- package/skills/migrate-react-router/SKILL.md +49 -38
- package/skills/migrate-react-router/component-migration.md +25 -6
- package/skills/migrate-react-router/data-and-actions.md +18 -5
- package/skills/migrate-react-router/route-mapping.md +12 -6
- package/skills/mime-routes/SKILL.md +12 -7
- package/skills/observability/SKILL.md +28 -14
- package/skills/parallel/SKILL.md +75 -39
- package/skills/ppr/SKILL.md +345 -150
- package/skills/prerender/SKILL.md +189 -100
- package/skills/rango/SKILL.md +65 -34
- package/skills/react-compiler/SKILL.md +6 -5
- package/skills/response-routes/SKILL.md +133 -25
- package/skills/route/SKILL.md +165 -60
- package/skills/router-setup/SKILL.md +418 -240
- package/skills/scripts/SKILL.md +31 -20
- package/skills/server-actions/SKILL.md +220 -59
- package/skills/shell-manifest/SKILL.md +12 -7
- package/skills/streams-and-websockets/SKILL.md +32 -14
- package/skills/tailwind/SKILL.md +37 -33
- package/skills/testing/SKILL.md +68 -32
- package/skills/testing/cache-prerender.md +55 -29
- package/skills/testing/client-components.md +59 -6
- package/skills/testing/e2e-parity.md +12 -2
- package/skills/testing/flight.md +19 -11
- package/skills/testing/handles.md +8 -5
- package/skills/testing/loader.md +8 -5
- package/skills/testing/middleware.md +8 -3
- package/skills/testing/predicates.md +120 -0
- package/skills/testing/render-handler.md +70 -22
- package/skills/testing/response-routes.md +11 -4
- package/skills/testing/reverse-and-types.md +20 -14
- package/skills/testing/server-actions.md +12 -3
- package/skills/testing/server-tree.md +9 -0
- package/skills/testing/setup.md +9 -5
- package/skills/theme/SKILL.md +106 -29
- package/skills/typesafety/SKILL.md +20 -9
- package/skills/typesafety/env-and-bindings.md +126 -47
- package/skills/typesafety/generated-files-and-cli.md +26 -14
- package/skills/typesafety/params-and-search.md +44 -19
- package/skills/typesafety/route-types.md +50 -14
- package/skills/use-cache/SKILL.md +200 -82
- package/skills/vercel/SKILL.md +21 -11
- package/skills/view-transitions/SKILL.md +41 -20
- package/src/__augment-tests__/augmented.check.ts +26 -1
- package/src/browser/index.ts +2 -2
- package/src/browser/navigation-bridge.ts +3 -4
- package/src/browser/navigation-transaction.ts +22 -2
- package/src/browser/partial-update.ts +75 -80
- package/src/browser/react/ScrollRestoration.tsx +6 -6
- package/src/browser/rsc-router.tsx +1 -1
- package/src/browser/scroll-restoration.ts +53 -6
- package/src/cache/cache-exec-scope.ts +37 -4
- package/src/cache/cache-key-utils.ts +1 -6
- package/src/cache/cache-runtime.ts +230 -61
- package/src/cache/cache-scope.ts +76 -12
- package/src/cache/document-cache.ts +15 -0
- package/src/cache/handle-capture.ts +90 -10
- package/src/cache/handle-snapshot.ts +132 -28
- package/src/cache/segment-codec.ts +26 -7
- package/src/cache/tag-invalidation.ts +4 -3
- package/src/cache/taint.ts +1 -1
- package/src/cache/types.ts +16 -3
- package/src/client.rsc.tsx +10 -9
- package/src/client.tsx +5 -4
- package/src/components/DefaultDocument.tsx +7 -2
- package/src/context-var.ts +6 -5
- package/src/escape-script.ts +1 -1
- package/src/handles/MetaTags.tsx +13 -11
- package/src/handles/Scripts.tsx +11 -10
- package/src/handles/meta.ts +2 -2
- package/src/handles/script.ts +17 -16
- package/src/href-client.ts +1 -1
- package/src/html.ts +12 -0
- package/src/index.rsc.ts +5 -0
- package/src/index.ts +1 -0
- package/src/loader-store.ts +106 -0
- package/src/prerender/build-shell-capture.ts +8 -11
- package/src/reverse.ts +44 -4
- package/src/route-definition/dsl-helpers.ts +134 -42
- package/src/route-definition/helpers-types.ts +36 -10
- package/src/route-definition/resolve-handler-use.ts +121 -6
- package/src/route-types.ts +2 -3
- package/src/router/debug-manifest.ts +10 -2
- package/src/router/error-handling.ts +51 -15
- package/src/router/handler-context.ts +34 -25
- package/src/router/intercept-resolution.ts +125 -39
- package/src/router/loader-resolution.ts +56 -11
- package/src/router/match-api.ts +12 -17
- package/src/router/match-middleware/background-revalidation.ts +161 -96
- package/src/router/match-middleware/cache-lookup.ts +93 -10
- package/src/router/match-middleware/cache-store.ts +17 -90
- package/src/router/middleware-types.ts +3 -5
- package/src/router/middleware.ts +52 -9
- package/src/router/prerender-match.ts +53 -19
- package/src/router/route-snapshot.ts +11 -1
- package/src/router/router-context.ts +2 -0
- package/src/router/router-interfaces.ts +25 -2
- package/src/router/router-options.ts +8 -5
- package/src/router/segment-resolution/fresh.ts +44 -12
- package/src/router/segment-resolution/loader-cache.ts +205 -45
- package/src/router/segment-resolution/loader-mask.ts +12 -22
- package/src/router/segment-resolution/loader-snapshot.ts +6 -6
- package/src/router/segment-resolution/mask-nested.ts +7 -7
- package/src/router/segment-resolution/revalidation.ts +34 -6
- package/src/router/segment-wrappers.ts +4 -0
- package/src/rsc/render-pipeline.ts +3 -0
- package/src/rsc/response-route-handler.ts +11 -3
- package/src/rsc/rsc-rendering.ts +8 -45
- package/src/rsc/shell-capture-constants.ts +13 -0
- package/src/rsc/shell-capture.ts +111 -90
- package/src/rsc/ssr-setup.ts +14 -8
- package/src/rsc/types.ts +11 -0
- package/src/server/context.ts +88 -11
- package/src/server/cookie-store.ts +5 -4
- package/src/server/handle-store.ts +232 -7
- package/src/server/request-context.ts +35 -30
- package/src/ssr/index.tsx +40 -7
- package/src/ssr-suspension-warning.ts +2 -13
- package/src/static-handler.ts +4 -1
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/generated-routes.ts +2 -2
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/internal/flight-client-globals.ts +7 -5
- package/src/testing/render-route.tsx +85 -24
- package/src/testing/run-loader.ts +5 -1
- package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
- package/src/testing/vitest.ts +62 -1
- package/src/theme/ThemeProvider.tsx +14 -12
- package/src/theme/ThemeScript.tsx +3 -3
- package/src/thenable-status.ts +16 -0
- package/src/types/cache-types.ts +14 -10
- package/src/types/handler-context.ts +14 -0
- package/src/types/loader-types.ts +8 -6
- package/src/urls/response-types.ts +3 -23
- package/src/use-loader.tsx +24 -4
- package/src/vite/plugins/use-cache-transform.ts +118 -74
- package/src/vite/rango.ts +7 -3
package/src/cache/taint.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Taint symbol for request-scoped objects.
|
|
3
3
|
*
|
|
4
|
-
* Objects branded with NOCACHE_SYMBOL (ctx
|
|
4
|
+
* Objects branded with NOCACHE_SYMBOL (ctx objects) are excluded from
|
|
5
5
|
* "use cache" cache keys and trigger handle capture mode so that side
|
|
6
6
|
* effects (breadcrumbs, metadata) are recorded and replayed on cache hit.
|
|
7
7
|
*/
|
package/src/cache/types.ts
CHANGED
|
@@ -424,9 +424,8 @@ export type DocumentShellCacheEntry = ShellCacheEntry & {
|
|
|
424
424
|
/**
|
|
425
425
|
* The families a shell snapshot pins. The item/segment/response families are
|
|
426
426
|
* cache-store reads/writes (recorded by RecordingShellStore); the loader family
|
|
427
|
-
* pins the settled CONTAINER of a bake-lane loader (
|
|
428
|
-
*
|
|
429
|
-
* docs/design/loader-container-bake.md). Excludes the shell family itself
|
|
427
|
+
* pins the settled CONTAINER of a bake-lane loader (lane rule: see
|
|
428
|
+
* resolveLoaderData, loader-cache.ts). Excludes the shell family itself
|
|
430
429
|
* (getShell/putShell) — the snapshot rides INSIDE a shell entry, so recording
|
|
431
430
|
* it would be self-referential.
|
|
432
431
|
*/
|
|
@@ -542,6 +541,11 @@ export interface CachedEntryData {
|
|
|
542
541
|
* (see handle-snapshot.ts encodeHandles) so Promise/ReactNode handle values
|
|
543
542
|
* round-trip through JSON-serializing stores instead of being flattened. */
|
|
544
543
|
handles: string;
|
|
544
|
+
/**
|
|
545
|
+
* Owning loader ids index-aligned with the decoded `handles` values
|
|
546
|
+
* (HandleOwners). Absent: every value restores as a plain replay.
|
|
547
|
+
*/
|
|
548
|
+
handleOwners?: HandleOwners;
|
|
545
549
|
/** Expiration timestamp (ms since epoch) */
|
|
546
550
|
expiresAt: number;
|
|
547
551
|
/** Cache tags for invalidation */
|
|
@@ -583,3 +587,12 @@ export interface CacheDefaults {
|
|
|
583
587
|
* Structure: { handleName: [values...] }
|
|
584
588
|
*/
|
|
585
589
|
export type SegmentHandleData = Record<string, unknown[]>;
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* segmentId -> handleName -> the owning loader id of each recorded value, by
|
|
593
|
+
* index (null: not owned). Only a PPR shell capture writes owners: the
|
|
594
|
+
* settled pushes of an `ssr: false` loader's own body, which the record keeps
|
|
595
|
+
* because the prelude rendered them. restoreHandles replays an owned value
|
|
596
|
+
* through HandleStore.pushReplayed, so that loader's live re-run replaces it.
|
|
597
|
+
*/
|
|
598
|
+
export type HandleOwners = Record<string, Record<string, (string | null)[]>>;
|
package/src/client.rsc.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* RSC-environment version of client exports
|
|
3
3
|
*
|
|
4
|
-
* This file is used when importing "
|
|
4
|
+
* This file is used when importing "@rangojs/router/client" from RSC (server components).
|
|
5
5
|
* It re-exports the server's createLoader so that loader definitions work in both
|
|
6
6
|
* environments with the same import.
|
|
7
7
|
*
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
* in RSC context, while the regular client.tsx is used in client components.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
// From html.ts directly, never via ./client.js: every export of that
|
|
13
|
+
// "use client" module is an opaque client reference here.
|
|
14
|
+
import * as Html from "./html.js";
|
|
15
|
+
|
|
12
16
|
// Re-export everything from client.tsx (Outlet, useLoader, etc.)
|
|
13
17
|
// These are safe to use in RSC context
|
|
14
18
|
export {
|
|
@@ -64,7 +68,10 @@ export {
|
|
|
64
68
|
useLinkStatus,
|
|
65
69
|
type LinkStatus,
|
|
66
70
|
} from "./browser/react/use-link-status.js";
|
|
67
|
-
export {
|
|
71
|
+
export {
|
|
72
|
+
useScrollRestoration,
|
|
73
|
+
type ScrollRestorationProps,
|
|
74
|
+
} from "./browser/react/ScrollRestoration.js";
|
|
68
75
|
|
|
69
76
|
export {
|
|
70
77
|
Link,
|
|
@@ -72,11 +79,6 @@ export {
|
|
|
72
79
|
type PrefetchStrategy,
|
|
73
80
|
} from "./browser/react/Link.js";
|
|
74
81
|
|
|
75
|
-
export {
|
|
76
|
-
ScrollRestoration,
|
|
77
|
-
type ScrollRestorationProps,
|
|
78
|
-
} from "./browser/react/ScrollRestoration.js";
|
|
79
|
-
|
|
80
82
|
export {
|
|
81
83
|
NavigationProvider,
|
|
82
84
|
type NavigationProviderProps,
|
|
@@ -91,14 +93,13 @@ export { MountContext } from "./browser/react/mount-context.js";
|
|
|
91
93
|
export { createHandle, isHandle, type Handle } from "./handle.js";
|
|
92
94
|
|
|
93
95
|
export { Meta } from "./handles/meta.js";
|
|
94
|
-
export { MetaTags } from "./handles/MetaTags.js";
|
|
95
96
|
export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
|
|
96
97
|
export {
|
|
97
98
|
Script,
|
|
98
99
|
type ScriptConfig,
|
|
99
100
|
type ScriptAttributes,
|
|
100
101
|
} from "./handles/script.js";
|
|
101
|
-
export {
|
|
102
|
+
export { Html };
|
|
102
103
|
export { Breadcrumbs, type BreadcrumbItem } from "./handles/breadcrumbs.js";
|
|
103
104
|
|
|
104
105
|
export {
|
package/src/client.tsx
CHANGED
|
@@ -8,6 +8,9 @@ import {
|
|
|
8
8
|
Suspense,
|
|
9
9
|
type ReactNode,
|
|
10
10
|
} from "react";
|
|
11
|
+
// Not `export * as Html`: plugin-rsc's "use client" proxy transform only
|
|
12
|
+
// expands bare `export *` and throws on a named one.
|
|
13
|
+
import * as Html from "./html.js";
|
|
11
14
|
import { OutletContext, type OutletContextValue } from "./outlet-context.js";
|
|
12
15
|
import {
|
|
13
16
|
type ClientErrorBoundaryFallbackProps,
|
|
@@ -300,7 +303,7 @@ interface ErrorBoundaryState {
|
|
|
300
303
|
* @example
|
|
301
304
|
* ```tsx
|
|
302
305
|
* "use client";
|
|
303
|
-
* import { ErrorBoundary } from "
|
|
306
|
+
* import { ErrorBoundary } from "@rangojs/router/client";
|
|
304
307
|
*
|
|
305
308
|
* function MyComponent() {
|
|
306
309
|
* return (
|
|
@@ -427,7 +430,6 @@ export {
|
|
|
427
430
|
} from "./browser/react/use-link-status.js";
|
|
428
431
|
|
|
429
432
|
export {
|
|
430
|
-
ScrollRestoration,
|
|
431
433
|
useScrollRestoration,
|
|
432
434
|
type ScrollRestorationProps,
|
|
433
435
|
} from "./browser/react/ScrollRestoration.js";
|
|
@@ -436,14 +438,13 @@ export { type Handle } from "./handle.js";
|
|
|
436
438
|
export { useHandle } from "./browser/react/use-handle.js";
|
|
437
439
|
|
|
438
440
|
export { Meta } from "./handles/meta.js";
|
|
439
|
-
export { MetaTags } from "./handles/MetaTags.js";
|
|
440
441
|
export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
|
|
441
442
|
export {
|
|
442
443
|
Script,
|
|
443
444
|
type ScriptConfig,
|
|
444
445
|
type ScriptAttributes,
|
|
445
446
|
} from "./handles/script.js";
|
|
446
|
-
export {
|
|
447
|
+
export { Html };
|
|
447
448
|
export { Breadcrumbs, type BreadcrumbItem } from "./handles/breadcrumbs.js";
|
|
448
449
|
|
|
449
450
|
export {
|
|
@@ -3,12 +3,16 @@
|
|
|
3
3
|
import type { ReactNode, ReactElement } from "react";
|
|
4
4
|
import { MetaTags } from "../handles/MetaTags.js";
|
|
5
5
|
import { Scripts } from "../handles/Scripts.js";
|
|
6
|
+
import { ScrollRestoration } from "../browser/react/ScrollRestoration.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Default document component that provides a basic HTML structure.
|
|
9
10
|
* Used when no custom document is provided to createRouter.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
11
|
+
* Renders every Html member: Html.Meta (charset, viewport, route meta), head +
|
|
12
|
+
* body Html.Scripts sites for the Script handle, and Html.ScrollRestoration.
|
|
13
|
+
* An app that wants a custom getKey or no scroll restoration passes its own
|
|
14
|
+
* document; rendering a second Html.ScrollRestoration under this one only
|
|
15
|
+
* warns ("[Scroll] Already initialized").
|
|
12
16
|
*
|
|
13
17
|
* Uses suppressHydrationWarning on <html> because the theme script
|
|
14
18
|
* may modify class/style attributes before React hydrates.
|
|
@@ -27,6 +31,7 @@ export function DefaultDocument({
|
|
|
27
31
|
<body>
|
|
28
32
|
<Scripts position="body" />
|
|
29
33
|
{children}
|
|
34
|
+
<ScrollRestoration />
|
|
30
35
|
</body>
|
|
31
36
|
</html>
|
|
32
37
|
);
|
package/src/context-var.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* interface PaginationData { current: number; total: number }
|
|
13
13
|
* export const Pagination = createVar<PaginationData>();
|
|
14
14
|
*
|
|
15
|
-
* // Non-cacheable var — ctx.get(User) throws inside
|
|
15
|
+
* // Non-cacheable var — ctx.get(User) throws inside cache() or "use cache"
|
|
16
16
|
* export const User = createVar<UserData>({ cache: false });
|
|
17
17
|
*
|
|
18
18
|
* // handler
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
export interface ContextVar<T> {
|
|
27
27
|
readonly __brand: "context-var";
|
|
28
28
|
readonly key: symbol;
|
|
29
|
-
/** When false, ctx.get(var) throws inside
|
|
29
|
+
/** When false, ctx.get(var) throws inside cache() or "use cache". */
|
|
30
30
|
readonly cache: boolean;
|
|
31
31
|
/** Phantom field to carry the type parameter. Never set at runtime. */
|
|
32
32
|
readonly __type?: T;
|
|
@@ -35,9 +35,10 @@ export interface ContextVar<T> {
|
|
|
35
35
|
export interface ContextVarOptions {
|
|
36
36
|
/**
|
|
37
37
|
* When false, marks this variable as non-cacheable.
|
|
38
|
-
* Reading this var with ctx.get() inside a cache() boundary
|
|
39
|
-
* inherently request-specific data (user sessions,
|
|
40
|
-
* must never be baked into cached segments
|
|
38
|
+
* Reading this var with ctx.get() inside a cache() boundary or a "use cache"
|
|
39
|
+
* function throws. Use for inherently request-specific data (user sessions,
|
|
40
|
+
* auth tokens, etc.) that must never be baked into cached segments or
|
|
41
|
+
* entries; pass the value into a "use cache" function as an argument.
|
|
41
42
|
*
|
|
42
43
|
* @default true
|
|
43
44
|
*/
|
package/src/escape-script.ts
CHANGED
|
@@ -45,7 +45,7 @@ export function escapeJsonForScript(json: string): string {
|
|
|
45
45
|
* source; for untrusted dynamic data, JSON-encode it and read it back, rather than
|
|
46
46
|
* inlining it as code.
|
|
47
47
|
*
|
|
48
|
-
* Used by the Script handle's <Scripts> renderer for inline `children`.
|
|
48
|
+
* Used by the Script handle's <Html.Scripts> renderer for inline `children`.
|
|
49
49
|
*/
|
|
50
50
|
export function escapeScriptBody(js: string): string {
|
|
51
51
|
return js.replace(/<!--/g, "<\\u0021--").replace(/<\/(script)/gi, "<\\/$1");
|
package/src/handles/MetaTags.tsx
CHANGED
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Component to render collected meta descriptors in the document head.
|
|
5
|
+
* Public as `Html.Meta` (`@rangojs/router/client`).
|
|
5
6
|
*
|
|
6
|
-
* Deferred (Promise) meta descriptors are resolved before
|
|
7
|
+
* Deferred (Promise) meta descriptors are resolved before `<Html.Meta />` renders
|
|
7
8
|
* (server-side on the full render, client-side before apply on navigation), so
|
|
8
9
|
* it only ever receives resolved descriptors and never suspends.
|
|
9
10
|
*
|
|
10
|
-
* When theme is enabled in the router config,
|
|
11
|
+
* When theme is enabled in the router config, `<Html.Meta />` also renders
|
|
11
12
|
* the theme initialization script to prevent FOUC (flash of unstyled content).
|
|
12
|
-
* This makes
|
|
13
|
-
* the standalone `<ThemeScript />` is only needed when
|
|
14
|
-
* Rendering both is safe (the inline script guards listener registration)
|
|
15
|
-
* redundant.
|
|
13
|
+
* This makes `<Html.Meta />` the sole FOUC-script injector for apps that render
|
|
14
|
+
* it; the standalone `<ThemeScript />` is only needed when `<Html.Meta />` is not
|
|
15
|
+
* used. Rendering both is safe (the inline script guards listener registration)
|
|
16
|
+
* but redundant.
|
|
16
17
|
*
|
|
17
18
|
* @example
|
|
18
19
|
* ```tsx
|
|
@@ -20,7 +21,7 @@
|
|
|
20
21
|
* return (
|
|
21
22
|
* <html lang="en" suppressHydrationWarning>
|
|
22
23
|
* <head>
|
|
23
|
-
* <
|
|
24
|
+
* <Html.Meta />
|
|
24
25
|
* </head>
|
|
25
26
|
* <body>...</body>
|
|
26
27
|
* </html>
|
|
@@ -179,17 +180,18 @@ export function renderMetaDescriptor(
|
|
|
179
180
|
}
|
|
180
181
|
|
|
181
182
|
/**
|
|
182
|
-
* Renders all collected meta descriptors from route handlers.
|
|
183
|
+
* Renders all collected meta descriptors from route handlers. Public as
|
|
184
|
+
* `Html.Meta` (`@rangojs/router/client`).
|
|
183
185
|
*
|
|
184
|
-
* Place
|
|
186
|
+
* Place `<Html.Meta />` inside the `<head>` element of your document.
|
|
185
187
|
* It will automatically update when meta descriptors change during navigation.
|
|
186
188
|
*
|
|
187
189
|
* When theme is enabled in router config, also renders the theme initialization
|
|
188
190
|
* script to prevent FOUC (flash of unstyled content).
|
|
189
191
|
*
|
|
190
|
-
* Deferred (Promise) meta descriptors are resolved BEFORE
|
|
192
|
+
* Deferred (Promise) meta descriptors are resolved BEFORE this renders —
|
|
191
193
|
* server-side on the full/SSR render, client-side before apply on navigation
|
|
192
|
-
* (resolve-by-default) — so
|
|
194
|
+
* (resolve-by-default) — so it only ever receives resolved descriptors and
|
|
193
195
|
* never suspends.
|
|
194
196
|
*/
|
|
195
197
|
export function MetaTags(): React.ReactNode {
|
package/src/handles/Scripts.tsx
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Renders the scripts collected by the Script handle into the document.
|
|
4
|
+
* Renders the scripts collected by the Script handle into the document. Public
|
|
5
|
+
* as `Html.Scripts` (`@rangojs/router/client`).
|
|
5
6
|
*
|
|
6
|
-
* Place `<Scripts />` inside `<head>` (default) and, if you push body
|
|
7
|
-
* `<Scripts position="body" />` at the top of `<body>`. Each site
|
|
8
|
-
* configs whose `position` matches; the request CSP nonce is applied
|
|
7
|
+
* Place `<Html.Scripts />` inside `<head>` (default) and, if you push body
|
|
8
|
+
* scripts, `<Html.Scripts position="body" />` at the top of `<body>`. Each site
|
|
9
|
+
* renders the configs whose `position` matches; the request CSP nonce is applied
|
|
9
10
|
* automatically to every DOCUMENT-RENDERED <script> (consumers never pass it). An
|
|
10
11
|
* async script first encountered on a soft navigation is injected client-side
|
|
11
12
|
* where the nonce is unavailable, so it carries no nonce and relies on
|
|
@@ -24,11 +25,11 @@
|
|
|
24
25
|
* ```tsx
|
|
25
26
|
* <html>
|
|
26
27
|
* <head>
|
|
27
|
-
* <
|
|
28
|
-
* <Scripts />
|
|
28
|
+
* <Html.Meta />
|
|
29
|
+
* <Html.Scripts />
|
|
29
30
|
* </head>
|
|
30
31
|
* <body>
|
|
31
|
-
* <Scripts position="body" />
|
|
32
|
+
* <Html.Scripts position="body" />
|
|
32
33
|
* {children}
|
|
33
34
|
* </body>
|
|
34
35
|
* </html>
|
|
@@ -80,10 +81,10 @@ function passthroughAttributes(
|
|
|
80
81
|
if (dev) {
|
|
81
82
|
console.warn(
|
|
82
83
|
isHandler
|
|
83
|
-
? `[Scripts] event handler "${key}" in a script's attributes is ` +
|
|
84
|
+
? `[Html.Scripts] event handler "${key}" in a script's attributes is ` +
|
|
84
85
|
`dropped; callbacks cannot cross the server -> client handle ` +
|
|
85
86
|
`boundary. Use a "use client" component for load/error handling.`
|
|
86
|
-
: `[Scripts] managed field "${key}" in a script's attributes is ` +
|
|
87
|
+
: `[Html.Scripts] managed field "${key}" in a script's attributes is ` +
|
|
87
88
|
`dropped; set it via the ScriptConfig fields (the request nonce ` +
|
|
88
89
|
`is applied automatically).`,
|
|
89
90
|
);
|
|
@@ -129,7 +130,7 @@ function renderScript(
|
|
|
129
130
|
async
|
|
130
131
|
) {
|
|
131
132
|
console.warn(
|
|
132
|
-
`[Scripts] An async external script (src="${src}") is hoisted into ` +
|
|
133
|
+
`[Html.Scripts] An async external script (src="${src}") is hoisted into ` +
|
|
133
134
|
`<head> by React; position: "body" is ignored for it.`,
|
|
134
135
|
);
|
|
135
136
|
}
|
package/src/handles/meta.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* return (
|
|
20
20
|
* <html>
|
|
21
21
|
* <head>
|
|
22
|
-
* <
|
|
22
|
+
* <Html.Meta />
|
|
23
23
|
* </head>
|
|
24
24
|
* ...
|
|
25
25
|
* </html>
|
|
@@ -225,7 +225,7 @@ function collectMeta(segments: MetaDescriptor[][]): MetaDescriptor[] {
|
|
|
225
225
|
* Built-in handle for managing document metadata.
|
|
226
226
|
*
|
|
227
227
|
* Use `ctx.use(Meta)` in route handlers to push meta descriptors.
|
|
228
|
-
* Use `<
|
|
228
|
+
* Use the `<Html.Meta />` component to render them in the document head.
|
|
229
229
|
*
|
|
230
230
|
* Deferred (Promise) descriptors are resolved before collectMeta runs
|
|
231
231
|
* (resolve-by-default), so they participate in deduplication and `%s`
|
package/src/handles/script.ts
CHANGED
|
@@ -3,22 +3,23 @@
|
|
|
3
3
|
* route/layout handlers.
|
|
4
4
|
*
|
|
5
5
|
* Push from a SERVER handler with `ctx.use(Script)(config)`; render with the
|
|
6
|
-
* `<Scripts />` component (from `@rangojs/router/client`) placed in the
|
|
7
|
-
* `<head>` (and optionally a second `<Scripts position="body" />`
|
|
8
|
-
* `<body>`). This mirrors the Meta / <
|
|
6
|
+
* `<Html.Scripts />` component (from `@rangojs/router/client`) placed in the
|
|
7
|
+
* Document `<head>` (and optionally a second `<Html.Scripts position="body" />`
|
|
8
|
+
* at the top of `<body>`). This mirrors the Meta / <Html.Meta> pair.
|
|
9
9
|
*
|
|
10
|
-
* The request CSP nonce is applied AUTOMATICALLY by <Scripts> to
|
|
11
|
-
* scripts; consumers never pass a nonce. (An async script
|
|
12
|
-
* navigation is injected client-side without a nonce —
|
|
13
|
-
* 'strict-dynamic' or a host allowance; see the EXECUTION CONTRACT
|
|
14
|
-
* /scripts skill.) A ScriptConfig is fully serializable (it
|
|
15
|
-
* server -> client handle-collection boundary), so callbacks like
|
|
16
|
-
* supported — a consumer needing them renders their own
|
|
10
|
+
* The request CSP nonce is applied AUTOMATICALLY by <Html.Scripts> to
|
|
11
|
+
* document-rendered scripts; consumers never pass a nonce. (An async script
|
|
12
|
+
* first loaded on a soft navigation is injected client-side without a nonce —
|
|
13
|
+
* it relies on 'strict-dynamic' or a host allowance; see the EXECUTION CONTRACT
|
|
14
|
+
* below and the /scripts skill.) A ScriptConfig is fully serializable (it
|
|
15
|
+
* crosses the server -> client handle-collection boundary), so callbacks like
|
|
16
|
+
* onLoad are NOT supported — a consumer needing them renders their own
|
|
17
|
+
* "use client" script.
|
|
17
18
|
*
|
|
18
19
|
* EXECUTION CONTRACT (see the /scripts skill for the full story):
|
|
19
20
|
* - Inline (`children`) and ordered external (`src`, optional `defer`) scripts
|
|
20
21
|
* are DOCUMENT-LOAD scripts: they execute only when present in the initial HTML
|
|
21
|
-
* response. <Scripts> freezes them after hydration, so a later client (soft)
|
|
22
|
+
* response. <Html.Scripts> freezes them after hydration, so a later client (soft)
|
|
22
23
|
* navigation never inserts an inert copy — React creates client-mounted
|
|
23
24
|
* <script> elements via innerHTML, which the HTML spec makes non-executing.
|
|
24
25
|
* - Async external scripts (`src` + `async: true`) are React RESOURCES: they load
|
|
@@ -78,9 +79,9 @@ export type ScriptAttributes = Omit<
|
|
|
78
79
|
/** Fields shared by every script shape. */
|
|
79
80
|
interface ScriptConfigBase {
|
|
80
81
|
/**
|
|
81
|
-
* Where <Scripts> renders this script.
|
|
82
|
-
* - "head" (default): the `<head>` <Scripts> site.
|
|
83
|
-
* - "body": the `<Scripts position="body" />` site at the top of <body>.
|
|
82
|
+
* Where <Html.Scripts> renders this script.
|
|
83
|
+
* - "head" (default): the `<head>` <Html.Scripts> site.
|
|
84
|
+
* - "body": the `<Html.Scripts position="body" />` site at the top of <body>.
|
|
84
85
|
* Note: an external `async` script is hoisted into <head> by React regardless.
|
|
85
86
|
*/
|
|
86
87
|
position?: "head" | "body";
|
|
@@ -96,7 +97,7 @@ interface ScriptConfigBase {
|
|
|
96
97
|
/**
|
|
97
98
|
* Inline script: a raw JS body rendered in place, escaped against `</script>`
|
|
98
99
|
* breakout. DOCUMENT-LOAD only (executes when present in the initial HTML;
|
|
99
|
-
* <Scripts> freezes it after hydration so navigation never inserts an inert
|
|
100
|
+
* <Html.Scripts> freezes it after hydration so navigation never inserts an inert
|
|
100
101
|
* copy). `id` is REQUIRED — inline scripts are never deduped by React, so a
|
|
101
102
|
* layout and a child pushing the same bootstrap would inject it twice. It is also
|
|
102
103
|
* rendered as the script's DOM `id`. Forbids `src`/`async`/`defer`. For analytics
|
|
@@ -146,7 +147,7 @@ export interface OrderedScriptConfig extends ScriptConfigBase {
|
|
|
146
147
|
* inline (`id` + `children`), external async (`src` + `async: true`), or external
|
|
147
148
|
* ordered (`src`, optional `defer`). Invalid combinations (both `src`+`children`,
|
|
148
149
|
* `async`+`defer`, inline without `id`) are compile errors. The CSP nonce is
|
|
149
|
-
* applied by <Scripts>, never here.
|
|
150
|
+
* applied by <Html.Scripts>, never here.
|
|
150
151
|
*/
|
|
151
152
|
export type ScriptConfig =
|
|
152
153
|
| InlineScriptConfig
|
package/src/href-client.ts
CHANGED
package/src/html.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Members of the `Html` namespace; client.tsx and client.rsc.tsx expose this
|
|
3
|
+
* module as `import * as Html`. A module namespace, not an object literal, so
|
|
4
|
+
* bundlers drop members an app never renders (a literal kept opt-in
|
|
5
|
+
* ScrollRestoration in every app: +313 B gzip, over the router-chunk ratchet).
|
|
6
|
+
* No "use client": each member stays its own module's client reference, so a
|
|
7
|
+
* server component can dot into `Html`. Pinned by __tests__/html.test.ts and
|
|
8
|
+
* testing/__tests__/html-namespace.rsc-test.tsx.
|
|
9
|
+
*/
|
|
10
|
+
export { MetaTags as Meta } from "./handles/MetaTags.js";
|
|
11
|
+
export { Scripts } from "./handles/Scripts.js";
|
|
12
|
+
export { ScrollRestoration } from "./browser/react/ScrollRestoration.js";
|
package/src/index.rsc.ts
CHANGED
|
@@ -51,6 +51,7 @@ export type {
|
|
|
51
51
|
LoaderDefinition,
|
|
52
52
|
LoaderFn,
|
|
53
53
|
LoaderContext,
|
|
54
|
+
LoaderOptions,
|
|
54
55
|
FetchableLoaderOptions,
|
|
55
56
|
LoadOptions,
|
|
56
57
|
// Error boundary types
|
|
@@ -188,6 +189,9 @@ export {
|
|
|
188
189
|
type RouterRequestInput,
|
|
189
190
|
} from "./router.js";
|
|
190
191
|
|
|
192
|
+
// Route manifest snapshot returned by router.debugManifest()
|
|
193
|
+
export type { SerializedManifest } from "./debug.js";
|
|
194
|
+
|
|
191
195
|
// RSC handler types (server-side)
|
|
192
196
|
export type { HandlerCacheConfig } from "./rsc/types.js";
|
|
193
197
|
export {
|
|
@@ -254,6 +258,7 @@ export type {
|
|
|
254
258
|
export type {
|
|
255
259
|
ScopedReverseFunction,
|
|
256
260
|
ReverseFunction,
|
|
261
|
+
GlobalReverseFunction,
|
|
257
262
|
ExtractLocalRoutes,
|
|
258
263
|
ParamsFor,
|
|
259
264
|
} from "./reverse.js";
|
package/src/index.ts
CHANGED
package/src/loader-store.ts
CHANGED
|
@@ -34,6 +34,15 @@
|
|
|
34
34
|
* the store — the hook falls back to `OutletContext.loaderData`.
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
|
+
import { unwrapsSynchronously } from "./thenable-status.js";
|
|
38
|
+
|
|
39
|
+
/** The loader-segment shape announcePendingStreams reads (ResolvedSegment). */
|
|
40
|
+
export interface PendingStreamSource {
|
|
41
|
+
type: string;
|
|
42
|
+
loaderId?: string;
|
|
43
|
+
loaderData?: unknown;
|
|
44
|
+
}
|
|
45
|
+
|
|
37
46
|
export interface LoaderEntry<T = unknown> {
|
|
38
47
|
readonly value: T | undefined;
|
|
39
48
|
/**
|
|
@@ -87,6 +96,14 @@ export interface SubscribeOptions {
|
|
|
87
96
|
* Refreshes this bucket in place (no params/body) and rejects on failure.
|
|
88
97
|
*/
|
|
89
98
|
refetch?: () => Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* Called by `announcePendingStreams` (inside a transition commit) when a
|
|
101
|
+
* navigation stream for this bucket's loader is still pending. The hook pins
|
|
102
|
+
* `isLoading: true` with a useOptimistic update; React reverts it when that
|
|
103
|
+
* transition commits. Route-context readers only — an ephemeral reader owns
|
|
104
|
+
* its own lifecycle.
|
|
105
|
+
*/
|
|
106
|
+
onStreamPending?: () => void;
|
|
90
107
|
}
|
|
91
108
|
|
|
92
109
|
interface InternalEntry {
|
|
@@ -118,6 +135,8 @@ interface InternalEntry {
|
|
|
118
135
|
groups: Map<string, number>;
|
|
119
136
|
/** Plain-GET refresh thunk for `refreshGroups`, set while in any group. */
|
|
120
137
|
refetch: (() => Promise<void>) | undefined;
|
|
138
|
+
/** Subscribers' `onStreamPending` callbacks (see SubscribeOptions). */
|
|
139
|
+
streamPendingListeners: Set<() => void>;
|
|
121
140
|
}
|
|
122
141
|
|
|
123
142
|
/**
|
|
@@ -137,6 +156,21 @@ export class LoaderStore {
|
|
|
137
156
|
private readonly families = new Map<string, Set<string>>();
|
|
138
157
|
/** refresh group name -> set of bucket keys, for refreshGroups(). */
|
|
139
158
|
private readonly groups = new Map<string, Set<string>>();
|
|
159
|
+
/**
|
|
160
|
+
* loader.$$id -> per-loader streams a committed navigation is still
|
|
161
|
+
* receiving. Registered and announced by announcePendingStreams(), which
|
|
162
|
+
* browser/partial-update.ts calls INSIDE every transition commit, so a
|
|
163
|
+
* reader whose content is held on screen by that transition pins
|
|
164
|
+
* `isLoading: true` for the data it is still showing until the transition
|
|
165
|
+
* lands (use-loader.tsx). Family-keyed on purpose: a persisting layout
|
|
166
|
+
* reader of the same createLoader the child is re-running is pinned too
|
|
167
|
+
* (the loader is in flight; not "this segment's copy is being replaced" —
|
|
168
|
+
* issue #862, working as designed). The new tree's own read of the loader
|
|
169
|
+
* suspends at the read site and never observes the flag. A Set, not a
|
|
170
|
+
* flag: overlapping navigations (second click before the first stream
|
|
171
|
+
* lands) keep the family pending until the LAST stream settles.
|
|
172
|
+
*/
|
|
173
|
+
private readonly pendingStreams = new Map<string, Set<Promise<unknown>>>();
|
|
140
174
|
|
|
141
175
|
private getOrCreate(bucketKey: string): InternalEntry {
|
|
142
176
|
let e = this.entries.get(bucketKey);
|
|
@@ -151,6 +185,7 @@ export class LoaderStore {
|
|
|
151
185
|
clearWhenSettled: false,
|
|
152
186
|
groups: new Map(),
|
|
153
187
|
refetch: undefined,
|
|
188
|
+
streamPendingListeners: new Set(),
|
|
154
189
|
};
|
|
155
190
|
this.entries.set(bucketKey, e);
|
|
156
191
|
}
|
|
@@ -195,8 +230,11 @@ export class LoaderStore {
|
|
|
195
230
|
e.pendingClear = false;
|
|
196
231
|
e.clearWhenSettled = false;
|
|
197
232
|
e.listeners.add(cb);
|
|
233
|
+
const onStreamPending = options?.onStreamPending;
|
|
234
|
+
if (onStreamPending) e.streamPendingListeners.add(onStreamPending);
|
|
198
235
|
return () => {
|
|
199
236
|
e.listeners.delete(cb);
|
|
237
|
+
if (onStreamPending) e.streamPendingListeners.delete(onStreamPending);
|
|
200
238
|
// Group membership is refcounted per subscriber so refreshGroups() never
|
|
201
239
|
// fetches for an unmounted reader, and a bucket shared by subscribers in
|
|
202
240
|
// different groups stays in each group until ALL of that group's
|
|
@@ -479,6 +517,73 @@ export class LoaderStore {
|
|
|
479
517
|
for (const cb of e.listeners) cb();
|
|
480
518
|
}
|
|
481
519
|
|
|
520
|
+
/**
|
|
521
|
+
* Register a still-streaming per-loader result for `loaderId`. No-op for a
|
|
522
|
+
* settled Flight chunk (unwrapsSynchronously / "rejected"): the forceAwait
|
|
523
|
+
* and action lanes commit settled chunks, and a { ssr: false } loader is
|
|
524
|
+
* awaited before flush. Released as the stream settles, either way.
|
|
525
|
+
*/
|
|
526
|
+
trackPendingStream(loaderId: string, stream: Promise<unknown>): void {
|
|
527
|
+
if (
|
|
528
|
+
unwrapsSynchronously(stream) ||
|
|
529
|
+
(stream as { status?: string }).status === "rejected"
|
|
530
|
+
) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
let set = this.pendingStreams.get(loaderId);
|
|
534
|
+
if (set?.has(stream)) return;
|
|
535
|
+
if (!set) {
|
|
536
|
+
set = new Set();
|
|
537
|
+
this.pendingStreams.set(loaderId, set);
|
|
538
|
+
}
|
|
539
|
+
set.add(stream);
|
|
540
|
+
const release = () => {
|
|
541
|
+
const current = this.pendingStreams.get(loaderId);
|
|
542
|
+
if (!current || !current.delete(stream)) return;
|
|
543
|
+
if (current.size === 0) this.pendingStreams.delete(loaderId);
|
|
544
|
+
};
|
|
545
|
+
stream.then(release, release);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/** True while any navigation stream for `loaderId` is still pending. */
|
|
549
|
+
isStreamPending(loaderId: string): boolean {
|
|
550
|
+
return this.pendingStreams.has(loaderId);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Register the loader segments of a tree being committed whose data is
|
|
555
|
+
* still a pending promise, then fire `onStreamPending` on every subscriber
|
|
556
|
+
* of every family with a pending stream. MUST run inside the startTransition
|
|
557
|
+
* that commits the tree: the hook answers with a useOptimistic update, which
|
|
558
|
+
* React reverts exactly when that transition commits — so the held reader
|
|
559
|
+
* reports isLoading:true for as long as its data stays on screen and not a
|
|
560
|
+
* frame longer. Outside a transition React reverts an optimistic update on
|
|
561
|
+
* the next render, pinning nothing useful. Registering here rather than in
|
|
562
|
+
* the tree build keeps register + announce in one call; settled chunks
|
|
563
|
+
* (cached/reused segments, forceAwait lanes) are skipped by
|
|
564
|
+
* trackPendingStream.
|
|
565
|
+
*/
|
|
566
|
+
announcePendingStreams(segments: readonly PendingStreamSource[]): void {
|
|
567
|
+
for (const segment of segments) {
|
|
568
|
+
if (
|
|
569
|
+
segment.type === "loader" &&
|
|
570
|
+
segment.loaderId &&
|
|
571
|
+
segment.loaderData instanceof Promise
|
|
572
|
+
) {
|
|
573
|
+
this.trackPendingStream(segment.loaderId, segment.loaderData);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
for (const loaderId of this.pendingStreams.keys()) {
|
|
577
|
+
const fam = this.families.get(loaderId);
|
|
578
|
+
if (!fam) continue;
|
|
579
|
+
for (const bucketKey of fam) {
|
|
580
|
+
const e = this.entries.get(bucketKey);
|
|
581
|
+
if (!e) continue;
|
|
582
|
+
for (const cb of e.streamPendingListeners) cb();
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
482
587
|
/**
|
|
483
588
|
* Test-only escape hatch. Drops every entry. Production code should never
|
|
484
589
|
* call this; the store is process-scoped and lives for the tab's lifetime.
|
|
@@ -488,6 +593,7 @@ export class LoaderStore {
|
|
|
488
593
|
this.entries.clear();
|
|
489
594
|
this.families.clear();
|
|
490
595
|
this.groups.clear();
|
|
596
|
+
this.pendingStreams.clear();
|
|
491
597
|
}
|
|
492
598
|
}
|
|
493
599
|
|