@rangojs/router 0.18.0 → 0.19.1
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/types/browser/event-controller.d.ts +3 -3
- package/dist/types/browser/history-state.d.ts +2 -0
- package/dist/types/browser/navigation-transaction.d.ts +20 -2
- package/dist/types/browser/partial-update.d.ts +16 -1
- package/dist/types/browser/prefetch/cache.d.ts +6 -6
- package/dist/types/browser/react/Link.d.ts +19 -9
- package/dist/types/browser/react/context.d.ts +2 -2
- package/dist/types/browser/react/location-state-shared.d.ts +95 -56
- package/dist/types/browser/react/location-state.d.ts +1 -1
- package/dist/types/browser/transition-when.d.ts +80 -0
- package/dist/types/browser/types.d.ts +104 -9
- package/dist/types/build/generate-manifest.d.ts +8 -0
- package/dist/types/cache/background-task.d.ts +24 -2
- package/dist/types/cache/cache-exec-scope.d.ts +37 -0
- package/dist/types/cache/cache-key-utils.d.ts +44 -13
- package/dist/types/cache/cache-policy.d.ts +1 -1
- package/dist/types/cache/cache-runtime.d.ts +7 -0
- package/dist/types/cache/cache-scope.d.ts +117 -35
- package/dist/types/cache/cache-tag.d.ts +48 -5
- package/dist/types/cache/cf/cf-cache-store.d.ts +55 -44
- package/dist/types/cache/cf/cf-shell-frame.d.ts +1 -1
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +0 -15
- package/dist/types/cache/document-cache.d.ts +10 -0
- package/dist/types/cache/handle-snapshot.d.ts +40 -8
- package/dist/types/cache/invalidation-order.d.ts +23 -0
- package/dist/types/cache/request-tag-mask.d.ts +54 -0
- package/dist/types/cache/shell-snapshot.d.ts +88 -141
- package/dist/types/cache/tag-invalidation.d.ts +38 -14
- package/dist/types/cache/types.d.ts +68 -61
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +23 -16
- package/dist/types/client-urls/navigation.d.ts +28 -2
- package/dist/types/client-urls/server-projection.d.ts +5 -2
- package/dist/types/client-urls/types.d.ts +5 -5
- package/dist/types/client.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/route-definition/helpers-types.d.ts +11 -11
- package/dist/types/route-name.d.ts +7 -0
- package/dist/types/router/error-handling.d.ts +1 -1
- package/dist/types/router/intercept-resolution.d.ts +2 -0
- package/dist/types/router/match-context.d.ts +5 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
- package/dist/types/router/router-options.d.ts +4 -1
- package/dist/types/router/segment-resolution/loader-cache.d.ts +18 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +14 -40
- package/dist/types/router/segment-resolution/mask-nested.d.ts +11 -3
- package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
- package/dist/types/router/transition-when.d.ts +24 -10
- package/dist/types/rsc/attach-transition-when.d.ts +18 -0
- package/dist/types/rsc/full-payload.d.ts +14 -7
- package/dist/types/rsc/response-cache-serve.d.ts +15 -12
- package/dist/types/rsc/shell-build-manifest.d.ts +4 -3
- package/dist/types/rsc/shell-capture-constants.d.ts +77 -20
- package/dist/types/rsc/shell-capture.d.ts +78 -81
- package/dist/types/rsc/shell-serve.d.ts +19 -48
- package/dist/types/rsc/types.d.ts +2 -0
- package/dist/types/segment-system.d.ts +7 -0
- package/dist/types/server/context.d.ts +126 -61
- package/dist/types/server/cookie-store.d.ts +30 -18
- package/dist/types/server/handle-store.d.ts +52 -1
- package/dist/types/server/request-context.d.ts +66 -45
- package/dist/types/server/request-headers.d.ts +44 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/index.d.ts +3 -2
- package/dist/types/testing/internal/action-id.d.ts +7 -0
- package/dist/types/testing/location-state-key.d.ts +24 -0
- package/dist/types/testing/render-route.d.ts +17 -1
- package/dist/types/testing/run-transition-when.d.ts +44 -69
- package/dist/types/testing/serve-shell-request.d.ts +7 -6
- package/dist/types/testing/shell-status.d.ts +34 -22
- package/dist/types/transition-when-ref.d.ts +79 -0
- package/dist/types/types/cache-types.d.ts +15 -3
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/segments.d.ts +86 -71
- package/dist/types/urls/path-helper-types.d.ts +3 -4
- package/dist/types/urls/pattern-types.d.ts +3 -4
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
- package/dist/vite/index.js +962 -97
- package/package.json +1 -1
- package/skills/cache-guide/SKILL.md +76 -49
- package/skills/caching/SKILL.md +167 -69
- package/skills/client-urls/SKILL.md +1 -1
- package/skills/comparison/references/framework-comparison.md +8 -5
- package/skills/document-cache/SKILL.md +24 -0
- package/skills/hooks/SKILL.md +3 -1
- package/skills/hooks/navigation.md +23 -2
- package/skills/hooks/state.md +98 -6
- package/skills/intercept/SKILL.md +29 -12
- package/skills/loader/SKILL.md +52 -21
- package/skills/middleware/SKILL.md +18 -0
- package/skills/migrate-nextjs/SKILL.md +3 -3
- package/skills/observability/SKILL.md +6 -5
- package/skills/parallel/SKILL.md +1 -1
- package/skills/ppr/SKILL.md +129 -77
- package/skills/prerender/SKILL.md +3 -3
- package/skills/rango/SKILL.md +25 -6
- package/skills/route/SKILL.md +2 -0
- package/skills/testing/SKILL.md +27 -25
- package/skills/testing/cache-prerender.md +9 -9
- package/skills/testing/client-components.md +45 -3
- package/skills/testing/loader.md +3 -2
- package/skills/testing/middleware.md +1 -0
- package/skills/testing/predicates.md +49 -49
- package/skills/testing/render-handler.md +1 -0
- package/skills/testing/serve-shell-request.md +14 -0
- package/skills/testing/server-actions.md +9 -3
- package/skills/theme/SKILL.md +8 -4
- package/skills/typesafety/env-and-bindings.md +8 -0
- package/skills/use-cache/SKILL.md +67 -19
- package/skills/view-transitions/SKILL.md +150 -29
- package/src/__augment-tests__/augmented.check.ts +23 -0
- package/src/browser/event-controller.ts +4 -4
- package/src/browser/history-state.ts +52 -1
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +77 -18
- package/src/browser/navigation-client.ts +4 -3
- package/src/browser/navigation-store.ts +43 -1
- package/src/browser/navigation-transaction.ts +31 -11
- package/src/browser/partial-update.ts +146 -35
- package/src/browser/prefetch/cache.ts +6 -6
- package/src/browser/prefetch/fetch.ts +2 -2
- package/src/browser/react/Link.tsx +38 -33
- package/src/browser/react/NavigationProvider.tsx +2 -2
- package/src/browser/react/context.ts +2 -2
- package/src/browser/react/location-state-shared.ts +241 -49
- package/src/browser/react/location-state.ts +5 -2
- package/src/browser/rsc-router.tsx +4 -0
- package/src/browser/server-action-bridge.ts +84 -15
- package/src/browser/transition-when.ts +205 -0
- package/src/browser/types.ts +124 -10
- package/src/build/generate-manifest.ts +77 -1
- package/src/cache/background-task.ts +55 -2
- package/src/cache/cache-exec-scope.ts +54 -1
- package/src/cache/cache-key-utils.ts +56 -16
- package/src/cache/cache-policy.ts +9 -4
- package/src/cache/cache-runtime.ts +202 -144
- package/src/cache/cache-scope.ts +304 -112
- package/src/cache/cache-tag.ts +209 -13
- package/src/cache/cf/cf-cache-store.ts +152 -76
- package/src/cache/cf/cf-shell-frame.ts +2 -2
- package/src/cache/cf/cf-tag-marker-memo.ts +11 -100
- package/src/cache/document-cache.ts +53 -7
- package/src/cache/handle-snapshot.ts +52 -9
- package/src/cache/invalidation-order.ts +84 -0
- package/src/cache/request-tag-mask.ts +147 -0
- package/src/cache/shell-snapshot.ts +130 -348
- package/src/cache/tag-invalidation.ts +57 -51
- package/src/cache/types.ts +71 -67
- package/src/cache/vercel/vercel-cache-store.ts +61 -58
- package/src/client-urls/client-root.tsx +8 -4
- package/src/client-urls/client-urls.ts +8 -6
- package/src/client-urls/navigation.ts +55 -4
- package/src/client-urls/server-projection.ts +31 -10
- package/src/client-urls/types.ts +12 -5
- package/src/client.tsx +2 -0
- package/src/host/cookie-handler.ts +2 -1
- package/src/index.rsc.ts +3 -0
- package/src/index.ts +3 -0
- package/src/loader-redirect.tsx +7 -3
- package/src/prerender/build-shell-capture.ts +155 -146
- package/src/route-definition/dsl-helpers.ts +2 -0
- package/src/route-definition/helpers-types.ts +11 -11
- package/src/route-name.ts +12 -0
- package/src/router/content-negotiation.ts +4 -1
- package/src/router/error-handling.ts +5 -2
- package/src/router/handler-context.ts +10 -7
- package/src/router/intercept-resolution.ts +11 -1
- package/src/router/loader-resolution.ts +42 -5
- package/src/router/logging.ts +5 -3
- package/src/router/match-api.ts +62 -34
- package/src/router/match-context.ts +5 -0
- package/src/router/match-handlers.ts +9 -29
- package/src/router/match-middleware/background-revalidation.ts +3 -5
- package/src/router/match-middleware/cache-lookup.ts +35 -19
- package/src/router/match-middleware/cache-store.ts +15 -9
- package/src/router/match-result.ts +1 -0
- package/src/router/middleware.ts +2 -5
- package/src/router/navigation-snapshot.ts +6 -5
- package/src/router/prerender-match.ts +4 -0
- package/src/router/request-classification.ts +7 -4
- package/src/router/router-options.ts +4 -1
- package/src/router/segment-resolution/fresh.ts +0 -4
- package/src/router/segment-resolution/loader-cache.ts +152 -26
- package/src/router/segment-resolution/loader-mask.ts +3 -4
- package/src/router/segment-resolution/loader-snapshot.ts +15 -78
- package/src/router/segment-resolution/mask-nested.ts +47 -22
- package/src/router/segment-resolution/revalidation.ts +0 -3
- package/src/router/segment-resolution/view-transition-default.ts +6 -21
- package/src/router/telemetry.ts +6 -3
- package/src/router/transition-when.ts +34 -52
- package/src/router.ts +2 -1
- package/src/rsc/attach-transition-when.ts +47 -0
- package/src/rsc/full-payload.ts +30 -10
- package/src/rsc/handler.ts +12 -10
- package/src/rsc/loader-fetch.ts +2 -1
- package/src/rsc/manifest-init.ts +4 -0
- package/src/rsc/origin-guard.ts +8 -5
- package/src/rsc/progressive-enhancement.ts +7 -30
- package/src/rsc/response-cache-serve.ts +68 -80
- package/src/rsc/response-route-handler.ts +2 -2
- package/src/rsc/rsc-rendering.ts +206 -252
- package/src/rsc/server-action.ts +8 -20
- package/src/rsc/shell-build-manifest.ts +22 -20
- package/src/rsc/shell-capture-constants.ts +136 -20
- package/src/rsc/shell-capture.ts +352 -589
- package/src/rsc/shell-serve.ts +73 -109
- package/src/rsc/ssr-setup.ts +3 -2
- package/src/rsc/types.ts +2 -0
- package/src/segment-system.tsx +40 -7
- package/src/server/context.ts +263 -124
- package/src/server/cookie-store.ts +153 -127
- package/src/server/handle-store.ts +245 -27
- package/src/server/request-context.ts +134 -87
- package/src/server/request-headers.ts +134 -0
- package/src/server.ts +4 -0
- package/src/testing/dispatch.ts +4 -7
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/internal/action-id.ts +25 -0
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/location-state-key.ts +45 -0
- package/src/testing/render-handler.ts +1 -1
- package/src/testing/render-route.tsx +109 -16
- package/src/testing/run-client-revalidate.ts +3 -19
- package/src/testing/run-loader.ts +5 -1
- package/src/testing/run-transition-when.ts +109 -172
- package/src/testing/serve-shell-request.ts +10 -7
- package/src/testing/shell-status.ts +60 -56
- package/src/transition-when-ref.ts +220 -0
- package/src/types/cache-types.ts +15 -3
- package/src/types/index.ts +3 -0
- package/src/types/segments.ts +94 -91
- package/src/urls/path-helper-types.ts +3 -4
- package/src/urls/pattern-types.ts +3 -4
- package/src/vite/discovery/discover-routers.ts +3 -0
- package/src/vite/discovery/shell-prerender-phase.ts +0 -2
- package/src/vite/discovery/virtual-module-codegen.ts +16 -6
- package/src/vite/plugins/transition-when-hoist.ts +1064 -0
- package/src/vite/rango.ts +2 -0
- package/src/vite/router-discovery.ts +75 -5
- package/dist/types/rsc/transition-gate.d.ts +0 -23
- package/src/rsc/transition-gate.ts +0 -86
|
@@ -27,6 +27,7 @@ declare global {
|
|
|
27
27
|
* - KV L2 for cross-colo cache persistence
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
+
import { requestHeaders } from "../../server/request-headers.js";
|
|
30
31
|
import type {
|
|
31
32
|
SegmentCacheStore,
|
|
32
33
|
CachedEntryData,
|
|
@@ -37,6 +38,7 @@ import type {
|
|
|
37
38
|
ShellCacheEntry,
|
|
38
39
|
ShellDocumentRead,
|
|
39
40
|
ShellDocumentReadOptions,
|
|
41
|
+
ShellEntryHead,
|
|
40
42
|
ShellReadStats,
|
|
41
43
|
ShellSnapshotFailure,
|
|
42
44
|
ShellSnapshotRecord,
|
|
@@ -99,9 +101,12 @@ import {
|
|
|
99
101
|
getTagMarkerMemo,
|
|
100
102
|
getTagMarkerInflight,
|
|
101
103
|
getShellMarkerReads,
|
|
102
|
-
isRequestMasked,
|
|
103
|
-
maskRequestTags,
|
|
104
104
|
} from "./cf-tag-marker-memo.js";
|
|
105
|
+
import {
|
|
106
|
+
gateMarkerRead,
|
|
107
|
+
maskRequestTags,
|
|
108
|
+
maskedForRequest,
|
|
109
|
+
} from "../request-tag-mask.js";
|
|
105
110
|
import {
|
|
106
111
|
TagMarkerMemo,
|
|
107
112
|
TagNameHints,
|
|
@@ -353,12 +358,21 @@ function isMemoizableShell(head: ShellFrameHead): boolean {
|
|
|
353
358
|
);
|
|
354
359
|
}
|
|
355
360
|
|
|
356
|
-
/**
|
|
361
|
+
/**
|
|
362
|
+
* @internal Reset the per-isolate shell and marker memos and the once-per-
|
|
363
|
+
* isolate warnings (tests).
|
|
364
|
+
*/
|
|
357
365
|
export function resetCFShellMemoForTests(): void {
|
|
358
366
|
cfShellMemo.clear();
|
|
359
367
|
recentShellInvalidations.clear();
|
|
360
368
|
cfMarkerMemo.clear();
|
|
361
369
|
cfTagHints.clear();
|
|
370
|
+
warnedNoKvReadInvalidation.clear();
|
|
371
|
+
warnedTagInvalidationTtlFloor.clear();
|
|
372
|
+
warnedShellTagsNoEviction.clear();
|
|
373
|
+
warnedNoTagInvalidationTtl.clear();
|
|
374
|
+
warnedCacheTagHeaderOverflow.clear();
|
|
375
|
+
warnedCacheTagOverflowUncacheable.clear();
|
|
362
376
|
}
|
|
363
377
|
|
|
364
378
|
/** openShellFrame outcome: the head and prelude, or why the read failed. */
|
|
@@ -397,7 +411,7 @@ function debugTimings(
|
|
|
397
411
|
}
|
|
398
412
|
|
|
399
413
|
/** The public entry shape of a frame head (no prelude, no snapshot). */
|
|
400
|
-
function shellHeadToEntry(head: ShellFrameHead):
|
|
414
|
+
function shellHeadToEntry(head: ShellFrameHead): ShellEntryHead {
|
|
401
415
|
return {
|
|
402
416
|
...(head.po !== undefined ? { postponed: head.po } : {}),
|
|
403
417
|
reactVersion: head.rv,
|
|
@@ -583,7 +597,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
583
597
|
this.waitUntil = (fn) => options.ctx.waitUntil(fn());
|
|
584
598
|
this.kv = options.kv;
|
|
585
599
|
// Without KV, isTagsInvalidatedSince has no durable history — it answers
|
|
586
|
-
// from the per-request
|
|
600
|
+
// from the per-request mask at best. Runtime shells tolerate that (purge
|
|
587
601
|
// eviction + ttl/swr bound the staleness), but an immutable TAGGED
|
|
588
602
|
// build-manifest shell must not serve on such a store: nothing could ever
|
|
589
603
|
// evict it (purge cannot delete a build asset), so the manifest gate
|
|
@@ -712,7 +726,9 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
712
726
|
const request = _getRequestContext()?.request as
|
|
713
727
|
| (Request & { cf?: { colo?: unknown } })
|
|
714
728
|
| undefined;
|
|
715
|
-
const ray = request
|
|
729
|
+
const ray = request
|
|
730
|
+
? (requestHeaders(request).get("cf-ray") ?? undefined)
|
|
731
|
+
: undefined;
|
|
716
732
|
const cfColo = request?.cf?.colo;
|
|
717
733
|
const colo =
|
|
718
734
|
typeof cfColo === "string"
|
|
@@ -2008,7 +2024,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
2008
2024
|
// serves its own shell from the Cache API. Tag eviction then needs purge
|
|
2009
2025
|
// mode (tagPurge) — shell L1 entries carry the same namespaced Cache-Tag
|
|
2010
2026
|
// tokens as the data families, read-your-own-writes comes from the
|
|
2011
|
-
// per-request
|
|
2027
|
+
// per-request tag mask, and the capture resurrection race narrows to
|
|
2012
2028
|
// cross-request timing bounded by ttl+swr (the data families' documented
|
|
2013
2029
|
// purge-mode stance). KV-less WITHOUT tagPurge still caches: freshness is
|
|
2014
2030
|
// ttl/swr only, and a tagged write warns once that invalidation cannot
|
|
@@ -2038,7 +2054,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
2038
2054
|
/**
|
|
2039
2055
|
* Generation gate for shell writes and the capture scheduler
|
|
2040
2056
|
* (isTagsInvalidatedSince). With KV it is the durable marker cascade.
|
|
2041
|
-
* Without KV there are no markers: in purge mode the per-request
|
|
2057
|
+
* Without KV there are no markers: in purge mode the per-request mask is
|
|
2042
2058
|
* the only signal — a capture racing THIS request's updateTag() is still
|
|
2043
2059
|
* rejected (read-your-own-writes), while cross-request races are bounded
|
|
2044
2060
|
* by ttl+swr exactly like the data families' purge-mode writes. Without
|
|
@@ -2052,14 +2068,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
2052
2068
|
if (this.kv) return this.isGloballyInvalidated(tags, since);
|
|
2053
2069
|
if (!this.tagPurge || !Array.isArray(tags) || tags.length === 0 || !since)
|
|
2054
2070
|
return false;
|
|
2055
|
-
|
|
2056
|
-
if (!ctx) return false;
|
|
2057
|
-
const memo = getTagMarkerMemo(ctx, this);
|
|
2058
|
-
for (const tag of tags) {
|
|
2059
|
-
const marker = memo.get(tag);
|
|
2060
|
-
if (marker != null && marker >= since) return true;
|
|
2061
|
-
}
|
|
2062
|
-
return false;
|
|
2071
|
+
return maskedForRequest(_getRequestContext(), this, tags, since);
|
|
2063
2072
|
}
|
|
2064
2073
|
|
|
2065
2074
|
/**
|
|
@@ -2082,9 +2091,9 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
2082
2091
|
if (!read) return null;
|
|
2083
2092
|
const snapshot = await read.snapshot;
|
|
2084
2093
|
if (await read.snapshotFailure) return null;
|
|
2085
|
-
|
|
2094
|
+
// A frame with no snapshot bytes carries no records.
|
|
2095
|
+
const entry: ShellCacheEntry = { ...read.entry, snapshot: snapshot ?? [] };
|
|
2086
2096
|
if (!entry.navigationOnly) entry.prelude = bytesToBase64(read.prelude);
|
|
2087
|
-
if (snapshot) entry.snapshot = snapshot;
|
|
2088
2097
|
return { entry, shouldRevalidate: read.shouldRevalidate };
|
|
2089
2098
|
}
|
|
2090
2099
|
|
|
@@ -2179,7 +2188,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
2179
2188
|
// generation marker — see the capture-start/purge race documented
|
|
2180
2189
|
// above. Without KV there are no markers: L1-only shells adopt the
|
|
2181
2190
|
// data families' purge-mode read semantics (a hit that survived the
|
|
2182
|
-
// purge is trusted; the per-request
|
|
2191
|
+
// purge is trusted; the per-request mask covers this request's own
|
|
2183
2192
|
// updateTag() writes; entries a purge cannot reach fall back to the
|
|
2184
2193
|
// marker check, which fails open KV-less).
|
|
2185
2194
|
const opened = await this.openShellFrame(
|
|
@@ -3239,6 +3248,8 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
3239
3248
|
if (!this.kv || !Array.isArray(tags) || tags.length === 0 || !taggedAt)
|
|
3240
3249
|
return false;
|
|
3241
3250
|
const ctx = _getRequestContext();
|
|
3251
|
+
// This request's own invalidations first (#973): no marker read needed.
|
|
3252
|
+
if (maskedForRequest(ctx, this, tags, taggedAt)) return true;
|
|
3242
3253
|
const memo = ctx ? getTagMarkerMemo(ctx, this) : undefined;
|
|
3243
3254
|
const inflight = ctx ? getTagMarkerInflight(ctx, this) : undefined;
|
|
3244
3255
|
try {
|
|
@@ -3249,6 +3260,9 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
3249
3260
|
: this.readTagMarker(tag, memo, inflight),
|
|
3250
3261
|
),
|
|
3251
3262
|
);
|
|
3263
|
+
// A read in flight when this request invalidated one of the tags (a
|
|
3264
|
+
// sibling segment's updateTag()) resolved to the marker before it.
|
|
3265
|
+
if (maskedForRequest(ctx, this, tags, taggedAt)) return true;
|
|
3252
3266
|
for (const marker of markers) {
|
|
3253
3267
|
if (marker != null && marker >= taggedAt) return true;
|
|
3254
3268
|
}
|
|
@@ -3268,16 +3282,16 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
3268
3282
|
* configured), invalidateTags() evicts L1 entries via a Cloudflare
|
|
3269
3283
|
* purge-by-tag call, so a hit that SURVIVED is trusted without a per-read
|
|
3270
3284
|
* marker lookup — that skipped lookup is the entire point of purge mode.
|
|
3271
|
-
* Only the
|
|
3272
|
-
* request that ran updateTag() still
|
|
3273
|
-
* propagation window (read-your-own-writes).
|
|
3285
|
+
* Only the request's mask and its memo are consulted (synchronous, no KV
|
|
3286
|
+
* read) so a request that ran updateTag() still misses its own entries
|
|
3287
|
+
* during the purge propagation window (read-your-own-writes).
|
|
3274
3288
|
*
|
|
3275
3289
|
* The trust is conditional on the entry actually CARRYING this store's
|
|
3276
3290
|
* entry Cache-Tag tokens (`headers`): an entry a purge cannot reach — one
|
|
3277
3291
|
* written before the tokens existed, or whose tag set overflowed the
|
|
3278
3292
|
* Cache-Tag header limit — keeps the full marker check, or purge mode
|
|
3279
3293
|
* would serve it stale until TTL with no eviction path. KV-less there are
|
|
3280
|
-
* no markers, so it gets the per-request
|
|
3294
|
+
* no markers, so it gets the per-request mask check like any hit (#973).
|
|
3281
3295
|
*
|
|
3282
3296
|
* Without tagPurge this is the full marker cascade. KV-tier reads and
|
|
3283
3297
|
* shells always use isGloballyInvalidated directly: purge cannot reach KV,
|
|
@@ -3296,6 +3310,8 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
3296
3310
|
}
|
|
3297
3311
|
const ctx = _getRequestContext();
|
|
3298
3312
|
if (!ctx) return false;
|
|
3313
|
+
if (maskedForRequest(ctx, this, tags, taggedAt)) return true;
|
|
3314
|
+
// Markers this request read from KV for other entries.
|
|
3299
3315
|
const memo = getTagMarkerMemo(ctx, this);
|
|
3300
3316
|
for (const tag of tags) {
|
|
3301
3317
|
const marker = memo.get(tag);
|
|
@@ -3312,9 +3328,8 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
3312
3328
|
/**
|
|
3313
3329
|
* Read a tag's latest invalidation timestamp (or null if never invalidated)
|
|
3314
3330
|
* through the cascade: per-request memo -> per-colo L1 cache (only when
|
|
3315
|
-
* tagCacheTtl > 0) -> KV (the global truth).
|
|
3316
|
-
*
|
|
3317
|
-
* and every KV/L1 result is written back into the memo. A Cache API miss
|
|
3331
|
+
* tagCacheTtl > 0) -> KV (the global truth). Every KV/L1 result is written
|
|
3332
|
+
* back into the memo, so the request reads one value per tag. A Cache API miss
|
|
3318
3333
|
* always falls through to KV; absence is represented by a cached sentinel,
|
|
3319
3334
|
* never by a miss.
|
|
3320
3335
|
*
|
|
@@ -3348,9 +3363,9 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
3348
3363
|
}
|
|
3349
3364
|
|
|
3350
3365
|
/**
|
|
3351
|
-
* A PPR shell read's marker for `tag`: this request's own
|
|
3352
|
-
* one
|
|
3353
|
-
*
|
|
3366
|
+
* A PPR shell read's marker for `tag`: this request's own store read if it
|
|
3367
|
+
* has one, else through the per-isolate marker memo (isolate-tag-memo.ts,
|
|
3368
|
+
* stale-while-revalidate
|
|
3354
3369
|
* under `markerFreshMs` / `markerMaxStaleMs`; a request carrying the
|
|
3355
3370
|
* fresh-reads cookie reads L1/KV). The value is kept in the request's
|
|
3356
3371
|
* shell-read record, never in the per-request memo the data families read,
|
|
@@ -3368,16 +3383,13 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
3368
3383
|
const read = cfMarkerMemo.readThrough(
|
|
3369
3384
|
this.markerMemoKey(tag),
|
|
3370
3385
|
async (background) => {
|
|
3371
|
-
const outcome = { timedOut: false,
|
|
3386
|
+
const outcome = { timedOut: false, masked: false };
|
|
3372
3387
|
const value = await this.fetchTagMarker(
|
|
3373
3388
|
tag,
|
|
3374
3389
|
background ? undefined : memo,
|
|
3375
3390
|
outcome,
|
|
3376
3391
|
);
|
|
3377
|
-
return {
|
|
3378
|
-
value,
|
|
3379
|
-
memoize: !outcome.timedOut && !outcome.fromRequest,
|
|
3380
|
-
};
|
|
3392
|
+
return { value, memoize: !outcome.timedOut && !outcome.masked };
|
|
3381
3393
|
},
|
|
3382
3394
|
{
|
|
3383
3395
|
freshMs: this.memo.markerFreshMs,
|
|
@@ -3404,31 +3416,16 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
3404
3416
|
private async fetchTagMarker(
|
|
3405
3417
|
tag: string,
|
|
3406
3418
|
memo: Map<string, number | null> | undefined,
|
|
3407
|
-
read?: { timedOut: boolean;
|
|
3419
|
+
read?: { timedOut: boolean; masked?: boolean },
|
|
3420
|
+
/** false: populate no L1 marker (isInvalidatedSincePastMemo). */
|
|
3421
|
+
publish = true,
|
|
3408
3422
|
): Promise<number | null> {
|
|
3409
|
-
// Write the resolved marker into the memo WITHOUT clobbering a value
|
|
3410
|
-
//
|
|
3411
|
-
//
|
|
3412
|
-
//
|
|
3413
|
-
// overwriting it with our (pre-invalidation) read result would break
|
|
3414
|
-
// read-your-own-writes for the rest of the request. If the tag was memoized
|
|
3415
|
-
// mid-read, that value wins and is returned. Without a memo, the read result
|
|
3416
|
-
// stands as-is.
|
|
3417
|
-
//
|
|
3418
|
-
// A mask memoized mid-read stays in this request: maskTagsForRequest
|
|
3419
|
-
// wrote it before the KV put was confirmed, and a failed put must not
|
|
3420
|
-
// reach other requests (#973). So it is not written to L1 here, and
|
|
3421
|
-
// `read.fromRequest` keeps the shell read from storing it in the isolate
|
|
3422
|
-
// marker memo; writeTagInvalidation writes both after the put. A value
|
|
3423
|
-
// another read memoized mid-read came from KV or L1 and is published.
|
|
3424
|
-
let fromRequest = false;
|
|
3423
|
+
// Write the resolved marker into the memo WITHOUT clobbering a value
|
|
3424
|
+
// another read of the tag memoized during our await: that value wins and
|
|
3425
|
+
// is returned, so the request keeps one value per tag. Without a memo,
|
|
3426
|
+
// the read result stands.
|
|
3425
3427
|
const memoize = (value: number | null): number | null => {
|
|
3426
|
-
if (memo && memo.has(tag))
|
|
3427
|
-
const ctx = _getRequestContext();
|
|
3428
|
-
fromRequest = ctx !== undefined && isRequestMasked(ctx, this, tag);
|
|
3429
|
-
if (read) read.fromRequest = fromRequest;
|
|
3430
|
-
return memo.get(tag) ?? null;
|
|
3431
|
-
}
|
|
3428
|
+
if (memo && memo.has(tag)) return memo.get(tag) ?? null;
|
|
3432
3429
|
memo?.set(tag, value);
|
|
3433
3430
|
return value;
|
|
3434
3431
|
};
|
|
@@ -3486,10 +3483,17 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
3486
3483
|
const value = raw != null ? Number(raw) : null;
|
|
3487
3484
|
const resolved = memoize(value);
|
|
3488
3485
|
|
|
3489
|
-
//
|
|
3490
|
-
//
|
|
3491
|
-
//
|
|
3492
|
-
|
|
3486
|
+
// A value read while this request invalidates the tag may predate the
|
|
3487
|
+
// invalidation's KV put: an L1 put of it could land after the put's own
|
|
3488
|
+
// write-through and hide the invalidation from the colo. The request
|
|
3489
|
+
// itself answers from its mask (#973). A shell read also skips the isolate
|
|
3490
|
+
// marker memo through `read.masked`; that memo keeps the higher value, so
|
|
3491
|
+
// the skip is conservative there.
|
|
3492
|
+
const masked = maskedForRequest(_getRequestContext(), this, [tag], 0);
|
|
3493
|
+
if (read) read.masked = masked;
|
|
3494
|
+
|
|
3495
|
+
// Populate L1 for subsequent reads in this colo (non-blocking).
|
|
3496
|
+
if (this.tagCacheTtl > 0 && !masked && publish) {
|
|
3493
3497
|
const put = () => this.putTagMarkerL1(tag, resolved);
|
|
3494
3498
|
if (this.waitUntil) this.waitUntil(put);
|
|
3495
3499
|
else void put();
|
|
@@ -3665,18 +3669,93 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
3665
3669
|
}
|
|
3666
3670
|
|
|
3667
3671
|
/**
|
|
3668
|
-
*
|
|
3669
|
-
*
|
|
3670
|
-
*
|
|
3671
|
-
*
|
|
3672
|
-
*
|
|
3672
|
+
* SegmentCacheStore.isTagsInvalidatedSince: the SAME KV markers used by
|
|
3673
|
+
* runtime envelopes also evict immutable build shells, and gate the writes
|
|
3674
|
+
* of cache executions that started before an invalidation (#977,
|
|
3675
|
+
* tag-invalidation.ts predatesInvalidation). Marker >= since. A marker read
|
|
3676
|
+
* that fails or times out answers false (fail open), or true for the write
|
|
3677
|
+
* gate (`failClosed`). With KV each marker is read past the per-request
|
|
3678
|
+
* memo (isInvalidatedSincePastMemo); KV-less it degrades to the per-request
|
|
3679
|
+
* mask in purge mode and to false otherwise — see
|
|
3673
3680
|
* isShellGenerationInvalidated.
|
|
3674
3681
|
*/
|
|
3675
3682
|
async isTagsInvalidatedSince(
|
|
3676
3683
|
tags: string[],
|
|
3677
3684
|
sinceMs: number,
|
|
3685
|
+
options?: { failClosed?: boolean },
|
|
3686
|
+
): Promise<boolean> {
|
|
3687
|
+
if (!this.kv) return this.isShellGenerationInvalidated(tags, sinceMs);
|
|
3688
|
+
return this.isInvalidatedSincePastMemo(
|
|
3689
|
+
tags,
|
|
3690
|
+
sinceMs,
|
|
3691
|
+
options?.failClosed === true,
|
|
3692
|
+
);
|
|
3693
|
+
}
|
|
3694
|
+
|
|
3695
|
+
/**
|
|
3696
|
+
* isTagsInvalidatedSince with KV, never answered from a marker value this
|
|
3697
|
+
* request read before the asking gate. A write gate asks after an
|
|
3698
|
+
* execution that can run for seconds, and the per-request memo can hold
|
|
3699
|
+
* the marker the request read before it (a stale hit reads its entry's
|
|
3700
|
+
* markers, then its refresh runs): answered from it, another isolate's
|
|
3701
|
+
* invalidation in between let the refresh write.
|
|
3702
|
+
*
|
|
3703
|
+
* - This request's own invalidations answer from its mask, checked again
|
|
3704
|
+
* once the reads are back: a read in flight when the request invalidated
|
|
3705
|
+
* one of the tags resolved to the marker before it.
|
|
3706
|
+
* - Each tag is read through gateMarkerRead (request-tag-mask.ts): gates
|
|
3707
|
+
* asking while a read is in flight share it; a settled read is not
|
|
3708
|
+
* reused.
|
|
3709
|
+
* - The read returns its own value from L1 (tagCacheTtl) or KV, not one an
|
|
3710
|
+
* older read of the tag memoized while it was in flight, and fills the
|
|
3711
|
+
* memo only when it holds nothing, so a build shell's check still serves
|
|
3712
|
+
* the request's later reads. A timed-out read fills nothing.
|
|
3713
|
+
* - It publishes to L1 unless the request masked the tag
|
|
3714
|
+
* (fetchTagMarker), and never without a request context (a detached
|
|
3715
|
+
* waitUntil task), where a mask cannot be told apart.
|
|
3716
|
+
* - A read that fails or times out answers `failClosed`: the write gate
|
|
3717
|
+
* counts it as an invalidation, other callers as none.
|
|
3718
|
+
* @internal
|
|
3719
|
+
*/
|
|
3720
|
+
private async isInvalidatedSincePastMemo(
|
|
3721
|
+
tags: string[],
|
|
3722
|
+
since: number,
|
|
3723
|
+
failClosed: boolean,
|
|
3678
3724
|
): Promise<boolean> {
|
|
3679
|
-
|
|
3725
|
+
if (!Array.isArray(tags) || tags.length === 0 || !since) return false;
|
|
3726
|
+
const ctx = _getRequestContext();
|
|
3727
|
+
if (maskedForRequest(ctx, this, tags, since)) return true;
|
|
3728
|
+
const memo = ctx ? getTagMarkerMemo(ctx, this) : undefined;
|
|
3729
|
+
try {
|
|
3730
|
+
const reads = await Promise.all(
|
|
3731
|
+
tags.map((tag) =>
|
|
3732
|
+
gateMarkerRead(ctx, this, tag, since, async () => {
|
|
3733
|
+
const outcome = { timedOut: false };
|
|
3734
|
+
const marker = await this.fetchTagMarker(
|
|
3735
|
+
tag,
|
|
3736
|
+
undefined,
|
|
3737
|
+
outcome,
|
|
3738
|
+
ctx !== undefined,
|
|
3739
|
+
);
|
|
3740
|
+
if (outcome.timedOut) return { marker, answered: false };
|
|
3741
|
+
if (memo && !memo.has(tag)) memo.set(tag, marker);
|
|
3742
|
+
return { marker, answered: true };
|
|
3743
|
+
}),
|
|
3744
|
+
),
|
|
3745
|
+
);
|
|
3746
|
+
if (maskedForRequest(ctx, this, tags, since)) return true;
|
|
3747
|
+
return reads.some(
|
|
3748
|
+
({ marker, answered }) =>
|
|
3749
|
+
(!answered && failClosed) || (marker != null && marker >= since),
|
|
3750
|
+
);
|
|
3751
|
+
} catch (error) {
|
|
3752
|
+
reportCacheError(
|
|
3753
|
+
error,
|
|
3754
|
+
"cache-read",
|
|
3755
|
+
"[CFCacheStore] tag invalidation check",
|
|
3756
|
+
);
|
|
3757
|
+
return failClosed;
|
|
3758
|
+
}
|
|
3680
3759
|
}
|
|
3681
3760
|
|
|
3682
3761
|
/**
|
|
@@ -3730,14 +3809,11 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
3730
3809
|
|
|
3731
3810
|
/**
|
|
3732
3811
|
* The request-local half of invalidateTags(), run before its first await
|
|
3733
|
-
* (#973):
|
|
3812
|
+
* (#973): the request masks the tags (request-tag-mask.ts), so every later
|
|
3734
3813
|
* read in the request (data families, purge-mode L1 hits, shell reads)
|
|
3735
|
-
* treats
|
|
3736
|
-
*
|
|
3737
|
-
*
|
|
3738
|
-
* pays extra misses, never a stale read, and later requests read the
|
|
3739
|
-
* durable state. Skipped where no read consults the memo (neither KV nor
|
|
3740
|
-
* tagPurge).
|
|
3814
|
+
* treats them as invalidated at `invalidatedAt` without waiting for the KV
|
|
3815
|
+
* put or the purge that revalidateTag() leaves to waitUntil. Skipped where
|
|
3816
|
+
* no read consults the mask (neither KV nor tagPurge).
|
|
3741
3817
|
*/
|
|
3742
3818
|
private maskTagsForRequest(tags: string[], invalidatedAt: number): void {
|
|
3743
3819
|
if (!this.kv && !this.tagPurge) return;
|
|
@@ -3798,8 +3874,8 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
3798
3874
|
);
|
|
3799
3875
|
|
|
3800
3876
|
// Write-through (isolate marker memo + L1) only for tags with a
|
|
3801
|
-
// confirmed durable marker; this request's own
|
|
3802
|
-
//
|
|
3877
|
+
// confirmed durable marker; this request's own mask was set up front
|
|
3878
|
+
// (maskTagsForRequest). The L1 Cache API writes are independent,
|
|
3803
3879
|
// so fan them out in parallel rather than awaiting each.
|
|
3804
3880
|
const l1Writes: Promise<void>[] = [];
|
|
3805
3881
|
for (const tag of tags) {
|
|
@@ -34,7 +34,7 @@ export interface ShellFrameHead {
|
|
|
34
34
|
/** ShellCacheEntry.reactVersion */
|
|
35
35
|
rv: string;
|
|
36
36
|
/** ShellCacheEntry.buildVersion */
|
|
37
|
-
bv
|
|
37
|
+
bv: string;
|
|
38
38
|
/** Capture-generation start time (ms epoch), used by tag marker checks. */
|
|
39
39
|
c: number;
|
|
40
40
|
/** When the entry becomes stale (ms epoch). */
|
|
@@ -70,7 +70,7 @@ function isShellFrameHead(value: unknown): value is ShellFrameHead {
|
|
|
70
70
|
const head = value as Partial<ShellFrameHead>;
|
|
71
71
|
return (
|
|
72
72
|
typeof head.rv === "string" &&
|
|
73
|
-
|
|
73
|
+
typeof head.bv === "string" &&
|
|
74
74
|
isFiniteNumber(head.c) &&
|
|
75
75
|
isFiniteNumber(head.s) &&
|
|
76
76
|
isFiniteNumber(head.e) &&
|
|
@@ -6,8 +6,14 @@
|
|
|
6
6
|
// and in-flight reads, the PPR shell read's own marker record, and the prefix
|
|
7
7
|
// and sentinel of the optional per-colo L1 marker cache. The maps are module
|
|
8
8
|
// singletons keyed by the request-context object, then the store INSTANCE.
|
|
9
|
+
// The request's own invalidations are not here: they are the per-request
|
|
10
|
+
// mask (request-tag-mask.ts), which every marker check consults first.
|
|
9
11
|
|
|
10
12
|
import type { MarkerMemoOutcome } from "../isolate-tag-memo.js";
|
|
13
|
+
import {
|
|
14
|
+
perRequestStoreValue,
|
|
15
|
+
type PerRequestStoreMap,
|
|
16
|
+
} from "../request-tag-mask.js";
|
|
11
17
|
|
|
12
18
|
/**
|
|
13
19
|
* Cache-API path prefix for the optional per-colo L1 cache of tag-invalidation
|
|
@@ -24,32 +30,6 @@ export const TAG_MARKER_CACHE_PREFIX = "__tagmarker__/";
|
|
|
24
30
|
*/
|
|
25
31
|
export const TAG_MARKER_ABSENT = "none";
|
|
26
32
|
|
|
27
|
-
/**
|
|
28
|
-
* Request context -> store instance -> T, both levels weakly held. Also
|
|
29
|
-
* VercelCacheStore's request tag mask (#973).
|
|
30
|
-
*/
|
|
31
|
-
export type PerRequestStoreMap<T> = WeakMap<object, WeakMap<object, T>>;
|
|
32
|
-
|
|
33
|
-
/** The value for (ctx, store), created on first use. */
|
|
34
|
-
export function perRequestStoreValue<T>(
|
|
35
|
-
map: PerRequestStoreMap<T>,
|
|
36
|
-
ctx: object,
|
|
37
|
-
store: object,
|
|
38
|
-
create: () => T,
|
|
39
|
-
): T {
|
|
40
|
-
let byStore = map.get(ctx);
|
|
41
|
-
if (!byStore) {
|
|
42
|
-
byStore = new WeakMap();
|
|
43
|
-
map.set(ctx, byStore);
|
|
44
|
-
}
|
|
45
|
-
let value = byStore.get(store);
|
|
46
|
-
if (value === undefined) {
|
|
47
|
-
value = create();
|
|
48
|
-
byStore.set(store, value);
|
|
49
|
-
}
|
|
50
|
-
return value;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
33
|
/**
|
|
54
34
|
* Per-request memo of tag-invalidation markers (tag -> latest invalidatedAt, or
|
|
55
35
|
* null when no marker exists). Keyed first by the request context object (so it
|
|
@@ -73,92 +53,23 @@ export function perRequestStoreValue<T>(
|
|
|
73
53
|
* Without the memo, isGloballyInvalidated() issues a KV read per tag on every
|
|
74
54
|
* tagged cache read, so a page composed of many segments/items sharing a tag
|
|
75
55
|
* pays that cost N times. The memo collapses it to one KV read per distinct tag
|
|
76
|
-
* per (request, store).
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* invalidation from the memo, without waiting for the KV put that
|
|
80
|
-
* revalidateTag() leaves to waitUntil.
|
|
56
|
+
* per (request, store). It holds only values read from L1 or KV: the request's
|
|
57
|
+
* own invalidations are the mask (request-tag-mask.ts), checked before any
|
|
58
|
+
* marker read, so a value in the memo is always safe to publish.
|
|
81
59
|
*
|
|
82
60
|
* It does NOT span requests, so a hot single-entry route still pays one KV read
|
|
83
61
|
* per request; that read hits Cloudflare KV's own edge read cache for hot keys.
|
|
84
62
|
* A context derived from the request (Object.create(reqCtx): a PPR HIT tail, a
|
|
85
|
-
* shell capture) keeps a memo of its own, so its marker reads stay its own
|
|
86
|
-
* starts from the request's mask (requestMasks) instead of empty, and takes
|
|
87
|
-
* every mask the request sets later (requestMemos).
|
|
63
|
+
* shell capture) keeps a memo of its own, so its marker reads stay its own.
|
|
88
64
|
*/
|
|
89
65
|
const tagMarkerMemo: PerRequestStoreMap<Map<string, number | null>> =
|
|
90
66
|
new WeakMap();
|
|
91
67
|
|
|
92
|
-
/**
|
|
93
|
-
* The tags each request invalidated through each store, with the
|
|
94
|
-
* invalidatedAt its mask carries (#973). Keyed by the request's root context
|
|
95
|
-
* (RequestContext._requestRoot) rather than by the context that called, so a
|
|
96
|
-
* derived context sees the request's mask and a mask it sets reaches the
|
|
97
|
-
* request. Its values are unconfirmed until the KV put lands: a marker read
|
|
98
|
-
* that finds one of them publishes nothing to other requests
|
|
99
|
-
* (CFCacheStore.fetchTagMarker).
|
|
100
|
-
*/
|
|
101
|
-
const requestMasks: PerRequestStoreMap<Map<string, number>> = new WeakMap();
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Every memo of a request, whichever context owns it, per (root, store): a
|
|
105
|
-
* mask set later reaches the memos that already exist (maskRequestTags), not
|
|
106
|
-
* only the caller's. They live as long as the request does.
|
|
107
|
-
*/
|
|
108
|
-
const requestMemos: PerRequestStoreMap<Set<Map<string, number | null>>> =
|
|
109
|
-
new WeakMap();
|
|
110
|
-
|
|
111
|
-
function requestRoot(ctx: object): object {
|
|
112
|
-
return (ctx as { _requestRoot?: object })._requestRoot ?? ctx;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
68
|
export function getTagMarkerMemo(
|
|
116
69
|
ctx: object,
|
|
117
70
|
store: object,
|
|
118
71
|
): Map<string, number | null> {
|
|
119
|
-
return perRequestStoreValue(tagMarkerMemo, ctx, store, () =>
|
|
120
|
-
const root = requestRoot(ctx);
|
|
121
|
-
const memo = new Map<string, number | null>(
|
|
122
|
-
requestMasks.get(root)?.get(store),
|
|
123
|
-
);
|
|
124
|
-
perRequestStoreValue(requestMemos, root, store, () => new Set()).add(memo);
|
|
125
|
-
return memo;
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Mask `tags` at `at` for the rest of the request: in the request's mask and
|
|
131
|
-
* in every memo the request's contexts hold for `store` (a memo created later
|
|
132
|
-
* starts from the mask).
|
|
133
|
-
*/
|
|
134
|
-
export function maskRequestTags(
|
|
135
|
-
ctx: object,
|
|
136
|
-
store: object,
|
|
137
|
-
tags: readonly string[],
|
|
138
|
-
at: number,
|
|
139
|
-
): void {
|
|
140
|
-
const root = requestRoot(ctx);
|
|
141
|
-
const mask = perRequestStoreValue(
|
|
142
|
-
requestMasks,
|
|
143
|
-
root,
|
|
144
|
-
store,
|
|
145
|
-
() => new Map<string, number>(),
|
|
146
|
-
);
|
|
147
|
-
getTagMarkerMemo(ctx, store);
|
|
148
|
-
const memos = requestMemos.get(root)?.get(store) ?? [];
|
|
149
|
-
for (const tag of tags) {
|
|
150
|
-
mask.set(tag, at);
|
|
151
|
-
for (const memo of memos) memo.set(tag, at);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/** Whether this request masked `tag` through `store` (maskRequestTags). */
|
|
156
|
-
export function isRequestMasked(
|
|
157
|
-
ctx: object,
|
|
158
|
-
store: object,
|
|
159
|
-
tag: string,
|
|
160
|
-
): boolean {
|
|
161
|
-
return requestMasks.get(requestRoot(ctx))?.get(store)?.has(tag) ?? false;
|
|
72
|
+
return perRequestStoreValue(tagMarkerMemo, ctx, store, () => new Map());
|
|
162
73
|
}
|
|
163
74
|
|
|
164
75
|
/**
|