@rangojs/router 0.20.0 → 0.22.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 +39 -15
- package/dist/types/browser/event-controller.d.ts +49 -0
- package/dist/types/browser/history-state.d.ts +16 -4
- package/dist/types/browser/navigation-store.d.ts +9 -0
- package/dist/types/browser/navigation-transaction.d.ts +7 -0
- package/dist/types/browser/partial-update.d.ts +2 -1
- package/dist/types/browser/prefetch/default-strategy.d.ts +16 -20
- package/dist/types/browser/rango-state.d.ts +3 -1
- package/dist/types/browser/react/NavigationProvider.d.ts +14 -1
- package/dist/types/browser/react/context.d.ts +25 -0
- package/dist/types/browser/react/location-state-shared.d.ts +84 -10
- package/dist/types/browser/react/location-state.d.ts +7 -2
- package/dist/types/browser/rsc-router.d.ts +13 -2
- package/dist/types/browser/types.d.ts +10 -0
- package/dist/types/build/generate-manifest.d.ts +2 -0
- package/dist/types/cache/background-task.d.ts +22 -0
- package/dist/types/cache/cache-key-utils.d.ts +50 -5
- package/dist/types/cache/cache-policy.d.ts +17 -0
- package/dist/types/cache/cache-scope.d.ts +5 -6
- package/dist/types/cache/cf/cf-cache-store.d.ts +34 -7
- package/dist/types/cache/cf/cf-cache-types.d.ts +12 -4
- package/dist/types/cache/document-cache.d.ts +8 -5
- package/dist/types/cache/handle-snapshot.d.ts +59 -50
- package/dist/types/cache/index.d.ts +3 -1
- package/dist/types/cache/invalidation-order.d.ts +14 -0
- package/dist/types/cache/memory-segment-store.d.ts +33 -1
- package/dist/types/cache/shell-snapshot.d.ts +29 -4
- package/dist/types/cache/store-scope.d.ts +22 -0
- package/dist/types/cache/tag-invalidation.d.ts +7 -6
- package/dist/types/cache/types.d.ts +84 -24
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +35 -17
- package/dist/types/client-urls/navigation.d.ts +7 -1
- package/dist/types/client-urls/optimistic-location.d.ts +10 -4
- package/dist/types/errors.d.ts +6 -0
- package/dist/types/handles/deferred-resolution.d.ts +18 -1
- package/dist/types/internal-suspense-audit.d.ts +1 -0
- package/dist/types/loader-store.d.ts +4 -8
- package/dist/types/outlet-provider.d.ts +2 -1
- package/dist/types/prerender/build-shell-capture.d.ts +8 -6
- package/dist/types/prerender/cloudflare.d.ts +49 -0
- package/dist/types/prerender/create-prerender-trigger.d.ts +121 -0
- package/dist/types/prerender/index.d.ts +12 -0
- package/dist/types/prerender/memory-prerender-store.d.ts +29 -0
- package/dist/types/prerender/on-demand.d.ts +272 -0
- package/dist/types/prerender/producer-guard.d.ts +33 -0
- package/dist/types/prerender/shell-manifest-key.d.ts +1 -18
- package/dist/types/prerender/warm-request.d.ts +65 -0
- package/dist/types/prerender/warm.d.ts +65 -0
- package/dist/types/prerender/writable-store.d.ts +170 -0
- package/dist/types/prerender.d.ts +27 -0
- package/dist/types/reverse.d.ts +1 -1
- package/dist/types/route-content-wrapper.d.ts +3 -1
- package/dist/types/router/error-handling.d.ts +2 -0
- package/dist/types/router/handler-context.d.ts +1 -1
- package/dist/types/router/match-middleware/cache-lookup.d.ts +15 -1
- package/dist/types/router/navigation-snapshot.d.ts +8 -1
- package/dist/types/router/pattern-matching.d.ts +2 -0
- package/dist/types/router/prefetch-default.d.ts +4 -4
- package/dist/types/router/prerender-match.d.ts +23 -1
- package/dist/types/router/route-trie-builder.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +1 -0
- package/dist/types/router/router-interfaces.d.ts +32 -1
- package/dist/types/router/router-options.d.ts +39 -5
- package/dist/types/router/segment-resolution/fresh.d.ts +7 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +23 -10
- package/dist/types/router/segment-resolution/revalidation.d.ts +5 -1
- package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +43 -0
- package/dist/types/router/trie-matching.d.ts +2 -0
- package/dist/types/router-versions.d.ts +39 -0
- package/dist/types/rsc/handler-context.d.ts +5 -0
- package/dist/types/rsc/shell-build-manifest.d.ts +9 -6
- package/dist/types/rsc/shell-capture-constants.d.ts +20 -5
- package/dist/types/rsc/shell-capture.d.ts +14 -1
- package/dist/types/rsc/shell-serve.d.ts +2 -2
- package/dist/types/rsc/ssr-module-loader.d.ts +15 -0
- package/dist/types/rsc/types.d.ts +12 -6
- package/dist/types/segment-boundary-content.d.ts +17 -0
- package/dist/types/segment-loader-promise.d.ts +2 -6
- package/dist/types/server/build-version-table.d.ts +53 -0
- package/dist/types/server/context.d.ts +12 -4
- package/dist/types/server/handle-store.d.ts +89 -46
- package/dist/types/server/request-context.d.ts +84 -9
- package/dist/types/suspense-audit-react.d.ts +26 -0
- package/dist/types/suspense-audit.d.ts +157 -0
- package/dist/types/testing/action-form.d.ts +34 -0
- package/dist/types/testing/build-versions.d.ts +32 -0
- package/dist/types/testing/dom.entry.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +3 -1
- package/dist/types/testing/index.d.ts +6 -0
- package/dist/types/testing/internal/seed-vars.d.ts +7 -2
- package/dist/types/testing/internal/server-modules.d.ts +8 -0
- package/dist/types/testing/location-state-key.d.ts +5 -1
- package/dist/types/testing/render-route.d.ts +77 -6
- package/dist/types/testing/serve-shell-request.d.ts +55 -4
- package/dist/types/testing/shell-status.d.ts +14 -6
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +9 -6
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/thenable-status.d.ts +6 -0
- package/dist/types/types/handler-context.d.ts +15 -5
- package/dist/types/types/route-entry.d.ts +5 -0
- package/dist/types/urls/path-helper.d.ts +18 -0
- package/dist/types/vite/discovery/build-versions.d.ts +393 -0
- package/dist/types/vite/discovery/installed-packages.d.ts +26 -0
- package/dist/types/vite/discovery/router-versions-phase.d.ts +52 -0
- package/dist/types/vite/discovery/shell-prerender-phase.d.ts +7 -17
- package/dist/types/vite/discovery/state.d.ts +25 -0
- package/dist/types/vite/encryption-key.d.ts +18 -0
- package/dist/types/vite/inject-client-debug.d.ts +6 -25
- package/dist/types/vite/plugin-types.d.ts +27 -0
- package/dist/types/vite/plugins/{version-injector.d.ts → entry-bootstrap-injector.d.ts} +4 -6
- package/dist/types/vite/plugins/expose-ids/export-analysis.d.ts +1 -0
- package/dist/types/vite/plugins/expose-ids/router-transform.d.ts +14 -0
- package/dist/types/vite/plugins/stable-rsc-output.d.ts +51 -0
- package/dist/types/vite/plugins/version-plugin.d.ts +9 -11
- package/dist/types/vite/plugins/virtual-entries.d.ts +18 -1
- package/dist/types/vite/router-discovery.d.ts +6 -0
- package/dist/types/vite/utils/bundle-analysis.d.ts +6 -2
- package/dist/types/vite/utils/client-chunks.d.ts +2 -0
- package/dist/types/vite/utils/compare-strings.d.ts +7 -0
- package/dist/types/vite/utils/package-resolution.d.ts +8 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +2 -0
- package/dist/vite/index.js +1707 -931
- package/package.json +12 -3
- package/skills/cache-guide/SKILL.md +102 -37
- package/skills/caching/SKILL.md +56 -9
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +16 -0
- package/skills/cloudflare/SKILL.md +133 -1
- package/skills/comparison/references/framework-comparison.md +17 -13
- package/skills/deployment-caching/SKILL.md +33 -12
- package/skills/document-cache/SKILL.md +16 -14
- package/skills/hooks/SKILL.md +25 -0
- package/skills/hooks/handle-and-actions.md +5 -0
- package/skills/hooks/navigation.md +38 -0
- package/skills/hooks/state.md +213 -11
- package/skills/hooks/urls.md +40 -1
- package/skills/host-router/SKILL.md +31 -0
- package/skills/intercept/SKILL.md +6 -0
- package/skills/loader/SKILL.md +25 -5
- package/skills/middleware/SKILL.md +2 -0
- package/skills/migrate-nextjs/SKILL.md +82 -9
- package/skills/ppr/SKILL.md +150 -48
- package/skills/prerender/SKILL.md +651 -10
- package/skills/rango/SKILL.md +9 -7
- package/skills/response-routes/SKILL.md +5 -5
- package/skills/route/SKILL.md +29 -0
- package/skills/router-setup/SKILL.md +15 -1
- package/skills/testing/SKILL.md +32 -27
- package/skills/testing/cache-prerender.md +18 -17
- package/skills/testing/client-components.md +193 -8
- package/skills/testing/handles.md +2 -0
- package/skills/testing/loader.md +1 -1
- package/skills/testing/middleware.md +1 -1
- package/skills/testing/render-handler.md +18 -18
- package/skills/testing/serve-shell-request.md +167 -22
- package/skills/testing/server-actions.md +17 -17
- package/skills/testing/server-tree.md +1 -1
- package/skills/typesafety/generated-files-and-cli.md +1 -1
- package/skills/use-cache/SKILL.md +25 -10
- package/skills/vercel/SKILL.md +5 -3
- package/skills/view-transitions/SKILL.md +9 -4
- package/src/bin/rango.ts +1 -1
- package/src/browser/event-controller.ts +119 -2
- package/src/browser/history-state.ts +39 -17
- package/src/browser/link-interceptor.ts +2 -2
- package/src/browser/navigation-bridge.ts +104 -41
- package/src/browser/navigation-store.ts +26 -0
- package/src/browser/navigation-transaction.ts +24 -12
- package/src/browser/network-error-handler.ts +2 -0
- package/src/browser/partial-update.ts +58 -14
- package/src/browser/prefetch/default-strategy.ts +22 -42
- package/src/browser/rango-state.ts +3 -1
- package/src/browser/react/Link.tsx +37 -43
- package/src/browser/react/NavigationProvider.tsx +85 -1
- package/src/browser/react/context.ts +30 -0
- package/src/browser/react/location-state-shared.ts +200 -34
- package/src/browser/react/location-state.ts +28 -77
- package/src/browser/react/use-action.ts +4 -1
- package/src/browser/react/use-handle.ts +3 -0
- package/src/browser/react/use-link-status.ts +4 -3
- package/src/browser/react/use-navigation.ts +4 -1
- package/src/browser/react/use-params.ts +4 -1
- package/src/browser/react/use-pathname.ts +4 -1
- package/src/browser/react/use-search-params.ts +5 -3
- package/src/browser/react/use-segments.ts +36 -54
- package/src/browser/rsc-router.tsx +93 -31
- package/src/browser/segment-reconciler.ts +3 -3
- package/src/browser/server-action-bridge.ts +43 -7
- package/src/browser/types.ts +11 -0
- package/src/build/generate-manifest.ts +17 -0
- package/src/build/merge-full-manifests.ts +5 -0
- package/src/build/route-trie.ts +1 -0
- package/src/cache/background-task.ts +61 -0
- package/src/cache/cache-key-utils.ts +91 -12
- package/src/cache/cache-policy.ts +11 -2
- package/src/cache/cache-runtime.ts +23 -23
- package/src/cache/cache-scope.ts +48 -36
- package/src/cache/cf/cf-cache-store.ts +91 -66
- package/src/cache/cf/cf-cache-types.ts +12 -4
- package/src/cache/document-cache.ts +35 -14
- package/src/cache/handle-snapshot.ts +118 -86
- package/src/cache/index.ts +5 -0
- package/src/cache/invalidation-order.ts +53 -0
- package/src/cache/memory-segment-store.ts +52 -1
- package/src/cache/shell-snapshot.ts +52 -5
- package/src/cache/store-scope.ts +36 -0
- package/src/cache/tag-invalidation.ts +7 -5
- package/src/cache/types.ts +90 -24
- package/src/cache/vercel/vercel-cache-store.ts +68 -26
- package/src/client-urls/client-root.tsx +1 -0
- package/src/client-urls/navigation.ts +10 -0
- package/src/client-urls/optimistic-location.ts +10 -4
- package/src/client.tsx +40 -3
- package/src/errors.ts +13 -6
- package/src/handles/deferred-resolution.ts +36 -0
- package/src/internal-suspense-audit.ts +12 -0
- package/src/loader-store.ts +4 -9
- package/src/outlet-provider.tsx +25 -1
- package/src/prerender/build-shell-capture.ts +11 -9
- package/src/prerender/cloudflare.ts +130 -0
- package/src/prerender/create-prerender-trigger.ts +780 -0
- package/src/prerender/index.ts +39 -0
- package/src/prerender/memory-prerender-store.ts +115 -0
- package/src/prerender/on-demand.ts +339 -0
- package/src/prerender/producer-guard.ts +66 -0
- package/src/prerender/shell-manifest-key.ts +12 -17
- package/src/prerender/warm-request.ts +154 -0
- package/src/prerender/warm.ts +193 -0
- package/src/prerender/writable-store.ts +278 -0
- package/src/prerender.ts +29 -0
- package/src/reverse.ts +1 -1
- package/src/route-content-wrapper.tsx +107 -13
- package/src/router/error-handling.ts +12 -12
- package/src/router/find-match.ts +1 -0
- package/src/router/handler-context.ts +2 -0
- package/src/router/intercept-resolution.ts +15 -8
- package/src/router/loader-resolution.ts +2 -10
- package/src/router/manifest.ts +2 -0
- package/src/router/match-middleware/background-revalidation.ts +2 -0
- package/src/router/match-middleware/cache-lookup.ts +239 -54
- package/src/router/navigation-snapshot.ts +25 -6
- package/src/router/pattern-matching.ts +14 -0
- package/src/router/prefetch-default.ts +4 -4
- package/src/router/prerender-match.ts +460 -352
- package/src/router/request-classification.ts +15 -12
- package/src/router/route-trie-builder.ts +9 -0
- package/src/router/router-context.ts +1 -0
- package/src/router/router-interfaces.ts +35 -1
- package/src/router/router-options.ts +45 -5
- package/src/router/segment-resolution/fresh.ts +20 -19
- package/src/router/segment-resolution/loader-cache.ts +110 -77
- package/src/router/segment-resolution/revalidation.ts +34 -24
- package/src/router/segment-resolution/streamed-handler-boundary.ts +133 -0
- package/src/router/trie-matching.ts +3 -0
- package/src/router-versions.ts +60 -0
- package/src/router.ts +107 -1
- package/src/rsc/full-payload.ts +6 -2
- package/src/rsc/handler-context.ts +5 -0
- package/src/rsc/handler.ts +64 -7
- package/src/rsc/progressive-enhancement.ts +5 -1
- package/src/rsc/response-cache-serve.ts +10 -4
- package/src/rsc/rsc-rendering.ts +65 -12
- package/src/rsc/shell-build-manifest.ts +17 -12
- package/src/rsc/shell-capture-constants.ts +39 -7
- package/src/rsc/shell-capture.ts +144 -45
- package/src/rsc/shell-serve.ts +12 -6
- package/src/rsc/ssr-module-loader.ts +26 -0
- package/src/rsc/types.ts +12 -6
- package/src/segment-boundary-content.ts +25 -0
- package/src/segment-loader-promise.ts +28 -38
- package/src/segment-system.tsx +117 -57
- package/src/server/build-version-table.ts +130 -0
- package/src/server/context.ts +40 -17
- package/src/server/cookie-store.ts +5 -0
- package/src/server/handle-store.ts +357 -127
- package/src/server/request-context.ts +149 -12
- package/src/ssr/ssr-root.tsx +32 -13
- package/src/suspense-audit-react.tsx +98 -0
- package/src/suspense-audit.ts +1030 -0
- package/src/testing/action-form.ts +66 -0
- package/src/testing/build-versions.ts +64 -0
- package/src/testing/dispatch.ts +6 -0
- package/src/testing/dom.entry.ts +1 -0
- package/src/testing/flight-runtime.d.ts +27 -0
- package/src/testing/flight.entry.ts +4 -0
- package/src/testing/index.ts +22 -0
- package/src/testing/internal/context.ts +2 -1
- package/src/testing/internal/seed-vars.ts +14 -2
- package/src/testing/internal/server-modules.ts +32 -0
- package/src/testing/location-state-key.ts +18 -8
- package/src/testing/render-handler.ts +2 -1
- package/src/testing/render-route.tsx +293 -46
- package/src/testing/serve-shell-request.ts +262 -37
- package/src/testing/shell-status.ts +17 -6
- package/src/testing/vitest-stubs/plugin-rsc.ts +37 -11
- package/src/testing/vitest-stubs/version.ts +4 -1
- package/src/thenable-status.ts +10 -0
- package/src/types/handler-context.ts +15 -5
- package/src/types/route-entry.ts +6 -0
- package/src/urls/path-helper.ts +69 -9
- package/src/use-loader.tsx +57 -30
- package/src/vite/discovery/build-versions.ts +1281 -0
- package/src/vite/discovery/bundle-postprocess.ts +4 -0
- package/src/vite/discovery/discover-routers.ts +15 -0
- package/src/vite/discovery/installed-packages.ts +159 -0
- package/src/vite/discovery/prerender-collection.ts +38 -1
- package/src/vite/discovery/router-versions-phase.ts +336 -0
- package/src/vite/discovery/shell-prerender-phase.ts +33 -43
- package/src/vite/discovery/state.ts +36 -1
- package/src/vite/discovery/virtual-module-codegen.ts +7 -2
- package/src/vite/encryption-key.ts +101 -9
- package/src/vite/inject-client-debug.ts +42 -20
- package/src/vite/plugin-types.ts +28 -0
- package/src/vite/plugins/{version-injector.ts → entry-bootstrap-injector.ts} +7 -22
- package/src/vite/plugins/expose-ids/export-analysis.ts +1 -1
- package/src/vite/plugins/expose-ids/router-transform.ts +36 -15
- package/src/vite/plugins/expose-internal-ids.ts +12 -3
- package/src/vite/plugins/stable-rsc-output.ts +147 -0
- package/src/vite/plugins/version-plugin.ts +19 -24
- package/src/vite/plugins/version.d.ts +17 -2
- package/src/vite/plugins/virtual-entries.ts +39 -6
- package/src/vite/rango.ts +28 -3
- package/src/vite/router-discovery.ts +96 -13
- package/src/vite/utils/bundle-analysis.ts +39 -7
- package/src/vite/utils/client-chunks.ts +5 -1
- package/src/vite/utils/compare-strings.ts +9 -0
- package/src/vite/utils/package-resolution.ts +9 -0
- package/src/vite/utils/prerender-utils.ts +4 -1
- package/src/vite/utils/shared-utils.ts +1 -1
- package/dist/types/segment-content-promise.d.ts +0 -13
- package/src/segment-content-promise.ts +0 -67
|
@@ -55,13 +55,23 @@ function createLateHandlePushError(
|
|
|
55
55
|
interface SlotTag {
|
|
56
56
|
/** Pushed inside a DSL loader scope; captureHandles drops it. */
|
|
57
57
|
loader?: boolean;
|
|
58
|
-
/** A
|
|
58
|
+
/** A cached unit's replay of this loader's recorded push (pushReplayed). */
|
|
59
59
|
replayOf?: string;
|
|
60
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* A record's copy for a loader that record does not supply the value of
|
|
62
|
+
* (pushPlaceholder): it stands in until the loader's own source delivers.
|
|
63
|
+
*/
|
|
64
|
+
placeholderOf?: string;
|
|
65
|
+
/** Pushed by this loader's live run after it replaced its copies. */
|
|
61
66
|
liveOf?: string;
|
|
67
|
+
/**
|
|
68
|
+
* The loaders around that run's body whose runs replaced copies too: their
|
|
69
|
+
* next push follows this one, so a run's pushes keep their order.
|
|
70
|
+
*/
|
|
71
|
+
under?: readonly string[];
|
|
62
72
|
/** Pushed by this loader's body and kept in records (push `owner`). */
|
|
63
73
|
owner?: string;
|
|
64
|
-
/** A
|
|
74
|
+
/** A record's copy for a loader served from that record's pin (pushRestored). */
|
|
65
75
|
restoredOf?: string;
|
|
66
76
|
/** A restored slot a dropped settled push of its loader stood in for. */
|
|
67
77
|
matched?: true;
|
|
@@ -70,6 +80,26 @@ interface SlotTag {
|
|
|
70
80
|
// Shared by every plain DSL-loader push; tags are never mutated in place.
|
|
71
81
|
const LOADER_SLOT: SlotTag = Object.freeze({ loader: true });
|
|
72
82
|
|
|
83
|
+
/**
|
|
84
|
+
* What a restored record is to one loader's handle pushes on this request
|
|
85
|
+
* (loader-cache.ts loaderPins; docs/design/handle-push-ownership.md). Two
|
|
86
|
+
* answers let the record's copies stand, two make them placeholders, and in
|
|
87
|
+
* each pair one also speaks for the loader's other pushes:
|
|
88
|
+
* - "pin": the request serves the loader's value from the pin stored with
|
|
89
|
+
* the record, and the record lists every settled push of that run. Its
|
|
90
|
+
* copies stand, and any other settled push of the loader is dropped.
|
|
91
|
+
* - "copies": its copies stand and nothing else is known: a dependency of
|
|
92
|
+
* pinned loaders, or a pin written before captures recorded every push.
|
|
93
|
+
* - "hole": the route runs the loader on this request (it registers it and
|
|
94
|
+
* the record does not pin it). Its copies are placeholders and a push made
|
|
95
|
+
* in its body is its own, with or without copies.
|
|
96
|
+
* - "placeholders": its copies are placeholders and nothing else is known: a
|
|
97
|
+
* dependency the route does not register while a pin is missing (the
|
|
98
|
+
* record does not name the loader that ran it). Without copies its push
|
|
99
|
+
* counts for the loader that awaits it.
|
|
100
|
+
*/
|
|
101
|
+
export type RecordAuthority = "pin" | "copies" | "hole" | "placeholders";
|
|
102
|
+
|
|
73
103
|
function cloneHandleData(data: HandleData): HandleData {
|
|
74
104
|
const clone: HandleData = {};
|
|
75
105
|
for (const handleName in data) {
|
|
@@ -158,15 +188,17 @@ export interface HandleStore {
|
|
|
158
188
|
): void;
|
|
159
189
|
|
|
160
190
|
/**
|
|
161
|
-
* push() a
|
|
162
|
-
*
|
|
163
|
-
* in this request, its first push removes every
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
191
|
+
* push() a cached unit's copy of a value `loaderId`'s body pushed
|
|
192
|
+
* (handle-snapshot.ts appendHandles: a loader-cache or "use cache" HIT). If
|
|
193
|
+
* that loader then runs live in this request, its first push removes every
|
|
194
|
+
* copy pushed for it (replays and placeholders) and takes the first one's
|
|
195
|
+
* position, and the pushes of that run follow in push order, a dependency's
|
|
196
|
+
* included, so the handle output stays live without a duplicate. A push
|
|
197
|
+
* counts as that loader's when it is made in its body, or in a body it
|
|
198
|
+
* entered that has no copies of its own (the capture credits a dependency's
|
|
199
|
+
* push under a live-lane loader to that loader). A run that ends without a
|
|
200
|
+
* push leaves a replay in place, unless the loader is a hole
|
|
201
|
+
* (settleLoaderRun).
|
|
170
202
|
*/
|
|
171
203
|
pushReplayed(
|
|
172
204
|
handleName: string,
|
|
@@ -176,16 +208,27 @@ export interface HandleStore {
|
|
|
176
208
|
): void;
|
|
177
209
|
|
|
178
210
|
/**
|
|
179
|
-
* push() a
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
211
|
+
* push() a record's copy of `loaderId`'s push as a placeholder
|
|
212
|
+
* (handle-snapshot.ts restoreHandles): the record does not supply that
|
|
213
|
+
* loader's value on this request, so its own source decides its pushes. A
|
|
214
|
+
* live run replaces it as it does a replay; a run that ends without a push
|
|
215
|
+
* drops it (settleLoaderRun); a cached unit that claims the loader delivers
|
|
216
|
+
* in its place (replacePlaceholders).
|
|
217
|
+
*/
|
|
218
|
+
pushPlaceholder(
|
|
219
|
+
handleName: string,
|
|
220
|
+
segmentId: string,
|
|
221
|
+
data: unknown,
|
|
222
|
+
loaderId: string,
|
|
223
|
+
): void;
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* push() a record's copy of `loaderId`'s settled push that stands
|
|
227
|
+
* (restoreHandles): the record supplies the loader's value too. From then
|
|
228
|
+
* on this request drops every settled, thenable-free push the loader makes
|
|
229
|
+
* (live, or replayed from a cache entry, anywhere inside its body), and
|
|
230
|
+
* keeps the ones holding a thenable (holdsThenable: what the record could
|
|
231
|
+
* not keep), placed among the restored copies in the loader's push order.
|
|
189
232
|
*/
|
|
190
233
|
pushRestored(
|
|
191
234
|
handleName: string,
|
|
@@ -195,41 +238,34 @@ export interface HandleStore {
|
|
|
195
238
|
): void;
|
|
196
239
|
|
|
197
240
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
* never takes a push made inside a hole it awaited, while a body inside the
|
|
205
|
-
* hole that restored copies of its own (a dependency the capture ran under
|
|
206
|
-
* a bake-lane loader) still takes its pushes.
|
|
241
|
+
* What the record this request restored is to each loader (RecordAuthority;
|
|
242
|
+
* CacheScope.lookupRouteDetailed sets it on a hit). A "pin" loader counts
|
|
243
|
+
* as restored with or without copies. A hole is a "hole" loader or one with
|
|
244
|
+
* placeholders: the walk out through enclosing loader bodies that finds the
|
|
245
|
+
* loader a push counts for stops at its body, so a pinned loader never
|
|
246
|
+
* takes a push made inside a hole it awaited.
|
|
207
247
|
*/
|
|
208
|
-
|
|
248
|
+
setRecordAuthority(authority: (loaderId: string) => RecordAuthority): void;
|
|
209
249
|
|
|
210
250
|
/**
|
|
211
251
|
* `loaderId`'s run in this request settled (resolved or rejected), its
|
|
212
|
-
* pushes made (loader-resolution.ts createLoaderExecutor).
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
* its settled pushes, as its pin is for its data. Restored copies are not
|
|
218
|
-
* replays and stand.
|
|
252
|
+
* pushes made (loader-resolution.ts createLoaderExecutor). Its placeholders
|
|
253
|
+
* that no push of the run replaced are dropped: the run's output decides,
|
|
254
|
+
* so a loader that throws or skips its push shows none rather than the
|
|
255
|
+
* record's. A hole's replays go with them. Any other loader's replays stay
|
|
256
|
+
* (a dependency another entry's HIT replayed), and so do restored copies.
|
|
219
257
|
*/
|
|
220
258
|
settleLoaderRun(loaderId: string): void;
|
|
221
259
|
|
|
222
260
|
/**
|
|
223
|
-
* A
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
* place in each array. Any other loader, or a hole with nothing replayed,
|
|
230
|
-
* delivers as a plain replay.
|
|
261
|
+
* A cached unit's HIT delivering the pushes it recorded for `loaderIds`,
|
|
262
|
+
* the loaders it claimed (appendHandles). Their placeholders give way to
|
|
263
|
+
* what `deliver` pushes for them, nothing when it pushes nothing, so the
|
|
264
|
+
* page shows the pushes of the run that produced the unit's value. The
|
|
265
|
+
* pushReplayed calls `deliver` makes take the removed values' place in each
|
|
266
|
+
* array, in order. Without placeholders it is a plain replay.
|
|
231
267
|
*/
|
|
232
|
-
|
|
268
|
+
replacePlaceholders(loaderIds: readonly string[], deliver: () => void): void;
|
|
233
269
|
|
|
234
270
|
/**
|
|
235
271
|
* Get all collected handle data after all handlers have settled.
|
|
@@ -237,6 +273,24 @@ export interface HandleStore {
|
|
|
237
273
|
*/
|
|
238
274
|
getData(): Promise<HandleData>;
|
|
239
275
|
|
|
276
|
+
/**
|
|
277
|
+
* Fix the document lane at the store's current state. From then on
|
|
278
|
+
* `stream("settled")` yields this state and nothing newer, and
|
|
279
|
+
* `streamLate()` delivers every change made after it, whether or not it
|
|
280
|
+
* beat the handler barrier. The first call wins. It must run before the
|
|
281
|
+
* document stream is first read: a stream that started on the live state
|
|
282
|
+
* stays on it (reported through `onError` in development).
|
|
283
|
+
*
|
|
284
|
+
* A PPR shell HIT calls it once its record is replayed and before a loader
|
|
285
|
+
* runs (rsc-rendering.ts serveShellHit). The prelude was rendered from the
|
|
286
|
+
* record's handle data (resolvedHandleStream `recordedOnly`), so that is
|
|
287
|
+
* what the client must hydrate with. What the request's loaders then push,
|
|
288
|
+
* replace or drop is theirs, not the shell's: in the document snapshot it
|
|
289
|
+
* rendered elements the prelude never had, or left out ones it has (issue
|
|
290
|
+
* #1035), and it got there or not by a race with the lane's first read.
|
|
291
|
+
*/
|
|
292
|
+
freezeDocumentSnapshot(): void;
|
|
293
|
+
|
|
240
294
|
/**
|
|
241
295
|
* Get an async iterator that yields handle data on each push.
|
|
242
296
|
* Completes at the chosen settlement barrier: "fullySettled" (default —
|
|
@@ -245,6 +299,9 @@ export interface HandleStore {
|
|
|
245
299
|
* blocking positions and must not wait on loader bodies).
|
|
246
300
|
* Each yield contains the full accumulated state (not just the delta).
|
|
247
301
|
* Safe for concurrent consumers (per-consumer version cursor).
|
|
302
|
+
* The "settled" lane of a frozen store (freezeDocumentSnapshot) yields the
|
|
303
|
+
* frozen state once, at once, and ends. That yield is the store's own
|
|
304
|
+
* frozen object: a consumer must not write to it.
|
|
248
305
|
*/
|
|
249
306
|
stream(
|
|
250
307
|
until?: "settled" | "fullySettled",
|
|
@@ -256,7 +313,9 @@ export interface HandleStore {
|
|
|
256
313
|
* fullySettled. Returns without yielding when the auxiliary lane is empty
|
|
257
314
|
* by the time `settled` resolves. The client consumes this post-hydration
|
|
258
315
|
* (rsc-router.tsx) and merges via the same application path as nav-lane
|
|
259
|
-
* progressive handle updates.
|
|
316
|
+
* progressive handle updates. On a frozen store (freezeDocumentSnapshot)
|
|
317
|
+
* it starts from the frozen state instead: a change made before the
|
|
318
|
+
* handler barrier is late too.
|
|
260
319
|
*/
|
|
261
320
|
streamLate(): AsyncGenerator<HandleData, void, unknown>;
|
|
262
321
|
|
|
@@ -281,6 +340,9 @@ export interface HandleStore {
|
|
|
281
340
|
segmentId: string,
|
|
282
341
|
): Record<string, (string | null)[]> | undefined;
|
|
283
342
|
|
|
343
|
+
/** The segment ids for which getRecordOwners returns an entry. */
|
|
344
|
+
getOwnedSegmentIds(): string[];
|
|
345
|
+
|
|
284
346
|
/**
|
|
285
347
|
* Replay cached handle data back into the store (for cache hits).
|
|
286
348
|
* Used to restore handle data when serving cached segments.
|
|
@@ -317,45 +379,51 @@ export function createHandleStore(): HandleStore {
|
|
|
317
379
|
// (not value identity) so primitive values are covered too.
|
|
318
380
|
// replaySegmentData installs fresh arrays, so restored values are untagged.
|
|
319
381
|
const slotTags = new WeakMap<unknown[], (SlotTag | undefined)[]>();
|
|
320
|
-
//
|
|
321
|
-
// holding
|
|
382
|
+
// Copies standing in for a loader that has not pushed live yet: loaderId ->
|
|
383
|
+
// the arrays holding them. Kept apart by kind: only a placeholder gives way
|
|
384
|
+
// to a run that ends without a push, or to a cached unit's delivery.
|
|
322
385
|
const pendingReplays = new Map<string, Set<unknown[]>>();
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
386
|
+
const pendingPlaceholders = new Map<string, Set<unknown[]>>();
|
|
387
|
+
// Loaders whose live run already replaced its copies.
|
|
388
|
+
const replaced = new Set<string>();
|
|
389
|
+
// Loaders a record restored standing copies for (pushRestored), and those
|
|
390
|
+
// it restored placeholders for (pushPlaceholder).
|
|
326
391
|
const restoredLoaders = new Set<string>();
|
|
392
|
+
const placeholderLoaders = new Set<string>();
|
|
393
|
+
let authority: ((loaderId: string) => RecordAuthority) | undefined;
|
|
327
394
|
const isRestored = (loaderId: string): boolean =>
|
|
328
|
-
restoredLoaders.has(loaderId);
|
|
329
|
-
// The route's live-lane loaders (markLiveLane): holes a restore never
|
|
330
|
-
// reaches into.
|
|
331
|
-
let liveLane: ReadonlySet<string> | undefined;
|
|
395
|
+
restoredLoaders.has(loaderId) || authority?.(loaderId) === "pin";
|
|
332
396
|
const isHole = (loaderId: string): boolean =>
|
|
333
|
-
|
|
397
|
+
placeholderLoaders.has(loaderId) || authority?.(loaderId) === "hole";
|
|
334
398
|
|
|
335
|
-
const
|
|
336
|
-
pendingReplays.has(loaderId) ||
|
|
399
|
+
const hasCopies = (loaderId: string): boolean =>
|
|
400
|
+
pendingReplays.has(loaderId) ||
|
|
401
|
+
pendingPlaceholders.has(loaderId) ||
|
|
402
|
+
replaced.has(loaderId);
|
|
337
403
|
|
|
338
404
|
// The restored loader a push counts for, as the capture credits it
|
|
339
405
|
// (shell-capture.ts deriveShellCaptureContext): the replayed loader when it
|
|
340
|
-
// restored
|
|
406
|
+
// is restored, else the innermost enclosing loader body that is. A
|
|
341
407
|
// "use cache" hit inside a restored body replays a dependency's push the
|
|
342
408
|
// capture recorded under that body. A hole's body, or a replay of a hole's
|
|
343
409
|
// push, ends the search: a live loader a restored loader awaits is still
|
|
344
410
|
// live.
|
|
345
411
|
function restoringLoader(replayOf: string | undefined): string | undefined {
|
|
346
412
|
if (replayOf !== undefined) {
|
|
347
|
-
if (
|
|
413
|
+
if (isRestored(replayOf)) return replayOf;
|
|
348
414
|
// Defensive: a hole's own cache() replay runs outside any loader body,
|
|
349
415
|
// so the walk below finds no restored loader either.
|
|
350
416
|
if (isHole(replayOf)) return undefined;
|
|
351
417
|
}
|
|
352
418
|
return findEnclosingLoaderBody(isRestored, isHole);
|
|
353
419
|
}
|
|
354
|
-
// The
|
|
355
|
-
//
|
|
356
|
-
// push
|
|
357
|
-
let replaying:
|
|
358
|
-
|
|
420
|
+
// The copy whose this.push() is in flight, as the tag its slot takes. Set
|
|
421
|
+
// around the call, not passed, so a capture wrapping push() sees it as a
|
|
422
|
+
// push.
|
|
423
|
+
let replaying:
|
|
424
|
+
| Pick<SlotTag, "replayOf" | "placeholderOf" | "restoredOf">
|
|
425
|
+
| undefined;
|
|
426
|
+
// A replacePlaceholders delivery in flight: per array, where its next
|
|
359
427
|
// pushReplayed value goes (the removed values' place).
|
|
360
428
|
let redelivery: Map<unknown[], number> | undefined;
|
|
361
429
|
|
|
@@ -383,22 +451,23 @@ export function createHandleStore(): HandleStore {
|
|
|
383
451
|
tags.splice(at, 0, tag);
|
|
384
452
|
}
|
|
385
453
|
|
|
386
|
-
// Remove `
|
|
387
|
-
function
|
|
454
|
+
// Remove the slots `drops` accepts; returns the first one's position.
|
|
455
|
+
function removeSlots(
|
|
388
456
|
values: unknown[],
|
|
389
|
-
|
|
457
|
+
drops: (tag: SlotTag) => boolean,
|
|
390
458
|
): number | undefined {
|
|
391
459
|
const tags = slotTags.get(values);
|
|
392
460
|
if (!tags) return undefined;
|
|
393
461
|
let first: number | undefined;
|
|
394
462
|
let kept = 0;
|
|
395
463
|
for (let i = 0; i < values.length; i++) {
|
|
396
|
-
|
|
464
|
+
const tag = tags[i];
|
|
465
|
+
if (tag && drops(tag)) {
|
|
397
466
|
first ??= kept;
|
|
398
467
|
continue;
|
|
399
468
|
}
|
|
400
469
|
values[kept] = values[i];
|
|
401
|
-
tags[kept] =
|
|
470
|
+
tags[kept] = tag;
|
|
402
471
|
kept++;
|
|
403
472
|
}
|
|
404
473
|
values.length = kept;
|
|
@@ -406,32 +475,80 @@ export function createHandleStore(): HandleStore {
|
|
|
406
475
|
return first;
|
|
407
476
|
}
|
|
408
477
|
|
|
409
|
-
//
|
|
410
|
-
//
|
|
411
|
-
|
|
412
|
-
function liveReplacementIndex(
|
|
413
|
-
loaderId: string,
|
|
414
|
-
values: unknown[],
|
|
415
|
-
): number | undefined {
|
|
416
|
-
const pending = pendingReplays.get(loaderId);
|
|
417
|
-
if (pending) {
|
|
418
|
-
pendingReplays.delete(loaderId);
|
|
419
|
-
replacedReplays.add(loaderId);
|
|
420
|
-
let at: number | undefined;
|
|
421
|
-
for (const replayed of pending) {
|
|
422
|
-
const first = removeReplayedSlots(replayed, loaderId);
|
|
423
|
-
if (replayed === values) at = first;
|
|
424
|
-
}
|
|
425
|
-
return at;
|
|
426
|
-
}
|
|
478
|
+
// After the last push of `loaderId`'s run in `values`: its own, or one made
|
|
479
|
+
// in a body it entered. undefined: none yet.
|
|
480
|
+
function runCursor(loaderId: string, values: unknown[]): number | undefined {
|
|
427
481
|
const tags = slotTags.get(values);
|
|
428
482
|
if (!tags) return undefined;
|
|
429
483
|
for (let i = tags.length - 1; i >= 0; i--) {
|
|
430
|
-
|
|
484
|
+
const tag = tags[i];
|
|
485
|
+
if (tag?.liveOf === loaderId || tag?.under?.includes(loaderId)) {
|
|
486
|
+
return i + 1;
|
|
487
|
+
}
|
|
431
488
|
}
|
|
432
489
|
return undefined;
|
|
433
490
|
}
|
|
434
491
|
|
|
492
|
+
// The loaders around `pusher`'s body, innermost first, whose runs replaced
|
|
493
|
+
// copies (SlotTag.under).
|
|
494
|
+
function replacedAround(pusher: string): string[] | undefined {
|
|
495
|
+
let around: string[] | undefined;
|
|
496
|
+
let outside = false;
|
|
497
|
+
findEnclosingLoaderBody((loaderId) => {
|
|
498
|
+
if (outside && replaced.has(loaderId)) (around ??= []).push(loaderId);
|
|
499
|
+
if (loaderId === pusher) outside = true;
|
|
500
|
+
return false;
|
|
501
|
+
});
|
|
502
|
+
return around;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Remove `loaderId`'s pending placeholders, and its replays with `replays`.
|
|
506
|
+
// Returns the first position removed from `values`.
|
|
507
|
+
function dropCopies(
|
|
508
|
+
loaderId: string,
|
|
509
|
+
replays: boolean,
|
|
510
|
+
values?: unknown[],
|
|
511
|
+
): number | undefined {
|
|
512
|
+
const placeholderArrays = pendingPlaceholders.get(loaderId);
|
|
513
|
+
const replayArrays = replays ? pendingReplays.get(loaderId) : undefined;
|
|
514
|
+
if (!placeholderArrays && !replayArrays) return undefined;
|
|
515
|
+
pendingPlaceholders.delete(loaderId);
|
|
516
|
+
if (replayArrays) pendingReplays.delete(loaderId);
|
|
517
|
+
replaced.add(loaderId);
|
|
518
|
+
const own = (tag: SlotTag): boolean =>
|
|
519
|
+
tag.placeholderOf === loaderId || (replays && tag.replayOf === loaderId);
|
|
520
|
+
let at: number | undefined;
|
|
521
|
+
for (const arrays of [placeholderArrays, replayArrays]) {
|
|
522
|
+
if (!arrays) continue;
|
|
523
|
+
for (const copied of arrays) {
|
|
524
|
+
const first = removeSlots(copied, own);
|
|
525
|
+
if (copied === values) at ??= first;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return at;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Where a live push by a loader with copies lands in `values`: its first
|
|
532
|
+
// push removes all of its copies and takes the first one's position in this
|
|
533
|
+
// array; later pushes follow the last push of its run. undefined: no place
|
|
534
|
+
// of its own here.
|
|
535
|
+
function liveReplacementIndex(
|
|
536
|
+
loaderId: string,
|
|
537
|
+
values: unknown[],
|
|
538
|
+
): number | undefined {
|
|
539
|
+
return dropCopies(loaderId, true, values) ?? runCursor(loaderId, values);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function addPending(
|
|
543
|
+
pending: Map<string, Set<unknown[]>>,
|
|
544
|
+
loaderId: string,
|
|
545
|
+
values: unknown[],
|
|
546
|
+
): void {
|
|
547
|
+
let arrays = pending.get(loaderId);
|
|
548
|
+
if (!arrays) pending.set(loaderId, (arrays = new Set()));
|
|
549
|
+
arrays.add(values);
|
|
550
|
+
}
|
|
551
|
+
|
|
435
552
|
// A restored loader's settled push: the first of its restored slots here
|
|
436
553
|
// not yet matched stands in for it. Returns nothing; the push is dropped.
|
|
437
554
|
function matchRestoredSlot(values: unknown[], loaderId: string): void {
|
|
@@ -561,6 +678,12 @@ export function createHandleStore(): HandleStore {
|
|
|
561
678
|
let emissionWaiters: (() => void)[] = [];
|
|
562
679
|
let completed = false;
|
|
563
680
|
let streamConsumed = false;
|
|
681
|
+
// freezeDocumentSnapshot: the state the document lane serves, and the
|
|
682
|
+
// version the late channel starts after.
|
|
683
|
+
let documentSnapshot: { version: number; data: HandleData } | undefined;
|
|
684
|
+
// A stream("settled") consumer started on the live state: a freeze after
|
|
685
|
+
// that does not reach it.
|
|
686
|
+
let documentStreamRead = false;
|
|
564
687
|
|
|
565
688
|
// Wake every waiting consumer (new push or a settlement barrier fired).
|
|
566
689
|
function signalEmission() {
|
|
@@ -631,21 +754,27 @@ export function createHandleStore(): HandleStore {
|
|
|
631
754
|
throw error;
|
|
632
755
|
}
|
|
633
756
|
|
|
634
|
-
const { replayOf, restoredOf } = replaying ?? {};
|
|
635
|
-
// A
|
|
636
|
-
//
|
|
757
|
+
const { replayOf, placeholderOf, restoredOf } = replaying ?? {};
|
|
758
|
+
// A record's copy is no loader's push. Any other push, while a loader
|
|
759
|
+
// has copies or a restore to reconcile, is attributed as the capture
|
|
637
760
|
// credits it: to the innermost loader body it is made in that has
|
|
638
|
-
//
|
|
639
|
-
//
|
|
640
|
-
//
|
|
641
|
-
//
|
|
761
|
+
// copies, up to a hole's body, so a replay made in a body replaces
|
|
762
|
+
// that body's copies and a dependency's push replaces those of the
|
|
763
|
+
// live-lane loader it ran under. Its record copy stands when a
|
|
764
|
+
// restored loader counts it (restoringLoader).
|
|
642
765
|
let pusher: string | undefined;
|
|
643
766
|
let restoredBy: string | undefined;
|
|
644
|
-
if (
|
|
645
|
-
if (
|
|
646
|
-
|
|
767
|
+
if (restoredOf === undefined && placeholderOf === undefined) {
|
|
768
|
+
if (
|
|
769
|
+
pendingReplays.size > 0 ||
|
|
770
|
+
pendingPlaceholders.size > 0 ||
|
|
771
|
+
replaced.size > 0
|
|
772
|
+
) {
|
|
773
|
+
pusher = findEnclosingLoaderBody(hasCopies, isHole);
|
|
774
|
+
}
|
|
775
|
+
if (restoredLoaders.size > 0 || authority) {
|
|
776
|
+
restoredBy = restoringLoader(replayOf);
|
|
647
777
|
}
|
|
648
|
-
if (restoredLoaders.size > 0) restoredBy = restoringLoader(replayOf);
|
|
649
778
|
}
|
|
650
779
|
if (restoredBy !== undefined && !holdsThenable(value)) {
|
|
651
780
|
const existing = data[handleName]?.[segmentId];
|
|
@@ -663,32 +792,52 @@ export function createHandleStore(): HandleStore {
|
|
|
663
792
|
const loader = (loaderPush ?? isInsideLoaderScope()) || undefined;
|
|
664
793
|
let at: number | undefined;
|
|
665
794
|
let liveOf: string | undefined;
|
|
666
|
-
|
|
795
|
+
let under: string[] | undefined;
|
|
796
|
+
if (restoredOf !== undefined) {
|
|
667
797
|
restoredLoaders.add(restoredOf);
|
|
798
|
+
} else if (placeholderOf !== undefined) {
|
|
799
|
+
placeholderLoaders.add(placeholderOf);
|
|
668
800
|
} else if (replayOf !== undefined && redelivery?.has(values)) {
|
|
669
801
|
at = redelivery.get(values)!;
|
|
670
802
|
redelivery.set(values, at + 1);
|
|
671
803
|
} else if (pusher !== undefined) {
|
|
804
|
+
under = replacedAround(pusher);
|
|
672
805
|
at = liveReplacementIndex(pusher, values);
|
|
806
|
+
// No place of its own here: it follows the run it was made in.
|
|
807
|
+
for (let i = 0; at === undefined && under && i < under.length; i++) {
|
|
808
|
+
at = runCursor(under[i]!, values);
|
|
809
|
+
}
|
|
673
810
|
liveOf = pusher;
|
|
674
811
|
} else if (restoredBy !== undefined) {
|
|
675
812
|
at = restoredInsertIndex(values, restoredBy);
|
|
676
|
-
if (
|
|
813
|
+
if (replayOf === undefined) liveOf = restoredBy;
|
|
677
814
|
}
|
|
678
815
|
insertSlot(
|
|
679
816
|
values,
|
|
680
817
|
at,
|
|
681
818
|
value,
|
|
682
|
-
replayOf
|
|
683
|
-
|
|
819
|
+
replayOf !== undefined ||
|
|
820
|
+
placeholderOf !== undefined ||
|
|
821
|
+
restoredOf !== undefined ||
|
|
822
|
+
liveOf !== undefined ||
|
|
823
|
+
owner !== undefined
|
|
824
|
+
? {
|
|
825
|
+
loader,
|
|
826
|
+
replayOf,
|
|
827
|
+
placeholderOf,
|
|
828
|
+
restoredOf,
|
|
829
|
+
liveOf,
|
|
830
|
+
under,
|
|
831
|
+
owner,
|
|
832
|
+
}
|
|
684
833
|
: loader
|
|
685
834
|
? LOADER_SLOT
|
|
686
835
|
: undefined,
|
|
687
836
|
);
|
|
688
|
-
if (replayOf) {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
837
|
+
if (replayOf !== undefined) {
|
|
838
|
+
addPending(pendingReplays, replayOf, values);
|
|
839
|
+
} else if (placeholderOf !== undefined) {
|
|
840
|
+
addPending(pendingPlaceholders, placeholderOf, values);
|
|
692
841
|
}
|
|
693
842
|
|
|
694
843
|
// Bump the version; each consumer's cursor decides when to clone+yield.
|
|
@@ -696,7 +845,7 @@ export function createHandleStore(): HandleStore {
|
|
|
696
845
|
signalEmission();
|
|
697
846
|
},
|
|
698
847
|
|
|
699
|
-
//
|
|
848
|
+
// All three go through this.push: active captures see a copy like a live
|
|
700
849
|
// push, and a diverting capture keeps it out of the store.
|
|
701
850
|
pushReplayed(
|
|
702
851
|
handleName: string,
|
|
@@ -707,6 +856,21 @@ export function createHandleStore(): HandleStore {
|
|
|
707
856
|
replayPush(this, { replayOf: loaderId }, handleName, segmentId, value);
|
|
708
857
|
},
|
|
709
858
|
|
|
859
|
+
pushPlaceholder(
|
|
860
|
+
handleName: string,
|
|
861
|
+
segmentId: string,
|
|
862
|
+
value: unknown,
|
|
863
|
+
loaderId: string,
|
|
864
|
+
): void {
|
|
865
|
+
replayPush(
|
|
866
|
+
this,
|
|
867
|
+
{ placeholderOf: loaderId },
|
|
868
|
+
handleName,
|
|
869
|
+
segmentId,
|
|
870
|
+
value,
|
|
871
|
+
);
|
|
872
|
+
},
|
|
873
|
+
|
|
710
874
|
pushRestored(
|
|
711
875
|
handleName: string,
|
|
712
876
|
segmentId: string,
|
|
@@ -716,30 +880,52 @@ export function createHandleStore(): HandleStore {
|
|
|
716
880
|
replayPush(this, { restoredOf: loaderId }, handleName, segmentId, value);
|
|
717
881
|
},
|
|
718
882
|
|
|
719
|
-
|
|
720
|
-
|
|
883
|
+
setRecordAuthority(of: (loaderId: string) => RecordAuthority): void {
|
|
884
|
+
authority = of;
|
|
721
885
|
},
|
|
722
886
|
|
|
723
887
|
settleLoaderRun(loaderId: string): void {
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
888
|
+
if (completed) return;
|
|
889
|
+
const hole = isHole(loaderId);
|
|
890
|
+
if (
|
|
891
|
+
!pendingPlaceholders.has(loaderId) &&
|
|
892
|
+
!(hole && pendingReplays.has(loaderId))
|
|
893
|
+
) {
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
dropCopies(loaderId, hole);
|
|
729
897
|
version++;
|
|
730
898
|
signalEmission();
|
|
731
899
|
},
|
|
732
900
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
901
|
+
replacePlaceholders(
|
|
902
|
+
loaderIds: readonly string[],
|
|
903
|
+
deliver: () => void,
|
|
904
|
+
): void {
|
|
905
|
+
// One pass per array over every loader's placeholders: an array two of
|
|
906
|
+
// them share gets one anchor, the first removed position.
|
|
907
|
+
let given: Set<string> | undefined;
|
|
908
|
+
let arrays: Set<unknown[]> | undefined;
|
|
909
|
+
if (!completed) {
|
|
910
|
+
for (const loaderId of loaderIds) {
|
|
911
|
+
const pending = pendingPlaceholders.get(loaderId);
|
|
912
|
+
if (!pending) continue;
|
|
913
|
+
pendingPlaceholders.delete(loaderId);
|
|
914
|
+
(given ??= new Set()).add(loaderId);
|
|
915
|
+
arrays ??= new Set();
|
|
916
|
+
for (const values of pending) arrays.add(values);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
if (!given || !arrays) {
|
|
736
920
|
deliver();
|
|
737
921
|
return;
|
|
738
922
|
}
|
|
739
|
-
|
|
923
|
+
const owners = given;
|
|
924
|
+
const ofGiven = (tag: SlotTag): boolean =>
|
|
925
|
+
tag.placeholderOf !== undefined && owners.has(tag.placeholderOf);
|
|
740
926
|
const anchors = new Map<unknown[], number>();
|
|
741
|
-
for (const values of
|
|
742
|
-
const first =
|
|
927
|
+
for (const values of arrays) {
|
|
928
|
+
const first = removeSlots(values, ofGiven);
|
|
743
929
|
if (first !== undefined) anchors.set(values, first);
|
|
744
930
|
}
|
|
745
931
|
const outer = redelivery;
|
|
@@ -758,6 +944,23 @@ export function createHandleStore(): HandleStore {
|
|
|
758
944
|
return this.settled.then(() => cloneHandleData(data));
|
|
759
945
|
},
|
|
760
946
|
|
|
947
|
+
freezeDocumentSnapshot(): void {
|
|
948
|
+
if (documentSnapshot) return;
|
|
949
|
+
if (process.env.NODE_ENV !== "production" && documentStreamRead) {
|
|
950
|
+
const error = new Error(
|
|
951
|
+
"HandleStore.freezeDocumentSnapshot() ran after the document's " +
|
|
952
|
+
'handle stream (stream("settled")) was first read. That stream ' +
|
|
953
|
+
"keeps reading the live store, so the document hydrates with " +
|
|
954
|
+
"whatever a loader pushed by then instead of the frozen state " +
|
|
955
|
+
"(issue #1035). Freeze before the payload that carries the " +
|
|
956
|
+
"stream starts rendering.",
|
|
957
|
+
);
|
|
958
|
+
if (this.onError) this.onError(error);
|
|
959
|
+
else console.error(error);
|
|
960
|
+
}
|
|
961
|
+
documentSnapshot = { version, data: cloneHandleData(data) };
|
|
962
|
+
},
|
|
963
|
+
|
|
761
964
|
async *stream(
|
|
762
965
|
until: "settled" | "fullySettled" = "fullySettled",
|
|
763
966
|
): AsyncGenerator<HandleData, void, unknown> {
|
|
@@ -768,6 +971,22 @@ export function createHandleStore(): HandleStore {
|
|
|
768
971
|
// then, so the completed flag must be armed here.
|
|
769
972
|
notifyDrain();
|
|
770
973
|
|
|
974
|
+
if (until === "settled") {
|
|
975
|
+
// A frozen document lane cannot change, so it is served at once:
|
|
976
|
+
// SSR and the pre-hydration drain block on this stream, and the
|
|
977
|
+
// batching hop below would cost every shell HIT a timer tick. It is
|
|
978
|
+
// yielded as the frozen object itself, already a copy: the lane's
|
|
979
|
+
// one consumer (resolvedHandleStream) builds a new object from a
|
|
980
|
+
// yield and never writes to it.
|
|
981
|
+
if (documentSnapshot) {
|
|
982
|
+
if (Object.keys(documentSnapshot.data).length > 0) {
|
|
983
|
+
yield documentSnapshot.data;
|
|
984
|
+
}
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
documentStreamRead = true;
|
|
988
|
+
}
|
|
989
|
+
|
|
771
990
|
// Per-consumer termination flag driven by the chosen barrier. The
|
|
772
991
|
// global `completed` (late-push guard) always keys on FULL drain via
|
|
773
992
|
// notifyDrain — a "settled"-scoped consumer finishing does not make
|
|
@@ -806,8 +1025,9 @@ export function createHandleStore(): HandleStore {
|
|
|
806
1025
|
// state. This channel exists only for pushes that land AFTER `settled`
|
|
807
1026
|
// — streaming loader bodies writing meta/breadcrumbs mid-body.
|
|
808
1027
|
await this.settled;
|
|
809
|
-
|
|
810
|
-
|
|
1028
|
+
// A frozen document snapshot carried nothing newer than its version.
|
|
1029
|
+
let seen = documentSnapshot?.version ?? version;
|
|
1030
|
+
if (auxInflightCount === 0 && version === seen) {
|
|
811
1031
|
// Auxiliary lane already drained: every loader push (if any) beat the
|
|
812
1032
|
// handler barrier and rode the normal snapshot. Nothing late follows —
|
|
813
1033
|
// handler pushes after settled are illegal by contract.
|
|
@@ -870,6 +1090,16 @@ export function createHandleStore(): HandleStore {
|
|
|
870
1090
|
return result;
|
|
871
1091
|
},
|
|
872
1092
|
|
|
1093
|
+
getOwnedSegmentIds(): string[] {
|
|
1094
|
+
const ids = new Set<string>();
|
|
1095
|
+
for (const handleName in data) {
|
|
1096
|
+
for (const id in data[handleName]) {
|
|
1097
|
+
if (!ids.has(id) && this.getRecordOwners(id)) ids.add(id);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
return [...ids];
|
|
1101
|
+
},
|
|
1102
|
+
|
|
873
1103
|
replaySegmentData(
|
|
874
1104
|
segmentId: string,
|
|
875
1105
|
segmentHandles: Record<string, unknown[]>,
|