@rangojs/router 0.18.0 → 0.19.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/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 +31 -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 +24 -4
- 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 +89 -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 +960 -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 +64 -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 +48 -1
- package/src/cache/cache-key-utils.ts +56 -16
- package/src/cache/cache-policy.ts +9 -4
- package/src/cache/cache-runtime.ts +197 -144
- package/src/cache/cache-scope.ts +304 -112
- package/src/cache/cache-tag.ts +105 -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 +28 -3
- 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 +150 -25
- 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 +189 -124
- package/src/server/cookie-store.ts +153 -127
- package/src/server/handle-store.ts +245 -27
- package/src/server/request-context.ts +123 -84
- 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
package/src/cache/cache-scope.ts
CHANGED
|
@@ -24,6 +24,10 @@ import {
|
|
|
24
24
|
runInSegmentTagScope,
|
|
25
25
|
} from "./cache-tag.js";
|
|
26
26
|
import { reportCacheError } from "./cache-error.js";
|
|
27
|
+
import {
|
|
28
|
+
predatesInvalidation,
|
|
29
|
+
type ExecutionStart,
|
|
30
|
+
} from "./tag-invalidation.js";
|
|
27
31
|
// segment-codec is the only module on cache-scope's import graph that eagerly
|
|
28
32
|
// pulls @vitejs/plugin-rsc (a virtual: module the plain node/vitest runner cannot
|
|
29
33
|
// resolve). It is imported LAZILY at the two call sites below (deserializeSegments
|
|
@@ -35,6 +39,7 @@ import {
|
|
|
35
39
|
captureHandles,
|
|
36
40
|
captureHandleOwners,
|
|
37
41
|
restoreHandles,
|
|
42
|
+
type OwnedPushDelivery,
|
|
38
43
|
encodeHandles,
|
|
39
44
|
decodeHandles,
|
|
40
45
|
} from "./handle-snapshot.js";
|
|
@@ -47,14 +52,7 @@ import {
|
|
|
47
52
|
resolveTagsOption,
|
|
48
53
|
} from "./cache-policy.js";
|
|
49
54
|
import type { RequestContext } from "../server/request-context.js";
|
|
50
|
-
|
|
51
|
-
export function resolveCacheTags(
|
|
52
|
-
config: PartialCacheOptions | false,
|
|
53
|
-
ctx: RequestContext | undefined,
|
|
54
|
-
): string[] | undefined {
|
|
55
|
-
if (config === false) return undefined;
|
|
56
|
-
return resolveTagsOption(config.tags, ctx, "CacheScope");
|
|
57
|
-
}
|
|
55
|
+
import { runIdentityExempt } from "./cache-exec-scope.js";
|
|
58
56
|
|
|
59
57
|
/**
|
|
60
58
|
* Narrow the request's route-record window (RequestContext._routeRecordWindow)
|
|
@@ -147,12 +145,18 @@ function getDefaultRouteCacheKey(
|
|
|
147
145
|
// CacheScope
|
|
148
146
|
// ============================================================================
|
|
149
147
|
|
|
150
|
-
const CACHE_HIT_OBSERVERS = new WeakMap<
|
|
151
|
-
CacheScope,
|
|
152
|
-
() => void | Promise<void>
|
|
153
|
-
>();
|
|
154
|
-
|
|
155
148
|
type CacheKeyFn = NonNullable<PartialCacheOptions["key"]>;
|
|
149
|
+
type CacheConditionFn = NonNullable<PartialCacheOptions["condition"]>;
|
|
150
|
+
type CacheTagsOption = NonNullable<PartialCacheOptions["tags"]>;
|
|
151
|
+
|
|
152
|
+
/** `inherited`, plus `own` when set. */
|
|
153
|
+
function appendOwn<T>(
|
|
154
|
+
inherited: readonly T[] | undefined,
|
|
155
|
+
own: T | undefined,
|
|
156
|
+
): readonly T[] {
|
|
157
|
+
if (own === undefined) return inherited ?? [];
|
|
158
|
+
return inherited ? [...inherited, own] : [own];
|
|
159
|
+
}
|
|
156
160
|
|
|
157
161
|
/**
|
|
158
162
|
* A configured `key()` cannot run without a request context. Falling back to
|
|
@@ -166,6 +170,52 @@ function keyWithoutContextError(): Error {
|
|
|
166
170
|
);
|
|
167
171
|
}
|
|
168
172
|
|
|
173
|
+
/** keyGenerators already warned about returning an empty key. */
|
|
174
|
+
const EMPTY_KEY_WARNED = new WeakSet<object>();
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* A keyGenerator result as a partition part: null when it is the default key.
|
|
178
|
+
* An empty result rejects (the request renders uncached, as for a throwing
|
|
179
|
+
* keyGenerator): positionalParts writes a default result as the empty part,
|
|
180
|
+
* so an empty result would name the partition of a store that returned the
|
|
181
|
+
* default key. Warned once per keyGenerator, naming its store.
|
|
182
|
+
*/
|
|
183
|
+
function generatedPart(
|
|
184
|
+
store: SegmentCacheStore | null,
|
|
185
|
+
generated: Promise<string>,
|
|
186
|
+
defaultKey: string,
|
|
187
|
+
): Promise<string | null> {
|
|
188
|
+
return generated.then((key) => {
|
|
189
|
+
if (key === defaultKey) return null;
|
|
190
|
+
if (key !== "") return key;
|
|
191
|
+
const name = store?.constructor?.name ?? "store";
|
|
192
|
+
const keyGenerator = store?.keyGenerator;
|
|
193
|
+
if (keyGenerator && !EMPTY_KEY_WARNED.has(keyGenerator)) {
|
|
194
|
+
EMPTY_KEY_WARNED.add(keyGenerator);
|
|
195
|
+
console.warn(
|
|
196
|
+
`[CacheScope] ${name}'s keyGenerator returned an empty key; rendering uncached. Return the default key to leave a request unpartitioned.`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
throw new Error(
|
|
200
|
+
`[CacheScope] ${name}'s keyGenerator returned an empty key; rendering uncached`,
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* keyGenerator parts by position: none when every one returned the default
|
|
207
|
+
* key (the key stays as it was), else all of them, an empty part for each
|
|
208
|
+
* default. Dropping only the defaults lost the positions: with a locale
|
|
209
|
+
* store that returns the default key for `en` and a region store that does
|
|
210
|
+
* for `us`, `{ en, de }` and `{ de, us }` both kept one part `…|de` and
|
|
211
|
+
* shared a record and a shell.
|
|
212
|
+
*/
|
|
213
|
+
function positionalParts(parts: readonly (string | null)[]): string[] {
|
|
214
|
+
return parts.every((part) => part === null)
|
|
215
|
+
? []
|
|
216
|
+
: parts.map((part) => part ?? "");
|
|
217
|
+
}
|
|
218
|
+
|
|
169
219
|
/**
|
|
170
220
|
* One key resolution per request, resolver (`by`: a `key()` or a store's
|
|
171
221
|
* keyGenerator) and `slot`, held on the request context (`_resolvedCacheKeys`,
|
|
@@ -230,6 +280,29 @@ export class CacheScope {
|
|
|
230
280
|
readonly keyFns: readonly CacheKeyFn[];
|
|
231
281
|
/** Whether this scope's own config sets `key()` (the last of keyFns). */
|
|
232
282
|
private readonly hasOwnKey: boolean;
|
|
283
|
+
/**
|
|
284
|
+
* The `condition()` predicates gating this scope, outermost first: every
|
|
285
|
+
* cache() on the parent chain that sets one, this scope's last (#974). A
|
|
286
|
+
* read or write needs all of them to allow it (conditionAllows). Before,
|
|
287
|
+
* only the innermost config's predicate ran, so an outer condition()
|
|
288
|
+
* refusing a request still let a nested cache() read and write.
|
|
289
|
+
*/
|
|
290
|
+
private readonly conditions: readonly CacheConditionFn[];
|
|
291
|
+
/**
|
|
292
|
+
* The `tags` options of this scope and every cache() on the parent chain,
|
|
293
|
+
* outermost first (#974): a nested record carries them all (resolveTags),
|
|
294
|
+
* so `updateTag(outerTag)` evicts it and the shells built from it.
|
|
295
|
+
*/
|
|
296
|
+
private readonly tagOptions: readonly CacheTagsOption[];
|
|
297
|
+
/**
|
|
298
|
+
* The enclosing enabled scopes without a `key()` of their own whose
|
|
299
|
+
* `store` option differs from this scope's, outermost first: a store
|
|
300
|
+
* keyGenerator of theirs partitions this scope's records too (#974,
|
|
301
|
+
* resolvePartitionParts). On the same store the keyGenerator already
|
|
302
|
+
* applies through this scope's own default key, or its own `key()`
|
|
303
|
+
* overrides it (#970).
|
|
304
|
+
*/
|
|
305
|
+
private readonly storeScopes: readonly CacheScope[];
|
|
233
306
|
|
|
234
307
|
constructor(
|
|
235
308
|
config: PartialCacheOptions | false,
|
|
@@ -243,20 +316,46 @@ export class CacheScope {
|
|
|
243
316
|
* a ppr route).
|
|
244
317
|
*/
|
|
245
318
|
readonly boundary?: string,
|
|
319
|
+
/**
|
|
320
|
+
* Awaited on every lookup HIT: the implicit doc scope's marker `onHit`
|
|
321
|
+
* (createShellImplicitDocScope), which arms a partial replay's bake-lane
|
|
322
|
+
* loader pins before the route's loaders resolve.
|
|
323
|
+
*/
|
|
324
|
+
private readonly onHit?: () => void | Promise<void>,
|
|
325
|
+
/**
|
|
326
|
+
* The record key of the implicit doc scope of a document HIT tail
|
|
327
|
+
* (createShellImplicitDocScope): the shell entry's own `docKey`, used as
|
|
328
|
+
* is. Not a `key()`, which would be namespaced (#975) and miss the
|
|
329
|
+
* record.
|
|
330
|
+
*/
|
|
331
|
+
private readonly fixedKey?: string,
|
|
246
332
|
) {
|
|
247
333
|
this.config = config;
|
|
248
334
|
this.parent = parent;
|
|
335
|
+
const own = config === false ? undefined : config;
|
|
249
336
|
// Extract and store explicit store reference
|
|
250
|
-
this.explicitStore =
|
|
337
|
+
this.explicitStore = own?.store;
|
|
251
338
|
// Only a function counts: a conditional `key: flag ? fn : null` from
|
|
252
339
|
// untyped code is no key(), as resolveCacheKey always treated it.
|
|
253
|
-
const ownKey =
|
|
254
|
-
config !== false && typeof config.key === "function"
|
|
255
|
-
? config.key
|
|
256
|
-
: undefined;
|
|
257
|
-
const inherited = parent?.keyFns ?? [];
|
|
340
|
+
const ownKey = typeof own?.key === "function" ? own.key : undefined;
|
|
258
341
|
this.hasOwnKey = ownKey !== undefined;
|
|
259
|
-
this.keyFns =
|
|
342
|
+
this.keyFns = appendOwn(parent?.keyFns, ownKey);
|
|
343
|
+
this.conditions = appendOwn(
|
|
344
|
+
parent?.conditions,
|
|
345
|
+
own?.condition || undefined,
|
|
346
|
+
);
|
|
347
|
+
this.tagOptions = appendOwn(parent?.tagOptions, own?.tags || undefined);
|
|
348
|
+
const storeScopes: CacheScope[] = [];
|
|
349
|
+
for (let scope = parent; scope; scope = scope.parent) {
|
|
350
|
+
if (
|
|
351
|
+
scope.enabled &&
|
|
352
|
+
!scope.hasOwnKey &&
|
|
353
|
+
scope.explicitStore !== this.explicitStore
|
|
354
|
+
) {
|
|
355
|
+
storeScopes.unshift(scope);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
this.storeScopes = storeScopes;
|
|
260
359
|
}
|
|
261
360
|
|
|
262
361
|
/**
|
|
@@ -336,6 +435,15 @@ export class CacheScope {
|
|
|
336
435
|
* 2. App-level store from request context
|
|
337
436
|
*/
|
|
338
437
|
getStore(): SegmentCacheStore | null {
|
|
438
|
+
// The implicit doc scope's store is a per-request overlay
|
|
439
|
+
// (createShellImplicitDocScope: SeededShellStore on a HIT tail or a
|
|
440
|
+
// partial replay, SnapshotOnlySegmentStore at capture), never a tag
|
|
441
|
+
// invalidation target. Registered like a cache({ store }), every HIT
|
|
442
|
+
// left one in the handler's explicit-store registry, and each
|
|
443
|
+
// updateTag()/revalidateTag() then warned about it.
|
|
444
|
+
if (this.isShellImplicitDocScope && this.explicitStore) {
|
|
445
|
+
return this.explicitStore;
|
|
446
|
+
}
|
|
339
447
|
return resolveCacheStore(this.explicitStore);
|
|
340
448
|
}
|
|
341
449
|
|
|
@@ -384,17 +492,21 @@ export class CacheScope {
|
|
|
384
492
|
* request: a ppr route's shell partition (resolvePartition) and the record
|
|
385
493
|
* lookup share the `key()` runs, and a rejection is shared too.
|
|
386
494
|
*
|
|
387
|
-
* With
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
* (
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
-
*
|
|
495
|
+
* With partition parts on the chain (resolvePartitionParts: the `key()`
|
|
496
|
+
* results, and the keyGenerator results of enclosing scopes on another
|
|
497
|
+
* store), they are composed by composeCacheKeys, which namespaces every
|
|
498
|
+
* `key()` result (#975), so a nested cache() keys its records within the
|
|
499
|
+
* enclosing partition (#970, #974). A scope whose own config sets no
|
|
500
|
+
* `key()` appends its own default key (resolveDefaultKey). Reusing the
|
|
501
|
+
* outer `key()` result alone would give every route under the inner
|
|
502
|
+
* cache() one record: a `key()` replaces the whole default key, so an
|
|
503
|
+
* outer key that names no route (`tier:gold`) would make `/a` HIT `/b`'s
|
|
504
|
+
* record. A scope with its own `key()` adds no default key: a `key()` is a
|
|
505
|
+
* full override, and its store's keyGenerator does not run. With no
|
|
506
|
+
* partition part it is resolveDefaultKey alone. With a `key()` on the
|
|
507
|
+
* chain but no request context it rejects (keyWithoutContextError).
|
|
508
|
+
* `partitionedPrefix` prefixes a key built from partition parts only (the
|
|
509
|
+
* response family's `response:`).
|
|
398
510
|
*
|
|
399
511
|
* Each `key()` result lives on the request context (`_resolvedCacheKeys`),
|
|
400
512
|
* by function: it runs once per request whatever the default key
|
|
@@ -405,30 +517,37 @@ export class CacheScope {
|
|
|
405
517
|
* itself: a `key()` calling `cookies()` would trip the capture guard there,
|
|
406
518
|
* and a capture must store the partition its request resolved.
|
|
407
519
|
*/
|
|
408
|
-
resolveKeyFrom(defaultKey: string): Promise<string> {
|
|
520
|
+
resolveKeyFrom(defaultKey: string, partitionedPrefix = ""): Promise<string> {
|
|
521
|
+
if (this.fixedKey !== undefined) return Promise.resolve(this.fixedKey);
|
|
409
522
|
const requestCtx = _getRequestContext();
|
|
410
|
-
if (this.
|
|
411
|
-
return this.
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const parts = this.resolveKeyResults(requestCtx, defaultKey);
|
|
415
|
-
if (!this.hasOwnKey) {
|
|
416
|
-
parts.push(this.resolveDefaultKey(requestCtx, defaultKey));
|
|
523
|
+
if (!requestCtx || !this.mayPartition) {
|
|
524
|
+
return this.keyFns.length > 0
|
|
525
|
+
? Promise.reject(keyWithoutContextError())
|
|
526
|
+
: this.resolveDefaultKey(requestCtx, defaultKey);
|
|
417
527
|
}
|
|
418
|
-
return
|
|
419
|
-
|
|
420
|
-
|
|
528
|
+
return Promise.all([
|
|
529
|
+
this.resolvePartitionParts(requestCtx, defaultKey),
|
|
530
|
+
this.hasOwnKey
|
|
531
|
+
? undefined
|
|
532
|
+
: this.resolveDefaultKey(requestCtx, defaultKey),
|
|
533
|
+
]).then(([[keys, storeParts], ownDefault]) => {
|
|
534
|
+
const generated = positionalParts(storeParts);
|
|
535
|
+
if (keys.length === 0 && generated.length === 0) return ownDefault!;
|
|
536
|
+
if (ownDefault !== undefined) generated.push(ownDefault);
|
|
537
|
+
return partitionedPrefix + composeCacheKeys(keys, generated);
|
|
538
|
+
});
|
|
421
539
|
}
|
|
422
540
|
|
|
423
541
|
/**
|
|
424
542
|
* @internal The request partition of a ppr route's shell under this scope
|
|
425
|
-
* (resolveShellPartition):
|
|
426
|
-
* record key but without the default key, which the
|
|
427
|
-
* already carries. A scope without its own `key()`
|
|
428
|
-
* keyGenerator adds that result when it differs from the
|
|
429
|
-
* key, as an unkeyed scope's partition always did: the
|
|
430
|
-
* it, so the shell must be too. Undefined
|
|
431
|
-
* when nothing can partition; null when
|
|
543
|
+
* (resolveShellPartition): its partition parts (resolvePartitionParts),
|
|
544
|
+
* composed like the record key but without the default key, which the
|
|
545
|
+
* URL-based shell key already carries. A scope without its own `key()`
|
|
546
|
+
* whose store has a keyGenerator adds that result when it differs from the
|
|
547
|
+
* `doc` default key, as an unkeyed scope's partition always did: the
|
|
548
|
+
* record is split by it, so the shell must be too. Undefined
|
|
549
|
+
* (synchronously, with no work) when nothing can partition; null when
|
|
550
|
+
* nothing does.
|
|
432
551
|
*/
|
|
433
552
|
resolvePartition(
|
|
434
553
|
pathname: string,
|
|
@@ -437,7 +556,7 @@ export class CacheScope {
|
|
|
437
556
|
const keyGenerator = this.hasOwnKey
|
|
438
557
|
? undefined
|
|
439
558
|
: this.getStore()?.keyGenerator;
|
|
440
|
-
if (this.
|
|
559
|
+
if (!this.mayPartition && !keyGenerator) return undefined;
|
|
441
560
|
const defaultKey = getDefaultRouteCacheKey(pathname, params, false, "doc");
|
|
442
561
|
const requestCtx = _getRequestContext();
|
|
443
562
|
if (!requestCtx) {
|
|
@@ -445,32 +564,71 @@ export class CacheScope {
|
|
|
445
564
|
? Promise.reject(keyWithoutContextError())
|
|
446
565
|
: Promise.resolve(null);
|
|
447
566
|
}
|
|
448
|
-
const generated = keyGenerator
|
|
449
|
-
? this.resolveDefaultKey(requestCtx, defaultKey)
|
|
450
|
-
: undefined;
|
|
451
567
|
return Promise.all([
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
568
|
+
this.resolvePartitionParts(requestCtx, defaultKey),
|
|
569
|
+
keyGenerator
|
|
570
|
+
? generatedPart(
|
|
571
|
+
this.getStore(),
|
|
572
|
+
this.resolveDefaultKey(requestCtx, defaultKey),
|
|
573
|
+
defaultKey,
|
|
574
|
+
)
|
|
575
|
+
: null,
|
|
576
|
+
]).then(([[keys, storeParts], ownGenerated]) => {
|
|
577
|
+
const generated = positionalParts(
|
|
578
|
+
keyGenerator ? [...storeParts, ownGenerated] : storeParts,
|
|
579
|
+
);
|
|
580
|
+
return keys.length === 0 && generated.length === 0
|
|
581
|
+
? null
|
|
582
|
+
: composeCacheKeys(keys, generated);
|
|
461
583
|
});
|
|
462
584
|
}
|
|
463
585
|
|
|
464
|
-
/**
|
|
465
|
-
private
|
|
586
|
+
/** Whether a partition part can apply: a `key()` or a store scope above. */
|
|
587
|
+
private get mayPartition(): boolean {
|
|
588
|
+
return this.keyFns.length > 0 || this.storeScopes.length > 0;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* The parts partitioning this scope's records, outermost first: the
|
|
593
|
+
* chain's `key()` results (each once per request, memoizedKey) and the
|
|
594
|
+
* keyGenerator result of each enclosing scope on another store (once per
|
|
595
|
+
* keyGenerator, #974), null where it is `defaultKey`. The callers keep those
|
|
596
|
+
* positions (positionalParts): a result equal to the default key
|
|
597
|
+
* partitions nothing only when every one does (#970).
|
|
598
|
+
*/
|
|
599
|
+
private resolvePartitionParts(
|
|
466
600
|
requestCtx: RequestContext,
|
|
467
601
|
defaultKey: string,
|
|
468
|
-
): Promise<string
|
|
469
|
-
|
|
602
|
+
): Promise<[string[], (string | null)[]]> {
|
|
603
|
+
const keys = this.keyFns.map((keyFn) =>
|
|
470
604
|
memoizedKey(requestCtx, keyFn, "", () =>
|
|
471
|
-
resolveCacheKey(keyFn, null, defaultKey
|
|
605
|
+
resolveCacheKey(keyFn, null, defaultKey),
|
|
472
606
|
),
|
|
473
607
|
);
|
|
608
|
+
const generated: Promise<string | null>[] = [];
|
|
609
|
+
if (this.storeScopes.length > 0) {
|
|
610
|
+
// By keyGenerator, not by store: a capture wraps the app store
|
|
611
|
+
// (shell-capture.ts RecordingShellStore) but not an explicit one, and
|
|
612
|
+
// the wrapper hands back the inner store's keyGenerator. By store, a
|
|
613
|
+
// chain naming the app store explicitly added its keyGenerator a second
|
|
614
|
+
// time in the capture, and the capture's record key was not the
|
|
615
|
+
// request's.
|
|
616
|
+
const seen = new Set([this.getStore()?.keyGenerator]);
|
|
617
|
+
for (const scope of this.storeScopes) {
|
|
618
|
+
const store = scope.getStore();
|
|
619
|
+
const keyGenerator = store?.keyGenerator;
|
|
620
|
+
if (!keyGenerator || seen.has(keyGenerator)) continue;
|
|
621
|
+
seen.add(keyGenerator);
|
|
622
|
+
generated.push(
|
|
623
|
+
generatedPart(
|
|
624
|
+
store,
|
|
625
|
+
scope.resolveDefaultKey(requestCtx, defaultKey),
|
|
626
|
+
defaultKey,
|
|
627
|
+
),
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return Promise.all([Promise.all(keys), Promise.all(generated)]);
|
|
474
632
|
}
|
|
475
633
|
|
|
476
634
|
/**
|
|
@@ -483,8 +641,7 @@ export class CacheScope {
|
|
|
483
641
|
): Promise<string> {
|
|
484
642
|
const store = this.getStore();
|
|
485
643
|
const keyGenerator = store?.keyGenerator;
|
|
486
|
-
const resolve = () =>
|
|
487
|
-
resolveCacheKey(undefined, store, defaultKey, "CacheScope");
|
|
644
|
+
const resolve = () => resolveCacheKey(undefined, store, defaultKey);
|
|
488
645
|
return requestCtx && keyGenerator
|
|
489
646
|
? memoizedKey(requestCtx, keyGenerator, defaultKey, resolve)
|
|
490
647
|
: resolve();
|
|
@@ -518,11 +675,11 @@ export class CacheScope {
|
|
|
518
675
|
}
|
|
519
676
|
|
|
520
677
|
/**
|
|
521
|
-
* Evaluate the cache `condition`
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
|
|
525
|
-
|
|
678
|
+
* Evaluate the cache `condition` predicates of this scope and every
|
|
679
|
+
* enclosing cache() (`conditions`, #974), outermost first. Returns false
|
|
680
|
+
* (skip the cache operation) when one returns false or throws; returns
|
|
681
|
+
* true when there is none or no request context to evaluate them against.
|
|
682
|
+
*
|
|
526
683
|
* One WRITE decision per (scope, request), memoized on the request context.
|
|
527
684
|
* A capture render has TWO writers consulting the same predicate — the
|
|
528
685
|
* explicit tier's cacheRoute and the snapshot-only doc record gate
|
|
@@ -535,28 +692,37 @@ export class CacheScope {
|
|
|
535
692
|
*/
|
|
536
693
|
private readonly writeConditionMemo = new WeakMap<RequestContext, boolean>();
|
|
537
694
|
|
|
695
|
+
/**
|
|
696
|
+
* Evaluate the cache `condition` predicate. Returns false (skip the cache
|
|
697
|
+
* operation) when the predicate returns false or throws; returns true when
|
|
698
|
+
* there is no condition or no request context to evaluate it against. A
|
|
699
|
+
* write decision is memoized (writeConditionMemo).
|
|
700
|
+
*/
|
|
538
701
|
private conditionAllows(op: "read" | "write"): boolean {
|
|
539
|
-
if (this.
|
|
540
|
-
const requestCtx =
|
|
702
|
+
if (this.conditions.length === 0) return true;
|
|
703
|
+
const requestCtx = _getRequestContext();
|
|
541
704
|
if (!requestCtx) return true;
|
|
542
705
|
if (op === "write") {
|
|
543
706
|
const memoized = this.writeConditionMemo.get(requestCtx);
|
|
544
707
|
if (memoized !== undefined) return memoized;
|
|
545
708
|
}
|
|
546
|
-
let allowed
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
709
|
+
let allowed = true;
|
|
710
|
+
for (const condition of this.conditions) {
|
|
711
|
+
try {
|
|
712
|
+
allowed = !!runIdentityExempt(() => condition(requestCtx));
|
|
713
|
+
if (!allowed) {
|
|
714
|
+
debugCacheLog(
|
|
715
|
+
`[CacheScope] condition returned false, skipping cache ${op}`,
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
} catch (error) {
|
|
719
|
+
console.error(
|
|
720
|
+
`[CacheScope] condition function threw, skipping cache ${op}:`,
|
|
721
|
+
error,
|
|
552
722
|
);
|
|
723
|
+
allowed = false;
|
|
553
724
|
}
|
|
554
|
-
|
|
555
|
-
console.error(
|
|
556
|
-
`[CacheScope] condition function threw, skipping cache ${op}:`,
|
|
557
|
-
error,
|
|
558
|
-
);
|
|
559
|
-
allowed = false;
|
|
725
|
+
if (!allowed) break;
|
|
560
726
|
}
|
|
561
727
|
if (op === "write") this.writeConditionMemo.set(requestCtx, allowed);
|
|
562
728
|
return allowed;
|
|
@@ -569,14 +735,15 @@ export class CacheScope {
|
|
|
569
735
|
* @param pathname - URL pathname for cache key generation
|
|
570
736
|
* @param params - Route params for cache key generation
|
|
571
737
|
* @param isIntercept - Whether this is an intercept navigation (uses different cache key)
|
|
572
|
-
* @param
|
|
573
|
-
*
|
|
738
|
+
* @param ownedPushes - How restoreHandles delivers the record's
|
|
739
|
+
* loader-owned values (withCacheLookup: the handler context's
|
|
740
|
+
* _claimLoaderPushes, and on a document HIT tail the owners it restores)
|
|
574
741
|
*/
|
|
575
742
|
async lookupRoute(
|
|
576
743
|
pathname: string,
|
|
577
744
|
params: Record<string, string>,
|
|
578
745
|
isIntercept?: boolean,
|
|
579
|
-
|
|
746
|
+
ownedPushes?: OwnedPushDelivery,
|
|
580
747
|
): Promise<{
|
|
581
748
|
segments: ResolvedSegment[];
|
|
582
749
|
shouldRevalidate: boolean;
|
|
@@ -585,7 +752,7 @@ export class CacheScope {
|
|
|
585
752
|
pathname,
|
|
586
753
|
params,
|
|
587
754
|
isIntercept,
|
|
588
|
-
|
|
755
|
+
ownedPushes,
|
|
589
756
|
);
|
|
590
757
|
return outcome.status === "hit" ? outcome.result : null;
|
|
591
758
|
}
|
|
@@ -612,7 +779,7 @@ export class CacheScope {
|
|
|
612
779
|
pathname: string,
|
|
613
780
|
params: Record<string, string>,
|
|
614
781
|
isIntercept?: boolean,
|
|
615
|
-
|
|
782
|
+
ownedPushes?: OwnedPushDelivery,
|
|
616
783
|
): Promise<CacheRouteLookupOutcome> {
|
|
617
784
|
if (!this.enabled) return { status: "bypass" };
|
|
618
785
|
if (!this.conditionAllows("read")) return { status: "bypass" };
|
|
@@ -694,7 +861,7 @@ export class CacheScope {
|
|
|
694
861
|
// A hit serves content that was tagged at write time, so the document
|
|
695
862
|
// tag union must include this entry's tags for updateTag()/revalidateTag()
|
|
696
863
|
// to invalidate any full-page entry built on top of it. The write path
|
|
697
|
-
// records via cacheRoute (
|
|
864
|
+
// records via cacheRoute (resolveTags); the hit path records here,
|
|
698
865
|
// onto the replayed segments too, for a record written from this replay
|
|
699
866
|
// (a shell capture's doc record).
|
|
700
867
|
recordRequestTags(cached.tags);
|
|
@@ -721,7 +888,7 @@ export class CacheScope {
|
|
|
721
888
|
handlesRecord,
|
|
722
889
|
handleStore,
|
|
723
890
|
cached.handleOwners,
|
|
724
|
-
|
|
891
|
+
ownedPushes,
|
|
725
892
|
);
|
|
726
893
|
}
|
|
727
894
|
}
|
|
@@ -738,7 +905,7 @@ export class CacheScope {
|
|
|
738
905
|
if (ambientContext)
|
|
739
906
|
this.noteRecordWindow(ambientContext, cached.expiresAt, false);
|
|
740
907
|
|
|
741
|
-
await
|
|
908
|
+
await this.onHit?.();
|
|
742
909
|
return { status: "hit", result: { segments, shouldRevalidate } };
|
|
743
910
|
} catch (error) {
|
|
744
911
|
// Covers a store.get() failure AND a throwing consumer key()/keyGenerator
|
|
@@ -771,7 +938,26 @@ export class CacheScope {
|
|
|
771
938
|
recordTags(requestCtx: RequestContext | undefined): void {
|
|
772
939
|
if (!this.enabled) return;
|
|
773
940
|
if (!this.conditionAllows("write")) return;
|
|
774
|
-
recordRequestTags(
|
|
941
|
+
recordRequestTags(this.resolveTags(requestCtx), requestCtx);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/**
|
|
945
|
+
* @internal The `tags` of this scope and every enclosing cache()
|
|
946
|
+
* (`tagOptions`, #974), static and function forms, for `ctx`: the tags its
|
|
947
|
+
* records carry. Undefined when none resolve (a disabled scope has none).
|
|
948
|
+
*/
|
|
949
|
+
resolveTags(ctx: RequestContext | undefined): string[] | undefined {
|
|
950
|
+
if (!this.enabled || this.tagOptions.length === 0) return undefined;
|
|
951
|
+
if (this.tagOptions.length === 1) {
|
|
952
|
+
return resolveTagsOption(this.tagOptions[0], ctx, "CacheScope");
|
|
953
|
+
}
|
|
954
|
+
const tags = new Set<string>();
|
|
955
|
+
for (const option of this.tagOptions) {
|
|
956
|
+
for (const tag of resolveTagsOption(option, ctx, "CacheScope") ?? []) {
|
|
957
|
+
tags.add(tag);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return tags.size > 0 ? [...tags] : undefined;
|
|
775
961
|
}
|
|
776
962
|
|
|
777
963
|
/**
|
|
@@ -783,12 +969,17 @@ export class CacheScope {
|
|
|
783
969
|
* @param params - Route params for cache key generation
|
|
784
970
|
* @param segments - All resolved segments to cache
|
|
785
971
|
* @param isIntercept - Whether this is an intercept navigation (uses different cache key)
|
|
972
|
+
* @param start - Where the render of `segments` started: the record is not
|
|
973
|
+
* written when one of its tags was invalidated since (#977,
|
|
974
|
+
* predatesInvalidation). A shell capture's doc record passes none: it
|
|
975
|
+
* lives only in the shell entry, which putShell gates by its own start.
|
|
786
976
|
*/
|
|
787
977
|
async cacheRoute(
|
|
788
978
|
pathname: string,
|
|
789
979
|
params: Record<string, string>,
|
|
790
980
|
segments: ResolvedSegment[],
|
|
791
981
|
isIntercept?: boolean,
|
|
982
|
+
start?: ExecutionStart,
|
|
792
983
|
): Promise<void> {
|
|
793
984
|
if (!this.enabled || segments.length === 0) return;
|
|
794
985
|
if (!this.conditionAllows("write")) return;
|
|
@@ -826,7 +1017,7 @@ export class CacheScope {
|
|
|
826
1017
|
}
|
|
827
1018
|
|
|
828
1019
|
// Resolve tags early (while request context is available, before waitUntil)
|
|
829
|
-
const tags =
|
|
1020
|
+
const tags = this.resolveTags(requestCtx);
|
|
830
1021
|
recordRequestTags(tags, requestCtx);
|
|
831
1022
|
|
|
832
1023
|
// Check if this is a partial request (navigation) vs document request
|
|
@@ -926,6 +1117,11 @@ export class CacheScope {
|
|
|
926
1117
|
};
|
|
927
1118
|
if (handleOwners && encodedHandles) data.handleOwners = handleOwners;
|
|
928
1119
|
|
|
1120
|
+
if (start && (await predatesInvalidation(store, data.tags, start))) {
|
|
1121
|
+
debugCacheLog(`[CacheScope] ${key}: invalidated since render`);
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
929
1125
|
if (INTERNAL_RANGO_DEBUG) {
|
|
930
1126
|
debugCacheLog(`[CacheScope] waitUntil: calling store.set for ${key}`);
|
|
931
1127
|
}
|
|
@@ -975,8 +1171,9 @@ function collectRecordTags(
|
|
|
975
1171
|
/**
|
|
976
1172
|
* @internal The request partition of a ppr route's shell: with a route
|
|
977
1173
|
* cache() scope, its CacheScope.resolvePartition (the chain's `key()`
|
|
978
|
-
* results, composed, plus
|
|
979
|
-
*
|
|
1174
|
+
* results, namespaced and composed (#975), plus the keyGenerator results of
|
|
1175
|
+
* enclosing scopes on another store (#974) and of a scope without its own
|
|
1176
|
+
* `key()` (#970)); with none, the app store's keyGenerator result
|
|
980
1177
|
* given the document default key. Undefined (synchronously, with no work)
|
|
981
1178
|
* when neither applies: the shell key stays host + path + filtered search.
|
|
982
1179
|
* Resolves to null when the partition is the default key itself (a
|
|
@@ -995,8 +1192,8 @@ export function resolveShellPartition(
|
|
|
995
1192
|
if (routeScope?.enabled) return routeScope.resolvePartition(pathname, params);
|
|
996
1193
|
if (!appStore?.keyGenerator) return undefined;
|
|
997
1194
|
const defaultKey = getDefaultRouteCacheKey(pathname, params, false, "doc");
|
|
998
|
-
return resolveCacheKey(undefined, appStore, defaultKey
|
|
999
|
-
|
|
1195
|
+
return resolveCacheKey(undefined, appStore, defaultKey).then((resolved) =>
|
|
1196
|
+
resolved === defaultKey ? null : resolved,
|
|
1000
1197
|
);
|
|
1001
1198
|
}
|
|
1002
1199
|
|
|
@@ -1033,19 +1230,14 @@ type ShellImplicitCacheMarker = NonNullable<
|
|
|
1033
1230
|
export function createShellImplicitDocScope(
|
|
1034
1231
|
marker: ShellImplicitCacheMarker,
|
|
1035
1232
|
): CacheScope {
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
{
|
|
1039
|
-
ttl: marker.ttl,
|
|
1040
|
-
swr: marker.swr,
|
|
1041
|
-
store: marker.store,
|
|
1042
|
-
...(fixedDocKey !== undefined && { key: () => fixedDocKey }),
|
|
1043
|
-
},
|
|
1233
|
+
return new CacheScope(
|
|
1234
|
+
{ ttl: marker.ttl, swr: marker.swr, store: marker.store },
|
|
1044
1235
|
null,
|
|
1045
1236
|
marker.keyPrefix,
|
|
1237
|
+
undefined,
|
|
1238
|
+
marker.onHit,
|
|
1239
|
+
marker.fixedDocKey,
|
|
1046
1240
|
);
|
|
1047
|
-
if (marker.onHit) CACHE_HIT_OBSERVERS.set(implicitScope, marker.onHit);
|
|
1048
|
-
return implicitScope;
|
|
1049
1241
|
}
|
|
1050
1242
|
|
|
1051
1243
|
/**
|