@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.80
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 +9 -0
- package/README.md +942 -4
- package/dist/bin/rango.js +1689 -0
- package/dist/vite/index.js +4960 -935
- package/package.json +70 -60
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +294 -0
- package/skills/caching/SKILL.md +93 -23
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +18 -16
- package/skills/fonts/SKILL.md +167 -0
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +334 -72
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +151 -8
- package/skills/layout/SKILL.md +122 -3
- package/skills/links/SKILL.md +92 -31
- package/skills/loader/SKILL.md +404 -44
- package/skills/middleware/SKILL.md +205 -37
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +764 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +263 -1
- package/skills/prerender/SKILL.md +685 -0
- package/skills/rango/SKILL.md +87 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +281 -14
- package/skills/router-setup/SKILL.md +210 -32
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +328 -89
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +92 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +317 -560
- package/src/browser/navigation-client.ts +206 -68
- package/src/browser/navigation-store.ts +73 -55
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +343 -316
- package/src/browser/prefetch/cache.ts +216 -0
- package/src/browser/prefetch/fetch.ts +206 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +160 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +253 -74
- package/src/browser/react/NavigationProvider.tsx +87 -11
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +30 -126
- package/src/browser/react/use-href.tsx +2 -2
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +44 -65
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +76 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +214 -58
- package/src/browser/scroll-restoration.ts +127 -52
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +510 -603
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +141 -48
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +235 -24
- package/src/build/generate-route-types.ts +39 -0
- package/src/build/index.ts +13 -0
- package/src/build/route-trie.ts +291 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +418 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +618 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +342 -0
- package/src/cache/cache-scope.ts +167 -309
- package/src/cache/cf/cf-cache-store.ts +571 -17
- package/src/cache/cf/index.ts +13 -3
- package/src/cache/document-cache.ts +116 -77
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +1 -15
- package/src/cache/memory-segment-store.ts +191 -13
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +3 -1
- package/src/client.tsx +135 -301
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +156 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +108 -2
- package/src/handle.ts +55 -29
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +21 -15
- package/src/host/errors.ts +8 -8
- package/src/host/index.ts +4 -7
- package/src/host/pattern-matcher.ts +27 -27
- package/src/host/router.ts +61 -39
- package/src/host/testing.ts +8 -8
- package/src/host/types.ts +15 -7
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +119 -29
- package/src/index.rsc.ts +155 -19
- package/src/index.ts +251 -30
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +26 -157
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +186 -0
- package/src/prerender.ts +524 -0
- package/src/reverse.ts +354 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +1121 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +478 -0
- package/src/route-definition/index.ts +55 -0
- package/src/route-definition/redirect.ts +101 -0
- package/src/route-definition/resolve-handler-use.ts +149 -0
- package/src/route-definition.ts +1 -1428
- package/src/route-map-builder.ts +217 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +77 -8
- package/src/router/content-negotiation.ts +215 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +438 -86
- package/src/router/intercept-resolution.ts +402 -0
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +356 -128
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +163 -35
- package/src/router/match-api.ts +555 -0
- package/src/router/match-context.ts +5 -3
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +108 -93
- package/src/router/match-middleware/cache-lookup.ts +460 -10
- package/src/router/match-middleware/cache-store.ts +98 -26
- package/src/router/match-middleware/intercept-resolution.ts +57 -17
- package/src/router/match-middleware/segment-resolution.ts +80 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +135 -35
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +220 -0
- package/src/router/middleware.ts +324 -369
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +211 -43
- package/src/router/prerender-match.ts +502 -0
- package/src/router/preview-match.ts +98 -0
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +41 -21
- package/src/router/router-interfaces.ts +484 -0
- package/src/router/router-options.ts +618 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +748 -0
- package/src/router/segment-resolution/helpers.ts +268 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1379 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +78 -3
- package/src/router.ts +740 -4252
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +907 -797
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +229 -0
- package/src/rsc/manifest-init.ts +90 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +391 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +246 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +356 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +46 -11
- package/src/search-params.ts +230 -0
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +134 -36
- package/src/server/context.ts +341 -61
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +113 -15
- package/src/server/loader-registry.ts +24 -64
- package/src/server/request-context.ts +607 -81
- package/src/server.ts +35 -130
- package/src/ssr/index.tsx +103 -30
- package/src/static-handler.ts +126 -0
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +791 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +210 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +120 -0
- package/src/types/segments.ts +150 -0
- package/src/types.ts +1 -1623
- package/src/urls/include-helper.ts +207 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +372 -0
- package/src/urls/path-helper.ts +364 -0
- package/src/urls/pattern-types.ts +107 -0
- package/src/urls/response-types.ts +116 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -802
- package/src/use-loader.tsx +161 -81
- package/src/vite/discovery/bundle-postprocess.ts +181 -0
- package/src/vite/discovery/discover-routers.ts +348 -0
- package/src/vite/discovery/prerender-collection.ts +439 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +117 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +15 -1133
- package/src/vite/plugin-types.ts +103 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
- package/src/vite/plugins/expose-id-utils.ts +299 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +786 -0
- package/src/vite/plugins/performance-tracks.ts +88 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +266 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +462 -0
- package/src/vite/router-discovery.ts +918 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +221 -0
- package/src/vite/utils/shared-utils.ts +170 -0
- package/CLAUDE.md +0 -43
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/href.ts +0 -255
- package/src/server/route-manifest-cache.ts +0 -173
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -426
- package/src/vite/expose-location-state-id.ts +0 -177
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -5,15 +5,83 @@
|
|
|
5
5
|
* Uses globalThis to survive HMR in development.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
SegmentCacheStore,
|
|
10
|
+
CachedEntryData,
|
|
11
|
+
CacheDefaults,
|
|
12
|
+
CacheGetResult,
|
|
13
|
+
CacheItemResult,
|
|
14
|
+
CacheItemOptions,
|
|
15
|
+
SegmentHandleData,
|
|
16
|
+
} from "./types.js";
|
|
9
17
|
import type { RequestContext } from "../server/request-context.js";
|
|
18
|
+
import {
|
|
19
|
+
resolveTtl,
|
|
20
|
+
resolveSwrWindow,
|
|
21
|
+
computeExpiration,
|
|
22
|
+
DEFAULT_FUNCTION_TTL,
|
|
23
|
+
} from "./cache-policy.js";
|
|
10
24
|
|
|
11
|
-
const
|
|
25
|
+
const CACHE_REGISTRY_KEY = "__rsc_router_segment_cache_registry__";
|
|
26
|
+
const RESPONSE_CACHE_REGISTRY_KEY = "__rsc_router_response_cache_registry__";
|
|
27
|
+
const ITEM_CACHE_REGISTRY_KEY = "__rsc_router_item_cache_registry__";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get or create a named Map from a globalThis-backed registry.
|
|
31
|
+
* The registry survives HMR; individual stores are keyed by name.
|
|
32
|
+
*/
|
|
33
|
+
function getNamedMap<V>(registryKey: string, name: string): Map<string, V> {
|
|
34
|
+
let registry = (globalThis as any)[registryKey] as
|
|
35
|
+
| Map<string, Map<string, V>>
|
|
36
|
+
| undefined;
|
|
37
|
+
if (!registry) {
|
|
38
|
+
registry = new Map();
|
|
39
|
+
(globalThis as any)[registryKey] = registry;
|
|
40
|
+
}
|
|
41
|
+
let map = registry.get(name);
|
|
42
|
+
if (!map) {
|
|
43
|
+
map = new Map<string, V>();
|
|
44
|
+
registry.set(name, map);
|
|
45
|
+
}
|
|
46
|
+
return map;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface CachedResponseEntry {
|
|
50
|
+
body: ArrayBuffer;
|
|
51
|
+
status: number;
|
|
52
|
+
headers: [string, string][];
|
|
53
|
+
expiresAt: number;
|
|
54
|
+
staleAt: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface CachedItemEntry {
|
|
58
|
+
value: string;
|
|
59
|
+
handles?: Record<string, SegmentHandleData>;
|
|
60
|
+
expiresAt: number;
|
|
61
|
+
staleAt: number;
|
|
62
|
+
}
|
|
12
63
|
|
|
13
64
|
/**
|
|
14
65
|
* Options for MemorySegmentCacheStore
|
|
15
66
|
*/
|
|
16
67
|
export interface MemorySegmentCacheStoreOptions<TEnv = unknown> {
|
|
68
|
+
/**
|
|
69
|
+
* Optional name for this store instance. Named stores persist their
|
|
70
|
+
* backing Map on globalThis so data survives Vite HMR module reloads.
|
|
71
|
+
* Stores with different names get separate Maps.
|
|
72
|
+
*
|
|
73
|
+
* When omitted, the store uses a plain instance-level Map with no
|
|
74
|
+
* globalThis sharing, which is the safest default for isolation.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* // Two named stores are isolated from each other
|
|
79
|
+
* const fast = new MemorySegmentCacheStore({ name: "fast", defaults: { ttl: 10 } });
|
|
80
|
+
* const slow = new MemorySegmentCacheStore({ name: "slow", defaults: { ttl: 300 } });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
name?: string;
|
|
84
|
+
|
|
17
85
|
/**
|
|
18
86
|
* Default cache options for cache() boundaries.
|
|
19
87
|
* When cache() is called without explicit ttl/swr,
|
|
@@ -35,14 +103,14 @@ export interface MemorySegmentCacheStoreOptions<TEnv = unknown> {
|
|
|
35
103
|
* @example
|
|
36
104
|
* ```typescript
|
|
37
105
|
* keyGenerator: (ctx, defaultKey) => {
|
|
38
|
-
* const locale =
|
|
106
|
+
* const locale = cookies().get('locale')?.value || 'en';
|
|
39
107
|
* return `${locale}:${defaultKey}`;
|
|
40
108
|
* }
|
|
41
109
|
* ```
|
|
42
110
|
*/
|
|
43
111
|
keyGenerator?: (
|
|
44
112
|
ctx: RequestContext<TEnv>,
|
|
45
|
-
defaultKey: string
|
|
113
|
+
defaultKey: string,
|
|
46
114
|
) => string | Promise<string>;
|
|
47
115
|
}
|
|
48
116
|
|
|
@@ -69,19 +137,40 @@ export interface MemorySegmentCacheStoreOptions<TEnv = unknown> {
|
|
|
69
137
|
* })
|
|
70
138
|
* ```
|
|
71
139
|
*/
|
|
72
|
-
export class MemorySegmentCacheStore<
|
|
140
|
+
export class MemorySegmentCacheStore<
|
|
141
|
+
TEnv = unknown,
|
|
142
|
+
> implements SegmentCacheStore<TEnv> {
|
|
73
143
|
private cache: Map<string, CachedEntryData>;
|
|
144
|
+
private responseCache: Map<string, CachedResponseEntry>;
|
|
145
|
+
private itemCache: Map<string, CachedItemEntry>;
|
|
74
146
|
readonly defaults?: CacheDefaults;
|
|
75
147
|
readonly keyGenerator?: (
|
|
76
148
|
ctx: RequestContext<TEnv>,
|
|
77
|
-
defaultKey: string
|
|
149
|
+
defaultKey: string,
|
|
78
150
|
) => string | Promise<string>;
|
|
79
151
|
|
|
80
152
|
constructor(options?: MemorySegmentCacheStoreOptions<TEnv>) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
153
|
+
if (options?.name != null) {
|
|
154
|
+
// Named stores use the globalThis registry so data survives HMR.
|
|
155
|
+
// Each name gets its own isolated Map.
|
|
156
|
+
this.cache = getNamedMap<CachedEntryData>(
|
|
157
|
+
CACHE_REGISTRY_KEY,
|
|
158
|
+
options.name,
|
|
159
|
+
);
|
|
160
|
+
this.responseCache = getNamedMap<CachedResponseEntry>(
|
|
161
|
+
RESPONSE_CACHE_REGISTRY_KEY,
|
|
162
|
+
options.name,
|
|
163
|
+
);
|
|
164
|
+
this.itemCache = getNamedMap<CachedItemEntry>(
|
|
165
|
+
ITEM_CACHE_REGISTRY_KEY,
|
|
166
|
+
options.name,
|
|
167
|
+
);
|
|
168
|
+
} else {
|
|
169
|
+
// Unnamed stores get a plain instance-level Map (no globalThis sharing).
|
|
170
|
+
this.cache = new Map<string, CachedEntryData>();
|
|
171
|
+
this.responseCache = new Map<string, CachedResponseEntry>();
|
|
172
|
+
this.itemCache = new Map<string, CachedItemEntry>();
|
|
173
|
+
}
|
|
85
174
|
this.defaults = options?.defaults;
|
|
86
175
|
this.keyGenerator = options?.keyGenerator;
|
|
87
176
|
}
|
|
@@ -103,7 +192,12 @@ export class MemorySegmentCacheStore<TEnv = unknown> implements SegmentCacheStor
|
|
|
103
192
|
return { data: cached, shouldRevalidate: false };
|
|
104
193
|
}
|
|
105
194
|
|
|
106
|
-
async set(
|
|
195
|
+
async set(
|
|
196
|
+
key: string,
|
|
197
|
+
data: CachedEntryData,
|
|
198
|
+
ttl: number,
|
|
199
|
+
_swr?: number,
|
|
200
|
+
): Promise<void> {
|
|
107
201
|
// Note: Memory store doesn't implement SWR - entries just expire at TTL
|
|
108
202
|
// For SWR support, use CFCacheStore or similar distributed cache
|
|
109
203
|
const entry: CachedEntryData = {
|
|
@@ -119,6 +213,88 @@ export class MemorySegmentCacheStore<TEnv = unknown> implements SegmentCacheStor
|
|
|
119
213
|
|
|
120
214
|
async clear(): Promise<void> {
|
|
121
215
|
this.cache.clear();
|
|
216
|
+
this.responseCache.clear();
|
|
217
|
+
this.itemCache.clear();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async getResponse(
|
|
221
|
+
key: string,
|
|
222
|
+
): Promise<{ response: Response; shouldRevalidate: boolean } | null> {
|
|
223
|
+
const cached = this.responseCache.get(key);
|
|
224
|
+
if (!cached) return null;
|
|
225
|
+
|
|
226
|
+
if (Date.now() > cached.expiresAt) {
|
|
227
|
+
this.responseCache.delete(key);
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const isStale = cached.staleAt > 0 && Date.now() > cached.staleAt;
|
|
232
|
+
const headers = new Headers(cached.headers);
|
|
233
|
+
return {
|
|
234
|
+
response: new Response(cached.body, {
|
|
235
|
+
status: cached.status,
|
|
236
|
+
headers,
|
|
237
|
+
}),
|
|
238
|
+
shouldRevalidate: isStale,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async putResponse(
|
|
243
|
+
key: string,
|
|
244
|
+
response: Response,
|
|
245
|
+
ttl: number,
|
|
246
|
+
swr?: number,
|
|
247
|
+
): Promise<void> {
|
|
248
|
+
const body = await response.clone().arrayBuffer();
|
|
249
|
+
const headers: [string, string][] = [];
|
|
250
|
+
response.headers.forEach((value, name) => {
|
|
251
|
+
headers.push([name, value]);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const swrWindow = resolveSwrWindow(swr, this.defaults);
|
|
255
|
+
const { staleAt, expiresAt } = computeExpiration(ttl, swrWindow);
|
|
256
|
+
|
|
257
|
+
this.responseCache.set(key, {
|
|
258
|
+
body,
|
|
259
|
+
status: response.status,
|
|
260
|
+
headers,
|
|
261
|
+
expiresAt,
|
|
262
|
+
staleAt,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async getItem(key: string): Promise<CacheItemResult | null> {
|
|
267
|
+
const cached = this.itemCache.get(key);
|
|
268
|
+
if (!cached) return null;
|
|
269
|
+
|
|
270
|
+
const now = Date.now();
|
|
271
|
+
if (now > cached.expiresAt) {
|
|
272
|
+
this.itemCache.delete(key);
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const isStale = now > cached.staleAt;
|
|
277
|
+
return {
|
|
278
|
+
value: cached.value,
|
|
279
|
+
handles: cached.handles,
|
|
280
|
+
shouldRevalidate: isStale,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async setItem(
|
|
285
|
+
key: string,
|
|
286
|
+
value: string,
|
|
287
|
+
options?: CacheItemOptions,
|
|
288
|
+
): Promise<void> {
|
|
289
|
+
const ttl = resolveTtl(options?.ttl, this.defaults, DEFAULT_FUNCTION_TTL);
|
|
290
|
+
const swrWindow = resolveSwrWindow(options?.swr, this.defaults);
|
|
291
|
+
const { staleAt, expiresAt } = computeExpiration(ttl, swrWindow);
|
|
292
|
+
this.itemCache.set(key, {
|
|
293
|
+
value,
|
|
294
|
+
handles: options?.handles,
|
|
295
|
+
expiresAt,
|
|
296
|
+
staleAt,
|
|
297
|
+
});
|
|
122
298
|
}
|
|
123
299
|
|
|
124
300
|
/**
|
|
@@ -133,7 +309,7 @@ export class MemorySegmentCacheStore<TEnv = unknown> implements SegmentCacheStor
|
|
|
133
309
|
}
|
|
134
310
|
|
|
135
311
|
/**
|
|
136
|
-
* Reset the global cache
|
|
312
|
+
* Reset the global cache registry.
|
|
137
313
|
* Useful for test isolation - call this in beforeEach to ensure
|
|
138
314
|
* tests don't share cache state via globalThis.
|
|
139
315
|
*
|
|
@@ -145,6 +321,8 @@ export class MemorySegmentCacheStore<TEnv = unknown> implements SegmentCacheStor
|
|
|
145
321
|
* ```
|
|
146
322
|
*/
|
|
147
323
|
static resetGlobalCache(): void {
|
|
148
|
-
delete (globalThis as any)[
|
|
324
|
+
delete (globalThis as any)[CACHE_REGISTRY_KEY];
|
|
325
|
+
delete (globalThis as any)[RESPONSE_CACHE_REGISTRY_KEY];
|
|
326
|
+
delete (globalThis as any)[ITEM_CACHE_REGISTRY_KEY];
|
|
149
327
|
}
|
|
150
328
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Profile Registry
|
|
3
|
+
*
|
|
4
|
+
* Named cache profiles for "use cache" directive.
|
|
5
|
+
* Profiles define TTL, SWR, and optional default tags.
|
|
6
|
+
* Set by createRouter() at startup, read by registerCachedFunction() at runtime.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface CacheProfile {
|
|
10
|
+
/** Time-to-live in seconds */
|
|
11
|
+
ttl: number;
|
|
12
|
+
/** Stale-while-revalidate window in seconds */
|
|
13
|
+
swr?: number;
|
|
14
|
+
/** Default cache tags for invalidation */
|
|
15
|
+
tags?: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const DEFAULT_PROFILE: CacheProfile = { ttl: 900, swr: 1800 };
|
|
19
|
+
|
|
20
|
+
let _profiles: Record<string, CacheProfile> = {
|
|
21
|
+
default: DEFAULT_PROFILE,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const PROFILE_NAME_RE = /^[a-zA-Z0-9_-]+$/;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validate and merge user profiles with the default profile.
|
|
28
|
+
* Returns a new object suitable for both DSL-time and request-scoped use.
|
|
29
|
+
*
|
|
30
|
+
* Used by createRouter() to compute the resolved profile map once,
|
|
31
|
+
* stored on the router instance and passed to every request context.
|
|
32
|
+
*/
|
|
33
|
+
export function resolveCacheProfiles(
|
|
34
|
+
profiles?: Record<string, CacheProfile>,
|
|
35
|
+
): Record<string, CacheProfile> {
|
|
36
|
+
const merged: Record<string, CacheProfile> = {
|
|
37
|
+
default: DEFAULT_PROFILE,
|
|
38
|
+
};
|
|
39
|
+
if (profiles) {
|
|
40
|
+
for (const name of Object.keys(profiles)) {
|
|
41
|
+
if (!PROFILE_NAME_RE.test(name)) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Invalid cache profile name "${name}". ` +
|
|
44
|
+
`Profile names must match [a-zA-Z0-9_-]+.`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
merged[name] = profiles[name];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return merged;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Set all cache profiles in the global registry.
|
|
55
|
+
* Called by createRouter() at startup for DSL-time resolution
|
|
56
|
+
* (cache("profileName") reads from this during route definition).
|
|
57
|
+
*
|
|
58
|
+
* WARNING: This is global mutable state. It exists only for DSL-time
|
|
59
|
+
* reads. Runtime resolution (registerCachedFunction) uses request-scoped
|
|
60
|
+
* profiles and does NOT read from this registry.
|
|
61
|
+
*/
|
|
62
|
+
export function setCacheProfiles(profiles: Record<string, CacheProfile>): void {
|
|
63
|
+
_profiles = resolveCacheProfiles(profiles);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get a cache profile by name from the global registry.
|
|
68
|
+
* Used only at DSL-time (cache("profileName") inside urls() evaluation).
|
|
69
|
+
* Runtime code uses request-scoped profiles instead.
|
|
70
|
+
*/
|
|
71
|
+
export function getCacheProfile(name: string): CacheProfile | undefined {
|
|
72
|
+
return _profiles[name];
|
|
73
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SWR Read-Through Engine
|
|
3
|
+
*
|
|
4
|
+
* Generic read-through cache with stale-while-revalidate support
|
|
5
|
+
* for item-level caching (getItem/setItem).
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. Lookup cached item by key
|
|
9
|
+
* 2. Fresh hit → deserialize, return
|
|
10
|
+
* 3. Stale hit → deserialize, return, revalidate in background
|
|
11
|
+
* 4. Miss → execute, cache write (blocking when no waitUntil), return
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { CacheItemResult, CacheItemOptions } from "./types.js";
|
|
15
|
+
import { runBackground } from "./background-task.js";
|
|
16
|
+
|
|
17
|
+
interface WaitUntilHost {
|
|
18
|
+
waitUntil?: (fn: () => Promise<void>) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ReadThroughItemConfig<T> {
|
|
22
|
+
/** Retrieve a cached item by key */
|
|
23
|
+
getItem: (key: string) => Promise<CacheItemResult | null>;
|
|
24
|
+
/** Store a serialized item by key */
|
|
25
|
+
setItem: (
|
|
26
|
+
key: string,
|
|
27
|
+
value: string,
|
|
28
|
+
options?: CacheItemOptions,
|
|
29
|
+
) => Promise<void>;
|
|
30
|
+
/** Cache key */
|
|
31
|
+
key: string;
|
|
32
|
+
/** Execute the underlying function/loader on miss or revalidation */
|
|
33
|
+
execute: () => Promise<T>;
|
|
34
|
+
/** Serialize result for storage. Return null to skip caching. */
|
|
35
|
+
serialize: (data: T) => Promise<string | null>;
|
|
36
|
+
/** Deserialize cached value back to the original type */
|
|
37
|
+
deserialize: (value: string) => Promise<T>;
|
|
38
|
+
/** Options passed to setItem on cache write */
|
|
39
|
+
storeOptions: CacheItemOptions;
|
|
40
|
+
/** Called on fresh cache hit (before returning data) */
|
|
41
|
+
onHit?: (cached: CacheItemResult) => void;
|
|
42
|
+
/** Called on stale cache hit (before scheduling background revalidation) */
|
|
43
|
+
onStale?: (cached: CacheItemResult) => void;
|
|
44
|
+
/** Called on cache miss (before executing) */
|
|
45
|
+
onMiss?: () => void;
|
|
46
|
+
/** Called after successful cache write */
|
|
47
|
+
onCached?: () => void;
|
|
48
|
+
/** Host with optional waitUntil for background tasks */
|
|
49
|
+
host?: WaitUntilHost | null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Read-through cache with SWR support for item-level caching.
|
|
54
|
+
*
|
|
55
|
+
* On fresh hit: returns deserialized cached data.
|
|
56
|
+
* On stale hit: returns stale data, schedules background revalidation.
|
|
57
|
+
* On miss: executes, writes to cache (blocking when no waitUntil), returns.
|
|
58
|
+
*/
|
|
59
|
+
export async function readThroughItem<T>(
|
|
60
|
+
config: ReadThroughItemConfig<T>,
|
|
61
|
+
): Promise<T> {
|
|
62
|
+
const {
|
|
63
|
+
getItem,
|
|
64
|
+
setItem,
|
|
65
|
+
key,
|
|
66
|
+
execute,
|
|
67
|
+
serialize,
|
|
68
|
+
deserialize,
|
|
69
|
+
storeOptions,
|
|
70
|
+
onHit,
|
|
71
|
+
onStale,
|
|
72
|
+
onMiss,
|
|
73
|
+
onCached,
|
|
74
|
+
host,
|
|
75
|
+
} = config;
|
|
76
|
+
|
|
77
|
+
// Cache lookup
|
|
78
|
+
try {
|
|
79
|
+
const cached = await getItem(key);
|
|
80
|
+
|
|
81
|
+
if (cached) {
|
|
82
|
+
const data = await deserialize(cached.value);
|
|
83
|
+
|
|
84
|
+
if (!cached.shouldRevalidate) {
|
|
85
|
+
onHit?.(cached);
|
|
86
|
+
return data;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Stale hit — return stale data, revalidate in background
|
|
90
|
+
onStale?.(cached);
|
|
91
|
+
runBackground(
|
|
92
|
+
host,
|
|
93
|
+
async () => {
|
|
94
|
+
try {
|
|
95
|
+
const fresh = await execute();
|
|
96
|
+
const serialized = await serialize(fresh);
|
|
97
|
+
if (serialized !== null) {
|
|
98
|
+
await setItem(key, serialized, storeOptions);
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
// Background revalidation failed silently
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
true,
|
|
105
|
+
);
|
|
106
|
+
return data;
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
// Cache lookup failed, fall through to fresh execution
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Cache miss
|
|
113
|
+
onMiss?.();
|
|
114
|
+
const data = await execute();
|
|
115
|
+
|
|
116
|
+
// Non-blocking cache write (blocks when no waitUntil)
|
|
117
|
+
await runBackground(
|
|
118
|
+
host,
|
|
119
|
+
async () => {
|
|
120
|
+
try {
|
|
121
|
+
const serialized = await serialize(data);
|
|
122
|
+
if (serialized !== null) {
|
|
123
|
+
await setItem(key, serialized, storeOptions);
|
|
124
|
+
onCached?.();
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
// Cache write failed silently
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
true,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
return data;
|
|
134
|
+
}
|