@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
|
@@ -47,7 +47,9 @@ import { encodeKV } from "../encode-kv.js";
|
|
|
47
47
|
import { runBackground } from "./background-task.js";
|
|
48
48
|
import { observePhase, PHASES } from "../router/instrument.js";
|
|
49
49
|
import {
|
|
50
|
+
assertLoaderReadsClean,
|
|
50
51
|
normalizeTags,
|
|
52
|
+
outsideCacheTagScope,
|
|
51
53
|
recordRequestTags,
|
|
52
54
|
runWithCacheTagScope,
|
|
53
55
|
} from "./cache-tag.js";
|
|
@@ -58,7 +60,12 @@ import {
|
|
|
58
60
|
type CacheExecScope,
|
|
59
61
|
} from "./cache-exec-scope.js";
|
|
60
62
|
import { reportCacheError } from "./cache-error.js";
|
|
61
|
-
import {
|
|
63
|
+
import {
|
|
64
|
+
executionStart,
|
|
65
|
+
invalidatedSince,
|
|
66
|
+
predatesInvalidation,
|
|
67
|
+
type ExecutionStart,
|
|
68
|
+
} from "./tag-invalidation.js";
|
|
62
69
|
import type { CacheItemResult } from "./types.js";
|
|
63
70
|
import type { InternalHandlerContext } from "../types.js";
|
|
64
71
|
|
|
@@ -267,6 +274,12 @@ interface CacheEnvelope {
|
|
|
267
274
|
tags: string[];
|
|
268
275
|
/** RSC-encoded handle blob captured during execution, if any. */
|
|
269
276
|
handles?: string;
|
|
277
|
+
/**
|
|
278
|
+
* One of `tags` was invalidated after the leader started, as the store
|
|
279
|
+
* tells it (another isolate, #977): the leader does not write it, and a
|
|
280
|
+
* follower does not serve it.
|
|
281
|
+
*/
|
|
282
|
+
predates?: boolean;
|
|
270
283
|
}
|
|
271
284
|
|
|
272
285
|
/**
|
|
@@ -295,8 +308,8 @@ interface InFlightExecution {
|
|
|
295
308
|
promise: Promise<CacheEnvelope>;
|
|
296
309
|
/** Date.now() at registration, for IN_FLIGHT_LEADER_MAX_WAIT_MS. */
|
|
297
310
|
registeredAt: number;
|
|
298
|
-
/**
|
|
299
|
-
|
|
311
|
+
/** Where the leader started, for invalidatedSince (#973, #977). */
|
|
312
|
+
start: ExecutionStart;
|
|
300
313
|
}
|
|
301
314
|
|
|
302
315
|
const inFlightExecutions = new Map<string, InFlightExecution>();
|
|
@@ -313,6 +326,18 @@ const IN_FLIGHT_LEADER_MAX_WAIT_MS = 15_000;
|
|
|
313
326
|
/** Distinguishes a leader timeout from a leader rejection in raceLeader. */
|
|
314
327
|
const LEADER_TIMED_OUT = Symbol("leader-timed-out");
|
|
315
328
|
|
|
329
|
+
/**
|
|
330
|
+
* @internal Reset this module's per-isolate state: the in-flight leaders a
|
|
331
|
+
* follower would join for up to IN_FLIGHT_LEADER_MAX_WAIT_MS, and the
|
|
332
|
+
* once-per-function warnings. Tests only (testing/serve-shell-request.ts
|
|
333
|
+
* resetShellTestState), never while a request is in flight.
|
|
334
|
+
*/
|
|
335
|
+
export function resetCacheRuntimeForTests(): void {
|
|
336
|
+
inFlightExecutions.clear();
|
|
337
|
+
warnedUncachedUnderTest.clear();
|
|
338
|
+
warnedUnkeyableArgs.clear();
|
|
339
|
+
}
|
|
340
|
+
|
|
316
341
|
/**
|
|
317
342
|
* Await a leader's envelope for at most `remainingMs`. Resolves with the
|
|
318
343
|
* envelope, `undefined` on leader rejection (the leader already cleared its
|
|
@@ -431,6 +456,20 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
431
456
|
);
|
|
432
457
|
}
|
|
433
458
|
|
|
459
|
+
// An entry's tags: the profile/DSL tags plus what its execution
|
|
460
|
+
// recorded, normalized (drops empty profile tags, matching the
|
|
461
|
+
// invalidate path) and deduped.
|
|
462
|
+
const entryTags = (recorded: Set<string>): string[] => [
|
|
463
|
+
...new Set(normalizeTags([...(profile.tags ?? []), ...recorded])),
|
|
464
|
+
];
|
|
465
|
+
// The value's Flight encode runs back inside its execution's tag scope:
|
|
466
|
+
// it renders the server components in the value, and a "use cache" call
|
|
467
|
+
// or cacheTag() among them belongs to this entry (#980).
|
|
468
|
+
const inTagScope = <T>(
|
|
469
|
+
recorded: Set<string>,
|
|
470
|
+
encode: () => Promise<T>,
|
|
471
|
+
): Promise<T> => runWithCacheTagScope(encode, recorded).result;
|
|
472
|
+
|
|
434
473
|
// Separate request-scoped args (ctx, a Request, the request's env) from
|
|
435
474
|
// key-generating args. For those that carry route context (ctx params,
|
|
436
475
|
// pathname, searchParams; a Request's URL), extract serializable values
|
|
@@ -631,124 +670,122 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
631
670
|
const liveStore = hasTaintedArgs ? requestCtx?._handleStore : undefined;
|
|
632
671
|
const recordKey = ownerKeyed ? useCacheRecordKey() : undefined;
|
|
633
672
|
// Background revalidation — must capture handles if tainted args present.
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
//
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
673
|
+
// Scheduled outside the caller's tag scope: an enclosing "use cache"
|
|
674
|
+
// entry bakes the stale value, not the refreshed one (#980).
|
|
675
|
+
outsideCacheTagScope(() =>
|
|
676
|
+
runBackground(requestCtx, async () => {
|
|
677
|
+
// The background body's handle pushes belong to the refreshed entry
|
|
678
|
+
// only: the foreground already replayed the stale entry's. They
|
|
679
|
+
// reach the REQUEST's store (a handler ctx.use(Handle) binds it at
|
|
680
|
+
// setupLoaderAccess, so a derived context with its own store never
|
|
681
|
+
// sees them), so the capture accepts only this body's chain and
|
|
682
|
+
// diverts what it records away from the live store. The foreground
|
|
683
|
+
// is still rendering here (runBackground starts on the next
|
|
684
|
+
// microtask), and its concurrent pushes are neither recorded nor
|
|
685
|
+
// diverted (issue #684, plan 010). A root scope: the task is
|
|
686
|
+
// detached, so an outer "use cache" body that scheduled it must not
|
|
687
|
+
// record the refresh's pushes.
|
|
688
|
+
const bgScope: CacheExecScope = {};
|
|
689
|
+
const bgCapture = liveStore
|
|
690
|
+
? startHandleCapture(liveStore, {
|
|
691
|
+
accept: () => isInCacheExecChain(bgScope),
|
|
692
|
+
divert: true,
|
|
693
|
+
key: recordKey,
|
|
694
|
+
})
|
|
695
|
+
: undefined;
|
|
696
|
+
|
|
697
|
+
// Tainted args are NOT stamped here, in contrast to the foreground
|
|
698
|
+
// miss path below. The args include the live HandlerContext the
|
|
699
|
+
// still-rendering foreground holds, and INSIDE_CACHE_EXEC is a
|
|
700
|
+
// property stamped onto that SHARED object — so for the whole
|
|
701
|
+
// revalidation window a concurrent foreground ctx.set() /
|
|
702
|
+
// ctx.headers.*() would throw (issue #684, plan 010). requestCtx is
|
|
703
|
+
// never stamped anywhere for the same reason. In-body misuse IS
|
|
704
|
+
// still guarded here: runWithCacheExecScope below follows only this
|
|
705
|
+
// body's async chain, so a conditional cookies()/headers() read
|
|
706
|
+
// that fires only during revalidation throws instead of baking one
|
|
707
|
+
// user's value into the shared entry — with zero visibility to the
|
|
708
|
+
// concurrent foreground.
|
|
709
|
+
|
|
710
|
+
try {
|
|
711
|
+
// Re-establish the request-context ALS so a "use cache" body that
|
|
712
|
+
// reads the ambient getRequestContext() (e.g.
|
|
713
|
+
// getRequestContext().env.ApiKey) resolves during the background
|
|
714
|
+
// revalidation instead of throwing "called outside of a request
|
|
715
|
+
// context". runWithRequestContext sets the store for fn's
|
|
716
|
+
// synchronous kickoff; its async continuations inherit it.
|
|
717
|
+
//
|
|
718
|
+
// The span opens inside the request ALS and wraps the WHOLE task —
|
|
719
|
+
// fn kickoff through the store write — so its platform spans nest
|
|
720
|
+
// under rango.background and a slow or failing setItem is part of
|
|
721
|
+
// the traced revalidation, not an untraced tail.
|
|
722
|
+
await runWithRequestContext(requestCtx, () =>
|
|
723
|
+
observePhase(
|
|
724
|
+
PHASES.background("use-cache-revalidation"),
|
|
725
|
+
async () => {
|
|
726
|
+
const refreshArgs = args.map(refreshView);
|
|
727
|
+
// The refresh reads its data from here on (#973, #977).
|
|
728
|
+
const start = executionStart();
|
|
729
|
+
const scoped = runWithCacheTagScope(() =>
|
|
730
|
+
runWithCacheExecScope(
|
|
731
|
+
() => fn.apply(this, refreshArgs),
|
|
732
|
+
bgScope,
|
|
733
|
+
),
|
|
734
|
+
);
|
|
735
|
+
const freshResult = await scoped.result;
|
|
736
|
+
bgCapture?.stop();
|
|
737
|
+
recordRequestTags(entryTags(scoped.tags), requestCtx);
|
|
738
|
+
const flightErrors: unknown[] = [];
|
|
739
|
+
const onFlightError = (error: unknown): void => {
|
|
740
|
+
flightErrors.push(error);
|
|
741
|
+
};
|
|
742
|
+
const serialized = await inTagScope(scoped.tags, () =>
|
|
743
|
+
serializeResult(freshResult, onFlightError),
|
|
744
|
+
);
|
|
745
|
+
if (serialized !== null) {
|
|
746
|
+
const encodedHandles = bgCapture
|
|
747
|
+
? await inTagScope(scoped.tags, () =>
|
|
748
|
+
encodeHandles(
|
|
749
|
+
bgCapture.capture.data,
|
|
750
|
+
onFlightError,
|
|
751
|
+
),
|
|
752
|
+
)
|
|
753
|
+
: undefined;
|
|
754
|
+
// An error row would replace the stale entry; the catch
|
|
755
|
+
// below reports it and the stale entry keeps serving.
|
|
756
|
+
if (flightErrors.length > 0) throw flightErrors[0];
|
|
757
|
+
assertLoaderReadsClean(bgScope);
|
|
758
|
+
const freshTags = entryTags(scoped.tags);
|
|
759
|
+
// One of the refreshed value's tags was invalidated after
|
|
760
|
+
// the refresh started: written now, a value that may
|
|
761
|
+
// predate it would outlive it.
|
|
762
|
+
if (await predatesInvalidation(store, freshTags, start)) {
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
await store.setItem!(cacheKey, serialized, {
|
|
766
|
+
handles: encodedHandles,
|
|
767
|
+
ttl: profile.ttl,
|
|
768
|
+
swr: profile.swr,
|
|
769
|
+
tags: freshTags.length > 0 ? freshTags : undefined,
|
|
770
|
+
});
|
|
728
771
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
"stale-revalidation",
|
|
747
|
-
"[use cache] background revalidation failed",
|
|
748
|
-
requestCtx,
|
|
749
|
-
);
|
|
750
|
-
}
|
|
751
|
-
});
|
|
772
|
+
},
|
|
773
|
+
),
|
|
774
|
+
);
|
|
775
|
+
} catch (bgError) {
|
|
776
|
+
bgCapture?.stop();
|
|
777
|
+
// Pass requestCtx explicitly: this runs in a detached background
|
|
778
|
+
// task where the ALS context is gone, so onError can only fire if
|
|
779
|
+
// we hand it the context captured up front.
|
|
780
|
+
reportCacheError(
|
|
781
|
+
bgError,
|
|
782
|
+
"stale-revalidation",
|
|
783
|
+
"[use cache] background revalidation failed",
|
|
784
|
+
requestCtx,
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
}),
|
|
788
|
+
);
|
|
752
789
|
return result;
|
|
753
790
|
} catch (error) {
|
|
754
791
|
// Stale value is corrupt/partial; report and fall through to a fresh
|
|
@@ -797,7 +834,10 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
797
834
|
// Leader rejected: its map entry is already cleared; run fresh.
|
|
798
835
|
if (!raced) break;
|
|
799
836
|
// Checked after the wait, so an invalidation made meanwhile counts too.
|
|
800
|
-
if (
|
|
837
|
+
if (
|
|
838
|
+
!raced.predates &&
|
|
839
|
+
!invalidatedSince(raced.tags, existing.start.seq)
|
|
840
|
+
) {
|
|
801
841
|
try {
|
|
802
842
|
return await serveCached({
|
|
803
843
|
value: raced.serialized,
|
|
@@ -814,11 +854,12 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
814
854
|
break; // Run fresh below.
|
|
815
855
|
}
|
|
816
856
|
}
|
|
817
|
-
//
|
|
818
|
-
//
|
|
819
|
-
//
|
|
820
|
-
//
|
|
821
|
-
// start one
|
|
857
|
+
// One of the leader's tags was invalidated after it started, in any
|
|
858
|
+
// request or, as the store tells it, another isolate (#973, #977): its
|
|
859
|
+
// value may predate the invalidation. The leader cleared its entry
|
|
860
|
+
// before resolving, so an entry now is a newer execution (another
|
|
861
|
+
// caller that made this same decision): join it rather than start one
|
|
862
|
+
// more.
|
|
822
863
|
const next = inFlightExecutions.get(cacheKey);
|
|
823
864
|
existing = next !== existing ? next : undefined;
|
|
824
865
|
}
|
|
@@ -837,11 +878,11 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
837
878
|
// Followers attach their own catch; guard the map's own reference so a
|
|
838
879
|
// rejected envelope with no waiter is not an unhandled rejection.
|
|
839
880
|
envelopePromise.catch(() => {});
|
|
840
|
-
const
|
|
881
|
+
const start = executionStart();
|
|
841
882
|
inFlightExecutions.set(cacheKey, {
|
|
842
883
|
promise: envelopePromise,
|
|
843
884
|
registeredAt: Date.now(),
|
|
844
|
-
|
|
885
|
+
start,
|
|
845
886
|
});
|
|
846
887
|
const clearSelf = (): void => {
|
|
847
888
|
if (inFlightExecutions.get(cacheKey)?.promise === envelopePromise) {
|
|
@@ -904,13 +945,10 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
904
945
|
capture?.stop();
|
|
905
946
|
}
|
|
906
947
|
|
|
907
|
-
//
|
|
908
|
-
//
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
...new Set(normalizeTags([...(profile.tags ?? []), ...scoped!.tags])),
|
|
912
|
-
];
|
|
913
|
-
recordRequestTags(allTags, requestCtx);
|
|
948
|
+
// Read scoped.tags after awaiting result so post-await cacheTag() calls
|
|
949
|
+
// are included.
|
|
950
|
+
const recorded = scoped!.tags;
|
|
951
|
+
recordRequestTags(entryTags(recorded), requestCtx);
|
|
914
952
|
|
|
915
953
|
// Serialize + encode handles ONCE, resolve the in-flight envelope so any
|
|
916
954
|
// concurrent followers can serve a synthetic hit, then persist to the store.
|
|
@@ -924,14 +962,21 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
924
962
|
flightErrors.push(error);
|
|
925
963
|
};
|
|
926
964
|
try {
|
|
927
|
-
serialized = await
|
|
965
|
+
serialized = await inTagScope(recorded, () =>
|
|
966
|
+
serializeResult(result, onFlightError),
|
|
967
|
+
);
|
|
928
968
|
encodedHandles = capture
|
|
929
|
-
? await
|
|
969
|
+
? await inTagScope(recorded, () =>
|
|
970
|
+
encodeHandles(capture.capture.data, onFlightError),
|
|
971
|
+
)
|
|
930
972
|
: undefined;
|
|
931
973
|
// Flight encodes an async component that throws, or a rejected
|
|
932
974
|
// promise in the result or a handle value, as an error row and
|
|
933
975
|
// completes normally; stored, every hit would serve that error.
|
|
934
976
|
if (flightErrors.length > 0) throw flightErrors[0];
|
|
977
|
+
// The encode awaited the result's nested promises: an identity read a
|
|
978
|
+
// loader value made after it settled is recorded now (#1011).
|
|
979
|
+
assertLoaderReadsClean(execScope);
|
|
935
980
|
} catch (buildError) {
|
|
936
981
|
// Serialize/handle-encode failed: no envelope for followers (they run
|
|
937
982
|
// fresh) and nothing to write.
|
|
@@ -940,8 +985,8 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
940
985
|
requestCtx?._reportBackgroundError?.(buildError, "cache-write");
|
|
941
986
|
return;
|
|
942
987
|
}
|
|
943
|
-
clearSelf();
|
|
944
988
|
if (serialized === null) {
|
|
989
|
+
clearSelf();
|
|
945
990
|
// Non-serializable result: no store write (matches the prior silent
|
|
946
991
|
// skip); reject so any waiter falls through to a fresh execution.
|
|
947
992
|
rejectEnvelope(
|
|
@@ -951,19 +996,32 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
951
996
|
);
|
|
952
997
|
return;
|
|
953
998
|
}
|
|
999
|
+
const tags = entryTags(recorded);
|
|
1000
|
+
// One of the value's tags was invalidated after the execution started
|
|
1001
|
+
// (#973, #977): written now, a value that may predate it would outlive
|
|
1002
|
+
// it. Asked before the followers get the value: another isolate's
|
|
1003
|
+
// invalidation is only in the store's answer, and a follower serving
|
|
1004
|
+
// the value would bake it into its own entries. Yes also when the store
|
|
1005
|
+
// cannot read a marker (predatesInvalidation asks it fail-closed) or
|
|
1006
|
+
// throws: the cost is a later miss.
|
|
1007
|
+
let predates = true;
|
|
1008
|
+
try {
|
|
1009
|
+
predates = await predatesInvalidation(store, tags, start);
|
|
1010
|
+
} catch (gateError) {
|
|
1011
|
+
requestCtx?._reportBackgroundError?.(gateError, "cache-write");
|
|
1012
|
+
}
|
|
1013
|
+
clearSelf();
|
|
954
1014
|
// Hand followers the envelope before the store write so a slow/failed
|
|
955
1015
|
// write never stalls them.
|
|
956
|
-
resolveEnvelope({ serialized, tags
|
|
957
|
-
//
|
|
958
|
-
|
|
959
|
-
// now it would outlive it. Skip the write; the next read runs fresh.
|
|
960
|
-
if (invalidatedSince(requestCtx, allTags, startSeq)) return;
|
|
1016
|
+
resolveEnvelope({ serialized, tags, handles: encodedHandles, predates });
|
|
1017
|
+
// Skipped, the next read runs fresh.
|
|
1018
|
+
if (predates) return;
|
|
961
1019
|
try {
|
|
962
1020
|
await store.setItem!(cacheKey, serialized, {
|
|
963
1021
|
handles: encodedHandles,
|
|
964
1022
|
ttl: profile.ttl,
|
|
965
1023
|
swr: profile.swr,
|
|
966
|
-
tags:
|
|
1024
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
967
1025
|
});
|
|
968
1026
|
} catch (writeError) {
|
|
969
1027
|
requestCtx?._reportBackgroundError?.(writeError, "cache-write");
|