@rangojs/router 0.0.0-experimental.e9c0b2f2 → 0.0.0-experimental.ea9f40f2
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/AGENTS.md +6 -10
- package/README.md +289 -938
- package/dist/bin/rango.js +271 -46
- package/dist/vite/index.js +673 -193
- package/package.json +10 -8
- package/skills/api-client/SKILL.md +1 -1
- package/skills/breadcrumbs/SKILL.md +31 -14
- package/skills/cache-guide/SKILL.md +5 -2
- package/skills/caching/SKILL.md +59 -4
- package/skills/catalog.json +271 -0
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +83 -2
- package/skills/debug-manifest/SKILL.md +1 -1
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +9 -1
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +8 -8
- package/skills/hooks/SKILL.md +54 -892
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +4 -4
- package/skills/i18n/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +46 -14
- package/skills/layout/SKILL.md +27 -10
- package/skills/links/SKILL.md +1 -1
- package/skills/loader/SKILL.md +23 -1
- package/skills/middleware/SKILL.md +7 -3
- package/skills/migrate-nextjs/SKILL.md +167 -6
- package/skills/migrate-react-router/SKILL.md +59 -677
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +1 -1
- package/skills/observability/SKILL.md +9 -1
- package/skills/parallel/SKILL.md +23 -4
- package/skills/ppr/SKILL.md +622 -0
- package/skills/prerender/SKILL.md +28 -18
- package/skills/rango/SKILL.md +84 -25
- package/skills/response-routes/SKILL.md +15 -1
- package/skills/route/SKILL.md +71 -4
- package/skills/router-setup/SKILL.md +14 -3
- package/skills/scripts/SKILL.md +1 -1
- package/skills/server-actions/SKILL.md +3 -2
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +1 -1
- package/skills/tailwind/SKILL.md +1 -1
- package/skills/testing/SKILL.md +2 -1
- package/skills/testing/handles.md +4 -2
- package/skills/testing/render-handler.md +15 -14
- package/skills/testing/reverse-and-types.md +8 -7
- package/skills/theme/SKILL.md +1 -1
- package/skills/typesafety/SKILL.md +45 -919
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +30 -3
- package/skills/vercel/SKILL.md +1 -1
- package/skills/view-transitions/SKILL.md +44 -1
- package/src/browser/event-controller.ts +62 -10
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +65 -16
- package/src/browser/navigation-client.ts +32 -2
- package/src/browser/navigation-store.ts +128 -14
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +76 -17
- package/src/browser/prefetch/cache.ts +51 -11
- package/src/browser/prefetch/fetch.ts +59 -21
- package/src/browser/prefetch/queue.ts +19 -4
- package/src/browser/react/Link.tsx +13 -3
- package/src/browser/react/NavigationProvider.tsx +108 -4
- package/src/browser/response-adapter.ts +38 -9
- package/src/browser/rsc-router.tsx +54 -4
- package/src/browser/scroll-restoration.ts +7 -5
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/server-action-bridge.ts +22 -10
- package/src/browser/types.ts +54 -1
- package/src/build/generate-manifest.ts +155 -131
- package/src/build/index.ts +3 -1
- package/src/build/route-trie.ts +35 -7
- package/src/build/route-types/include-resolution.ts +347 -47
- package/src/build/runtime-discovery.ts +4 -1
- package/src/cache/cache-key-utils.ts +29 -0
- package/src/cache/cache-runtime.ts +262 -71
- package/src/cache/cache-scope.ts +2 -17
- package/src/cache/cache-tag.ts +60 -14
- package/src/cache/cf/cf-cache-store.ts +243 -20
- package/src/cache/document-cache.ts +54 -21
- package/src/cache/index.ts +1 -0
- package/src/cache/memory-segment-store.ts +110 -3
- package/src/cache/profile-registry.ts +15 -0
- package/src/cache/read-through-swr.ts +15 -1
- package/src/cache/segment-codec.ts +4 -4
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/types.ts +158 -0
- package/src/cache/vercel/vercel-cache-store.ts +401 -124
- package/src/client.rsc.tsx +0 -3
- package/src/client.tsx +0 -3
- package/src/cloudflare/tracing.ts +7 -8
- package/src/defer.ts +11 -22
- package/src/handle.ts +37 -15
- package/src/handles/MetaTags.tsx +16 -82
- package/src/handles/breadcrumbs.ts +12 -14
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +7 -8
- package/src/handles/meta.ts +7 -44
- package/src/host/errors.ts +15 -0
- package/src/host/index.ts +1 -0
- package/src/index.rsc.ts +8 -2
- package/src/index.ts +19 -13
- package/src/internal-debug.ts +11 -8
- package/src/prerender.ts +17 -4
- package/src/redirect-origin.ts +14 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/route-content-wrapper.tsx +12 -5
- package/src/route-definition/dsl-helpers.ts +21 -32
- package/src/route-definition/helper-factories.ts +0 -2
- package/src/route-definition/helpers-types.ts +43 -43
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/resolve-handler-use.ts +0 -1
- package/src/route-definition/use-item-types.ts +3 -6
- package/src/route-map-builder.ts +41 -4
- package/src/route-types.ts +0 -5
- package/src/router/find-match.ts +86 -8
- package/src/router/instrument.ts +9 -4
- package/src/router/lazy-includes.ts +72 -12
- package/src/router/loader-resolution.ts +14 -2
- package/src/router/manifest.ts +56 -11
- package/src/router/match-api.ts +76 -32
- package/src/router/match-handlers.ts +181 -135
- package/src/router/match-middleware/background-revalidation.ts +40 -23
- package/src/router/match-middleware/cache-store.ts +39 -24
- package/src/router/match-result.ts +35 -15
- package/src/router/middleware.ts +64 -38
- package/src/router/navigation-snapshot.ts +7 -5
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +53 -64
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +11 -5
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +23 -8
- package/src/router/route-snapshot.ts +14 -2
- package/src/router/router-context.ts +3 -1
- package/src/router/router-interfaces.ts +32 -1
- package/src/router/router-options.ts +30 -0
- package/src/router/segment-resolution/fresh.ts +39 -3
- package/src/router/segment-resolution/loader-cache.ts +93 -2
- package/src/router/segment-resolution/loader-mask.ts +60 -0
- package/src/router/segment-resolution/loader-snapshot.ts +259 -0
- package/src/router/segment-resolution/mask-nested.ts +83 -0
- package/src/router/segment-resolution/revalidation.ts +3 -0
- package/src/router/segment-resolution/view-transition-default.ts +35 -15
- package/src/router/substitute-pattern-params.ts +54 -35
- package/src/router/telemetry-otel.ts +6 -8
- package/src/router/telemetry.ts +9 -1
- package/src/router/tracing.ts +14 -5
- package/src/router/trie-matching.ts +19 -11
- package/src/router/url-params.ts +13 -0
- package/src/router.ts +47 -16
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler.ts +60 -33
- package/src/rsc/manifest-init.ts +1 -1
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/progressive-enhancement.ts +61 -4
- package/src/rsc/redirect-guard.ts +2 -1
- package/src/rsc/rsc-rendering.ts +429 -37
- package/src/rsc/server-action.ts +25 -2
- package/src/rsc/shell-capture.ts +1190 -0
- package/src/rsc/shell-serve.ts +181 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +30 -0
- package/src/segment-loader-promise.ts +18 -0
- package/src/segment-system.tsx +149 -14
- package/src/server/context.ts +67 -9
- package/src/server/cookie-store.ts +73 -1
- package/src/server/loader-registry.ts +13 -1
- package/src/server/request-context.ts +169 -10
- package/src/ssr/index.tsx +462 -178
- package/src/ssr/inject-rsc-eager.ts +167 -0
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/testing/collect-handle.ts +14 -8
- package/src/testing/dispatch.ts +152 -40
- package/src/testing/generated-routes.ts +27 -11
- package/src/testing/index.ts +6 -0
- package/src/testing/render-handler.ts +14 -0
- package/src/testing/render-route.tsx +13 -10
- package/src/testing/run-transition-when.ts +164 -0
- package/src/theme/ThemeProvider.tsx +36 -26
- package/src/types/handler-context.ts +1 -1
- package/src/types/index.ts +2 -0
- package/src/types/route-config.ts +19 -7
- package/src/types/segments.ts +100 -0
- package/src/urls/include-helper.ts +10 -8
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +1 -0
- package/src/urls/path-helper-types.ts +44 -12
- package/src/urls/path-helper.ts +5 -0
- package/src/urls/pattern-types.ts +36 -0
- package/src/urls/type-extraction.ts +43 -18
- package/src/urls/urls-function.ts +0 -1
- package/src/vercel/tracing.ts +7 -7
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +1 -1
- package/src/vite/discovery/discovery-errors.ts +61 -0
- package/src/vite/index.ts +7 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugins/vercel-output.ts +114 -25
- package/src/vite/plugins/version-injector.ts +22 -7
- package/src/vite/plugins/virtual-entries.ts +80 -22
- package/src/vite/rango.ts +29 -19
- package/src/vite/router-discovery.ts +171 -43
- package/src/vite/utils/prerender-utils.ts +17 -4
- package/src/vite/utils/shared-utils.ts +47 -0
- package/src/network-error-thrower.tsx +0 -18
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
CacheGetResult,
|
|
13
13
|
CacheItemResult,
|
|
14
14
|
CacheItemOptions,
|
|
15
|
+
ShellCacheEntry,
|
|
15
16
|
} from "./types.js";
|
|
16
17
|
import type { RequestContext } from "../server/request-context.js";
|
|
17
18
|
import { isPerClientSignalHeader } from "../browser/cookie-name.js";
|
|
@@ -26,6 +27,7 @@ import { reportCacheError } from "./cache-error.js";
|
|
|
26
27
|
const CACHE_REGISTRY_KEY = "__rsc_router_segment_cache_registry__";
|
|
27
28
|
const RESPONSE_CACHE_REGISTRY_KEY = "__rsc_router_response_cache_registry__";
|
|
28
29
|
const ITEM_CACHE_REGISTRY_KEY = "__rsc_router_item_cache_registry__";
|
|
30
|
+
const SHELL_CACHE_REGISTRY_KEY = "__rsc_router_shell_cache_registry__";
|
|
29
31
|
const TAG_INDEX_REGISTRY_KEY = "__rsc_router_tag_index_registry__";
|
|
30
32
|
const KEY_TAGS_REGISTRY_KEY = "__rsc_router_key_tags_registry__";
|
|
31
33
|
|
|
@@ -65,6 +67,13 @@ interface CachedItemEntry {
|
|
|
65
67
|
tags?: string[];
|
|
66
68
|
}
|
|
67
69
|
|
|
70
|
+
interface CachedShellEntry {
|
|
71
|
+
entry: ShellCacheEntry;
|
|
72
|
+
expiresAt: number;
|
|
73
|
+
staleAt: number;
|
|
74
|
+
tags?: string[];
|
|
75
|
+
}
|
|
76
|
+
|
|
68
77
|
/**
|
|
69
78
|
* Options for MemorySegmentCacheStore
|
|
70
79
|
*/
|
|
@@ -121,8 +130,25 @@ export interface MemorySegmentCacheStoreOptions<TEnv = unknown> {
|
|
|
121
130
|
ctx: RequestContext<TEnv>,
|
|
122
131
|
defaultKey: string,
|
|
123
132
|
) => string | Promise<string>;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Maximum number of entries kept per internal family (segment, response,
|
|
136
|
+
* item, shell). On insert, once a family reaches the cap its oldest entry is
|
|
137
|
+
* evicted FIFO (insertion order) and its tag-index entries are cleaned up, so
|
|
138
|
+
* a long-lived instance can't grow without bound. Lazy TTL expiry still runs
|
|
139
|
+
* independently. Defaults to DEFAULT_MAX_ENTRIES (1000).
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```typescript
|
|
143
|
+
* const store = new MemorySegmentCacheStore({ maxEntries: 500 });
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
maxEntries?: number;
|
|
124
147
|
}
|
|
125
148
|
|
|
149
|
+
/** Default per-family entry cap for MemorySegmentCacheStore (FIFO eviction). */
|
|
150
|
+
const DEFAULT_MAX_ENTRIES = 1000;
|
|
151
|
+
|
|
126
152
|
/**
|
|
127
153
|
* In-memory segment cache store.
|
|
128
154
|
*
|
|
@@ -130,10 +156,16 @@ export interface MemorySegmentCacheStoreOptions<TEnv = unknown> {
|
|
|
130
156
|
* For production with multiple instances, use a distributed store
|
|
131
157
|
* like Cloudflare KV or Redis.
|
|
132
158
|
*
|
|
159
|
+
* Each family is size-capped at `maxEntries` (default 1000) with FIFO eviction
|
|
160
|
+
* on insert, so a long-lived instance can't grow unbounded; TTL expiry stays
|
|
161
|
+
* lazy on top of that.
|
|
162
|
+
*
|
|
133
163
|
* Tag-index cleanup is lazy, mirroring the data maps: a tagged entry that
|
|
134
164
|
* expires but is never re-read or invalidated leaves its forward+reverse index
|
|
135
|
-
* entries resident until the key is reused or
|
|
136
|
-
* the distinct-tag count and acceptable for a dev/single-instance
|
|
165
|
+
* entries resident until the key is reused, invalidated, or FIFO-evicted. This
|
|
166
|
+
* is bounded by the distinct-tag count and acceptable for a dev/single-instance
|
|
167
|
+
* store. FIFO eviction unregisters the evicted key's tags, so the tag maps do
|
|
168
|
+
* not outlive the capped data maps.
|
|
137
169
|
*
|
|
138
170
|
* @example
|
|
139
171
|
* ```typescript
|
|
@@ -157,7 +189,8 @@ export class MemorySegmentCacheStore<
|
|
|
157
189
|
private cache: Map<string, CachedEntryData>;
|
|
158
190
|
private responseCache: Map<string, CachedResponseEntry>;
|
|
159
191
|
private itemCache: Map<string, CachedItemEntry>;
|
|
160
|
-
|
|
192
|
+
private shellCache: Map<string, CachedShellEntry>;
|
|
193
|
+
/** tag -> set of prefixed cache keys (seg:key, res:key, item:key, shell:key) */
|
|
161
194
|
private tagIndex: Map<string, Set<string>>;
|
|
162
195
|
/** prefixed cache key -> set of tags (reverse index for O(tags) unregister) */
|
|
163
196
|
private keyTags: Map<string, Set<string>>;
|
|
@@ -166,6 +199,8 @@ export class MemorySegmentCacheStore<
|
|
|
166
199
|
ctx: RequestContext<TEnv>,
|
|
167
200
|
defaultKey: string,
|
|
168
201
|
) => string | Promise<string>;
|
|
202
|
+
/** Per-family FIFO entry cap. */
|
|
203
|
+
readonly maxEntries: number;
|
|
169
204
|
|
|
170
205
|
constructor(options?: MemorySegmentCacheStoreOptions<TEnv>) {
|
|
171
206
|
if (options?.name != null) {
|
|
@@ -181,6 +216,10 @@ export class MemorySegmentCacheStore<
|
|
|
181
216
|
ITEM_CACHE_REGISTRY_KEY,
|
|
182
217
|
options.name,
|
|
183
218
|
);
|
|
219
|
+
this.shellCache = getNamedMap<CachedShellEntry>(
|
|
220
|
+
SHELL_CACHE_REGISTRY_KEY,
|
|
221
|
+
options.name,
|
|
222
|
+
);
|
|
184
223
|
this.tagIndex = getNamedMap<Set<string>>(
|
|
185
224
|
TAG_INDEX_REGISTRY_KEY,
|
|
186
225
|
options.name,
|
|
@@ -193,11 +232,34 @@ export class MemorySegmentCacheStore<
|
|
|
193
232
|
this.cache = new Map<string, CachedEntryData>();
|
|
194
233
|
this.responseCache = new Map<string, CachedResponseEntry>();
|
|
195
234
|
this.itemCache = new Map<string, CachedItemEntry>();
|
|
235
|
+
this.shellCache = new Map<string, CachedShellEntry>();
|
|
196
236
|
this.tagIndex = new Map<string, Set<string>>();
|
|
197
237
|
this.keyTags = new Map<string, Set<string>>();
|
|
198
238
|
}
|
|
199
239
|
this.defaults = options?.defaults;
|
|
200
240
|
this.keyGenerator = options?.keyGenerator;
|
|
241
|
+
this.maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* FIFO cap enforcement, called before inserting `incomingKey` into `map`.
|
|
246
|
+
* When the key is new and the family is at capacity, the oldest entries are
|
|
247
|
+
* evicted in insertion order and their tag-index entries unregistered (so the
|
|
248
|
+
* tag maps don't outlive the capped data map). Overwriting an existing key
|
|
249
|
+
* grows nothing, so it is a no-op.
|
|
250
|
+
*/
|
|
251
|
+
private evictIfNeeded<V>(
|
|
252
|
+
map: Map<string, V>,
|
|
253
|
+
incomingKey: string,
|
|
254
|
+
prefix: string,
|
|
255
|
+
): void {
|
|
256
|
+
if (map.has(incomingKey)) return;
|
|
257
|
+
while (map.size >= this.maxEntries) {
|
|
258
|
+
const oldest = map.keys().next().value;
|
|
259
|
+
if (oldest === undefined) break;
|
|
260
|
+
map.delete(oldest);
|
|
261
|
+
this.unregisterTags(`${prefix}:${oldest}`);
|
|
262
|
+
}
|
|
201
263
|
}
|
|
202
264
|
|
|
203
265
|
async get(key: string): Promise<CacheGetResult | null> {
|
|
@@ -230,6 +292,7 @@ export class MemorySegmentCacheStore<
|
|
|
230
292
|
};
|
|
231
293
|
const prefixedKey = `seg:${key}`;
|
|
232
294
|
this.unregisterTags(prefixedKey);
|
|
295
|
+
this.evictIfNeeded(this.cache, key, "seg");
|
|
233
296
|
this.cache.set(key, entry);
|
|
234
297
|
if (data.tags && data.tags.length > 0) {
|
|
235
298
|
this.registerTags(data.tags, prefixedKey);
|
|
@@ -245,6 +308,7 @@ export class MemorySegmentCacheStore<
|
|
|
245
308
|
this.cache.clear();
|
|
246
309
|
this.responseCache.clear();
|
|
247
310
|
this.itemCache.clear();
|
|
311
|
+
this.shellCache.clear();
|
|
248
312
|
this.tagIndex.clear();
|
|
249
313
|
this.keyTags.clear();
|
|
250
314
|
}
|
|
@@ -292,6 +356,7 @@ export class MemorySegmentCacheStore<
|
|
|
292
356
|
|
|
293
357
|
const prefixedKey = `res:${key}`;
|
|
294
358
|
this.unregisterTags(prefixedKey);
|
|
359
|
+
this.evictIfNeeded(this.responseCache, key, "res");
|
|
295
360
|
this.responseCache.set(key, {
|
|
296
361
|
body,
|
|
297
362
|
status: response.status,
|
|
@@ -341,6 +406,7 @@ export class MemorySegmentCacheStore<
|
|
|
341
406
|
const { staleAt, expiresAt } = computeExpiration(ttl, swrWindow);
|
|
342
407
|
const prefixedKey = `item:${key}`;
|
|
343
408
|
this.unregisterTags(prefixedKey);
|
|
409
|
+
this.evictIfNeeded(this.itemCache, key, "item");
|
|
344
410
|
this.itemCache.set(key, {
|
|
345
411
|
value,
|
|
346
412
|
handles: options?.handles,
|
|
@@ -353,6 +419,44 @@ export class MemorySegmentCacheStore<
|
|
|
353
419
|
}
|
|
354
420
|
}
|
|
355
421
|
|
|
422
|
+
async getShell(
|
|
423
|
+
key: string,
|
|
424
|
+
): Promise<{ entry: ShellCacheEntry; shouldRevalidate?: boolean } | null> {
|
|
425
|
+
const cached = this.shellCache.get(key);
|
|
426
|
+
if (!cached) return null;
|
|
427
|
+
|
|
428
|
+
const now = Date.now();
|
|
429
|
+
if (now > cached.expiresAt) {
|
|
430
|
+
this.unregisterTags(`shell:${key}`);
|
|
431
|
+
this.shellCache.delete(key);
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// SWR mirrors the item family: stale within the swr window still serves, and
|
|
436
|
+
// signals shouldRevalidate so the middleware schedules a background recapture.
|
|
437
|
+
const shouldRevalidate = cached.staleAt > 0 && now > cached.staleAt;
|
|
438
|
+
return { entry: cached.entry, shouldRevalidate };
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async putShell(
|
|
442
|
+
key: string,
|
|
443
|
+
entry: ShellCacheEntry,
|
|
444
|
+
ttlSeconds?: number,
|
|
445
|
+
swrSeconds?: number,
|
|
446
|
+
tags?: string[],
|
|
447
|
+
): Promise<void> {
|
|
448
|
+
const ttl = resolveTtl(ttlSeconds, this.defaults, DEFAULT_FUNCTION_TTL);
|
|
449
|
+
const swrWindow = resolveSwrWindow(swrSeconds, this.defaults);
|
|
450
|
+
const { staleAt, expiresAt } = computeExpiration(ttl, swrWindow);
|
|
451
|
+
const prefixedKey = `shell:${key}`;
|
|
452
|
+
this.unregisterTags(prefixedKey);
|
|
453
|
+
this.evictIfNeeded(this.shellCache, key, "shell");
|
|
454
|
+
this.shellCache.set(key, { entry, expiresAt, staleAt, tags });
|
|
455
|
+
if (tags && tags.length > 0) {
|
|
456
|
+
this.registerTags(tags, prefixedKey);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
356
460
|
async invalidateTags(tags: string[]): Promise<void> {
|
|
357
461
|
for (const tag of tags) {
|
|
358
462
|
const keys = this.tagIndex.get(tag);
|
|
@@ -371,6 +475,8 @@ export class MemorySegmentCacheStore<
|
|
|
371
475
|
this.responseCache.delete(rawKey);
|
|
372
476
|
} else if (prefix === "item") {
|
|
373
477
|
this.itemCache.delete(rawKey);
|
|
478
|
+
} else if (prefix === "shell") {
|
|
479
|
+
this.shellCache.delete(rawKey);
|
|
374
480
|
}
|
|
375
481
|
|
|
376
482
|
this.unregisterTags(prefixedKey);
|
|
@@ -421,6 +527,7 @@ export class MemorySegmentCacheStore<
|
|
|
421
527
|
delete (globalThis as any)[CACHE_REGISTRY_KEY];
|
|
422
528
|
delete (globalThis as any)[RESPONSE_CACHE_REGISTRY_KEY];
|
|
423
529
|
delete (globalThis as any)[ITEM_CACHE_REGISTRY_KEY];
|
|
530
|
+
delete (globalThis as any)[SHELL_CACHE_REGISTRY_KEY];
|
|
424
531
|
delete (globalThis as any)[TAG_INDEX_REGISTRY_KEY];
|
|
425
532
|
delete (globalThis as any)[KEY_TAGS_REGISTRY_KEY];
|
|
426
533
|
}
|
|
@@ -15,6 +15,21 @@ export interface CacheProfile {
|
|
|
15
15
|
swr?: number;
|
|
16
16
|
/** Default cache tags for invalidation */
|
|
17
17
|
tags?: string[];
|
|
18
|
+
/**
|
|
19
|
+
* When true, a stale entry encountered during a server action's revalidation
|
|
20
|
+
* render is re-executed in the FOREGROUND (the action response reflects the
|
|
21
|
+
* refreshed value) instead of being served stale + revalidated in the
|
|
22
|
+
* background. Only the store write is deferred. Use this for mutation-related
|
|
23
|
+
* cached data that an action should refresh immediately; ordinary navigations
|
|
24
|
+
* always keep SWR. Default false: incidental TTL staleness must not turn an
|
|
25
|
+
* unrelated action into a synchronous cache-refresh barrier — for strong
|
|
26
|
+
* read-your-own-writes after a mutation, prefer updateTag() (a hard purge, so
|
|
27
|
+
* the action's own re-render is a fresh foreground miss).
|
|
28
|
+
*
|
|
29
|
+
* Honored only by `"use cache"` functions; route-level `cache()` and cached
|
|
30
|
+
* loaders ignore it (use updateTag() there).
|
|
31
|
+
*/
|
|
32
|
+
foregroundOnAction?: boolean;
|
|
18
33
|
}
|
|
19
34
|
|
|
20
35
|
const DEFAULT_PROFILE: CacheProfile = { ttl: 900, swr: 1800 };
|
|
@@ -36,6 +36,14 @@ export interface ReadThroughItemConfig<T> {
|
|
|
36
36
|
key: string;
|
|
37
37
|
/** Execute the underlying function/loader on miss or revalidation */
|
|
38
38
|
execute: () => Promise<T>;
|
|
39
|
+
/**
|
|
40
|
+
* Optional wrapper applied to execute() ONLY on the background
|
|
41
|
+
* stale-revalidation path (not the foreground miss, where the caller's context
|
|
42
|
+
* is already established). Used to re-establish the request-context ALS, which
|
|
43
|
+
* a detached waitUntil task loses on workerd. Defaults to calling execute()
|
|
44
|
+
* directly.
|
|
45
|
+
*/
|
|
46
|
+
wrapBackground?: (run: () => Promise<T>) => Promise<T>;
|
|
39
47
|
/** Serialize result for storage. Return null to skip caching. */
|
|
40
48
|
serialize: (data: T) => Promise<string | null>;
|
|
41
49
|
/** Deserialize cached value back to the original type */
|
|
@@ -77,6 +85,7 @@ export async function readThroughItem<T>(
|
|
|
77
85
|
onMiss,
|
|
78
86
|
onCached,
|
|
79
87
|
host,
|
|
88
|
+
wrapBackground,
|
|
80
89
|
} = config;
|
|
81
90
|
|
|
82
91
|
// Cache lookup. An infra read failure (getItem) is reported by the store
|
|
@@ -106,7 +115,12 @@ export async function readThroughItem<T>(
|
|
|
106
115
|
host,
|
|
107
116
|
async () => {
|
|
108
117
|
try {
|
|
109
|
-
|
|
118
|
+
// Re-establish the caller's context (request-context ALS) for the
|
|
119
|
+
// detached background execution; the foreground miss below calls
|
|
120
|
+
// execute() directly since its context is already established.
|
|
121
|
+
const fresh = await (wrapBackground
|
|
122
|
+
? wrapBackground(execute)
|
|
123
|
+
: execute());
|
|
110
124
|
const serialized = await serialize(fresh);
|
|
111
125
|
if (serialized !== null) {
|
|
112
126
|
await setItem(key, serialized, storeOptions);
|
|
@@ -25,16 +25,16 @@ export async function streamToString(
|
|
|
25
25
|
): Promise<string> {
|
|
26
26
|
const reader = stream.getReader();
|
|
27
27
|
const decoder = new TextDecoder();
|
|
28
|
-
|
|
28
|
+
const chunks: string[] = [];
|
|
29
29
|
|
|
30
30
|
while (true) {
|
|
31
31
|
const { done, value } = await reader.read();
|
|
32
32
|
if (done) break;
|
|
33
|
-
|
|
33
|
+
chunks.push(decoder.decode(value, { stream: true }));
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
return
|
|
36
|
+
chunks.push(decoder.decode()); // flush
|
|
37
|
+
return chunks.join("");
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|