@rangojs/router 0.0.0-experimental.002d056c
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 +899 -0
- package/dist/bin/rango.js +1606 -0
- package/dist/vite/index.js +5153 -0
- package/package.json +177 -0
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +253 -0
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +112 -0
- package/skills/document-cache/SKILL.md +182 -0
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +704 -0
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +313 -0
- package/skills/layout/SKILL.md +310 -0
- package/skills/links/SKILL.md +239 -0
- package/skills/loader/SKILL.md +596 -0
- package/skills/middleware/SKILL.md +339 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +305 -0
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +118 -0
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +385 -0
- package/skills/router-setup/SKILL.md +439 -0
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +79 -0
- package/skills/typesafety/SKILL.md +623 -0
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +273 -0
- 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/event-controller.ts +899 -0
- package/src/browser/history-state.ts +80 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +141 -0
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +134 -0
- package/src/browser/navigation-bridge.ts +638 -0
- package/src/browser/navigation-client.ts +261 -0
- package/src/browser/navigation-store.ts +806 -0
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +582 -0
- package/src/browser/prefetch/cache.ts +206 -0
- package/src/browser/prefetch/fetch.ts +145 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +128 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +368 -0
- package/src/browser/react/NavigationProvider.tsx +413 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +59 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +162 -0
- package/src/browser/react/location-state.ts +107 -0
- package/src/browser/react/mount-context.ts +37 -0
- 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 +218 -0
- package/src/browser/react/use-client-cache.ts +58 -0
- package/src/browser/react/use-handle.ts +162 -0
- package/src/browser/react/use-href.tsx +40 -0
- package/src/browser/react/use-link-status.ts +135 -0
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +99 -0
- 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 +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +171 -0
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +464 -0
- package/src/browser/scroll-restoration.ts +397 -0
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +83 -0
- package/src/browser/server-action-bridge.ts +667 -0
- package/src/browser/shallow.ts +40 -0
- package/src/browser/types.ts +547 -0
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +438 -0
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +35 -0
- package/src/build/route-trie.ts +265 -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 +411 -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 +479 -0
- package/src/build/route-types/scan-filter.ts +78 -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 +338 -0
- package/src/cache/cache-scope.ts +382 -0
- package/src/cache/cf/cf-cache-store.ts +982 -0
- package/src/cache/cf/index.ts +29 -0
- package/src/cache/document-cache.ts +369 -0
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +44 -0
- package/src/cache/memory-segment-store.ts +328 -0
- 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 +98 -0
- package/src/cache/types.ts +342 -0
- package/src/client.rsc.tsx +85 -0
- package/src/client.tsx +601 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +27 -0
- package/src/context-var.ts +86 -0
- package/src/debug.ts +243 -0
- package/src/default-error-boundary.tsx +88 -0
- package/src/deps/browser.ts +8 -0
- package/src/deps/html-stream-client.ts +2 -0
- package/src/deps/html-stream-server.ts +2 -0
- package/src/deps/rsc.ts +10 -0
- package/src/deps/ssr.ts +2 -0
- package/src/errors.ts +365 -0
- package/src/handle.ts +135 -0
- package/src/handles/MetaTags.tsx +246 -0
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +7 -0
- package/src/handles/meta.ts +264 -0
- package/src/host/cookie-handler.ts +165 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +53 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +352 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +146 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +222 -0
- package/src/index.rsc.ts +233 -0
- package/src/index.ts +277 -0
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +89 -0
- package/src/loader.ts +64 -0
- package/src/network-error-thrower.tsx +23 -0
- package/src/outlet-context.ts +15 -0
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +289 -0
- package/src/route-content-wrapper.tsx +196 -0
- package/src/route-definition/dsl-helpers.ts +934 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -0
- package/src/route-map-builder.ts +281 -0
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +259 -0
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +451 -0
- package/src/router/intercept-resolution.ts +397 -0
- package/src/router/lazy-includes.ts +236 -0
- package/src/router/loader-resolution.ts +420 -0
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +269 -0
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +266 -0
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +223 -0
- package/src/router/match-middleware/cache-lookup.ts +634 -0
- package/src/router/match-middleware/cache-store.ts +295 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +306 -0
- package/src/router/match-middleware/segment-resolution.ts +193 -0
- package/src/router/match-pipelines.ts +179 -0
- package/src/router/match-result.ts +219 -0
- package/src/router/metrics.ts +282 -0
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +749 -0
- package/src/router/pattern-matching.ts +563 -0
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +289 -0
- package/src/router/router-context.ts +320 -0
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1242 -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 +170 -0
- package/src/router.ts +1006 -0
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +1089 -0
- package/src/rsc/helpers.ts +198 -0
- package/src/rsc/index.ts +36 -0
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +32 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +237 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +263 -0
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +454 -0
- package/src/server/context.ts +591 -0
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +308 -0
- package/src/server/loader-registry.ts +133 -0
- package/src/server/request-context.ts +920 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +51 -0
- package/src/ssr/index.tsx +365 -0
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +297 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/constants.ts +62 -0
- package/src/theme/index.ts +48 -0
- package/src/theme/theme-context.ts +44 -0
- package/src/theme/theme-script.ts +155 -0
- package/src/theme/types.ts +182 -0
- package/src/theme/use-theme.ts +44 -0
- 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 +687 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +109 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -0
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -0
- package/src/use-loader.tsx +354 -0
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -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 +108 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +16 -0
- package/src/vite/plugin-types.ts +48 -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/plugins/expose-action-id.ts +363 -0
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -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 +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -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/plugins/version.d.ts +12 -0
- package/src/vite/plugins/virtual-entries.ts +123 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +445 -0
- package/src/vite/router-discovery.ts +777 -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/utils/package-resolution.ts +121 -0
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-Memory Segment Cache Store
|
|
3
|
+
*
|
|
4
|
+
* Simple in-memory implementation of SegmentCacheStore.
|
|
5
|
+
* Uses globalThis to survive HMR in development.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
SegmentCacheStore,
|
|
10
|
+
CachedEntryData,
|
|
11
|
+
CacheDefaults,
|
|
12
|
+
CacheGetResult,
|
|
13
|
+
CacheItemResult,
|
|
14
|
+
CacheItemOptions,
|
|
15
|
+
SegmentHandleData,
|
|
16
|
+
} from "./types.js";
|
|
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";
|
|
24
|
+
|
|
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
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Options for MemorySegmentCacheStore
|
|
66
|
+
*/
|
|
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
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Default cache options for cache() boundaries.
|
|
87
|
+
* When cache() is called without explicit ttl/swr,
|
|
88
|
+
* these defaults are used.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* const store = new MemorySegmentCacheStore({
|
|
93
|
+
* defaults: { ttl: 60, swr: 300 }
|
|
94
|
+
* });
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
defaults?: CacheDefaults;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Custom key generator applied to all cache operations.
|
|
101
|
+
* Receives the full RequestContext and the default-generated key.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* keyGenerator: (ctx, defaultKey) => {
|
|
106
|
+
* const locale = cookies().get('locale')?.value || 'en';
|
|
107
|
+
* return `${locale}:${defaultKey}`;
|
|
108
|
+
* }
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
keyGenerator?: (
|
|
112
|
+
ctx: RequestContext<TEnv>,
|
|
113
|
+
defaultKey: string,
|
|
114
|
+
) => string | Promise<string>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* In-memory segment cache store.
|
|
119
|
+
*
|
|
120
|
+
* Suitable for development and single-instance deployments.
|
|
121
|
+
* For production with multiple instances, use a distributed store
|
|
122
|
+
* like Cloudflare KV or Redis.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```typescript
|
|
126
|
+
* // Basic usage
|
|
127
|
+
* const store = new MemorySegmentCacheStore();
|
|
128
|
+
*
|
|
129
|
+
* // With defaults for cache() boundaries
|
|
130
|
+
* const store = new MemorySegmentCacheStore({
|
|
131
|
+
* defaults: { ttl: 60 }
|
|
132
|
+
* });
|
|
133
|
+
*
|
|
134
|
+
* createRSCHandler({
|
|
135
|
+
* router,
|
|
136
|
+
* cache: { store }
|
|
137
|
+
* })
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export class MemorySegmentCacheStore<
|
|
141
|
+
TEnv = unknown,
|
|
142
|
+
> implements SegmentCacheStore<TEnv> {
|
|
143
|
+
private cache: Map<string, CachedEntryData>;
|
|
144
|
+
private responseCache: Map<string, CachedResponseEntry>;
|
|
145
|
+
private itemCache: Map<string, CachedItemEntry>;
|
|
146
|
+
readonly defaults?: CacheDefaults;
|
|
147
|
+
readonly keyGenerator?: (
|
|
148
|
+
ctx: RequestContext<TEnv>,
|
|
149
|
+
defaultKey: string,
|
|
150
|
+
) => string | Promise<string>;
|
|
151
|
+
|
|
152
|
+
constructor(options?: MemorySegmentCacheStoreOptions<TEnv>) {
|
|
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
|
+
}
|
|
174
|
+
this.defaults = options?.defaults;
|
|
175
|
+
this.keyGenerator = options?.keyGenerator;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async get(key: string): Promise<CacheGetResult | null> {
|
|
179
|
+
const cached = this.cache.get(key);
|
|
180
|
+
|
|
181
|
+
if (!cached) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Check expiration
|
|
186
|
+
if (Date.now() > cached.expiresAt) {
|
|
187
|
+
this.cache.delete(key);
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Memory store doesn't support SWR - never triggers revalidation
|
|
192
|
+
return { data: cached, shouldRevalidate: false };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async set(
|
|
196
|
+
key: string,
|
|
197
|
+
data: CachedEntryData,
|
|
198
|
+
ttl: number,
|
|
199
|
+
_swr?: number,
|
|
200
|
+
): Promise<void> {
|
|
201
|
+
// Note: Memory store doesn't implement SWR - entries just expire at TTL
|
|
202
|
+
// For SWR support, use CFCacheStore or similar distributed cache
|
|
203
|
+
const entry: CachedEntryData = {
|
|
204
|
+
...data,
|
|
205
|
+
expiresAt: Date.now() + ttl * 1000,
|
|
206
|
+
};
|
|
207
|
+
this.cache.set(key, entry);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async delete(key: string): Promise<boolean> {
|
|
211
|
+
return this.cache.delete(key);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async clear(): Promise<void> {
|
|
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
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Get cache statistics for debugging purposes.
|
|
302
|
+
* @internal
|
|
303
|
+
*/
|
|
304
|
+
getStats(): { size: number; keys: string[] } {
|
|
305
|
+
return {
|
|
306
|
+
size: this.cache.size,
|
|
307
|
+
keys: Array.from(this.cache.keys()),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Reset the global cache registry.
|
|
313
|
+
* Useful for test isolation - call this in beforeEach to ensure
|
|
314
|
+
* tests don't share cache state via globalThis.
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```typescript
|
|
318
|
+
* beforeEach(() => {
|
|
319
|
+
* MemorySegmentCacheStore.resetGlobalCache();
|
|
320
|
+
* });
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
static resetGlobalCache(): void {
|
|
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];
|
|
327
|
+
}
|
|
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
|
+
}
|