@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
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cache-guide
|
|
3
|
+
description: When to use cache() DSL vs "use cache" directive — key differences and decision guide
|
|
4
|
+
argument-hint:
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# cache() vs "use cache" — When to Use Which
|
|
8
|
+
|
|
9
|
+
Both mechanisms share the same backing store, cache profiles, and tag-based
|
|
10
|
+
invalidation. They differ in scope, cache key, execution model, and runtime control.
|
|
11
|
+
|
|
12
|
+
## Key Differences
|
|
13
|
+
|
|
14
|
+
| | `cache()` DSL | `"use cache"` directive |
|
|
15
|
+
| -------------------- | ----------------------------------------------------- | -------------------------------------------------- |
|
|
16
|
+
| **Scope** | Route segment tree (handler + children + parallels) | Single function return value |
|
|
17
|
+
| **Defined at** | Route definition site (`urls.ts`) | Inside function body or at file top |
|
|
18
|
+
| **Cache key** | Request type + pathname + params (+ optional custom) | Function identity + serialized non-tainted args |
|
|
19
|
+
| **Execution on hit** | All-or-nothing: entire handler skipped | Partial: function body skipped, calling code runs |
|
|
20
|
+
| **Runtime control** | `condition` to disable, custom `key` function | None — if the directive is present, it caches |
|
|
21
|
+
| **Side effects** | No guards needed — handler doesn't run on hit | `ctx.header()`, `ctx.set()`, etc. throw at runtime |
|
|
22
|
+
| **Handle data** | Captured and replayed | Captured and replayed |
|
|
23
|
+
| **Loaders** | Always fresh — excluded from cache, opt-in per loader | Can be used inside loaders |
|
|
24
|
+
| **Nesting** | Nest `cache()` boundaries with different TTLs | Compose by calling cached functions from uncached |
|
|
25
|
+
|
|
26
|
+
### cache() Cache Key
|
|
27
|
+
|
|
28
|
+
The key is `{requestType}:{pathname}:{params}` where requestType is one of
|
|
29
|
+
`doc:`, `partial:`, or `intercept:`. This means the same URL cached separately
|
|
30
|
+
for full document loads, client navigations, and intercept navigations.
|
|
31
|
+
|
|
32
|
+
Custom `key` functions can segment the cache further (e.g., by user role or locale).
|
|
33
|
+
`condition` can disable caching entirely at runtime (e.g., skip for authenticated users).
|
|
34
|
+
|
|
35
|
+
### "use cache" Cache Key
|
|
36
|
+
|
|
37
|
+
The key is `use-cache:{functionId}:{serializedArgs}` where functionId is a stable
|
|
38
|
+
ID from the Vite transform (module path + export name) and args are serialized via
|
|
39
|
+
RSC `encodeReply()`. Tainted arguments (ctx, env, req) are excluded.
|
|
40
|
+
|
|
41
|
+
## Execution Model
|
|
42
|
+
|
|
43
|
+
This is the most important distinction.
|
|
44
|
+
|
|
45
|
+
### cache() — all-or-nothing
|
|
46
|
+
|
|
47
|
+
On cache hit, the cache-lookup middleware short-circuits the entire pipeline.
|
|
48
|
+
No handler code runs. On miss, all handlers execute normally and segments are
|
|
49
|
+
stored.
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
HIT → cached segments served, loaders resolved fresh, no handler runs
|
|
53
|
+
MISS → all handlers run, segments cached, response built normally
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Headers, cookies, and ctx.set() calls inside handlers naturally don't execute on
|
|
57
|
+
hit. There is no partial execution, so no runtime guards are needed.
|
|
58
|
+
|
|
59
|
+
### "use cache" — partial execution
|
|
60
|
+
|
|
61
|
+
Only the wrapped function body is skipped on hit. The code that calls the
|
|
62
|
+
cached function still runs. This means ctx side effects inside the cached body
|
|
63
|
+
would silently disappear on hit.
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
HIT → function body skipped, calling code runs, handle data replayed
|
|
67
|
+
MISS → function body runs, return value + handle data cached
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Runtime guards throw if you call cookies(), headers(), ctx.header(), ctx.set(),
|
|
71
|
+
ctx.onResponse(), ctx.setTheme(), or ctx.setLocationState() inside a "use cache"
|
|
72
|
+
function. cookies() and headers() are blocked because per-request data is not in the
|
|
73
|
+
cache key. Side-effect methods are blocked because their effects are lost on hit.
|
|
74
|
+
Use ctx.use(Handle) instead for data — handle data is captured and replayed.
|
|
75
|
+
|
|
76
|
+
## When to Use cache()
|
|
77
|
+
|
|
78
|
+
Use the route-level `cache()` DSL when:
|
|
79
|
+
|
|
80
|
+
- **Caching entire routes or sections** — wrap a set of paths with one TTL.
|
|
81
|
+
- **You need runtime control** — disable caching for authenticated users with
|
|
82
|
+
`condition`, or segment cache keys by user/locale with `key`.
|
|
83
|
+
- **UI rendering is expensive** — the cached segments include the rendered
|
|
84
|
+
component tree, skipping RSC rendering on hit.
|
|
85
|
+
- **You want one cache entry per URL** — keyed on pathname + params, not on
|
|
86
|
+
function arguments.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
export const urlpatterns = urls(({ path, cache }) => [
|
|
90
|
+
cache({ ttl: 300, condition: (ctx) => !ctx.get("user") }, () => [
|
|
91
|
+
path("/blog", BlogIndex, { name: "blog" }),
|
|
92
|
+
path("/blog/:slug", BlogPost, { name: "blogPost" }),
|
|
93
|
+
]),
|
|
94
|
+
]);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## When to Use "use cache"
|
|
98
|
+
|
|
99
|
+
Use the `"use cache"` directive when:
|
|
100
|
+
|
|
101
|
+
- **Caching a specific data fetch** — one database query used across multiple
|
|
102
|
+
routes or components.
|
|
103
|
+
- **Different call sites need different cache entries** — the cache key includes
|
|
104
|
+
all non-tainted arguments, so `getProduct("a")` and `getProduct("b")` cache
|
|
105
|
+
separately.
|
|
106
|
+
- **Fine-grained caching within a handler** — cache the expensive part, keep
|
|
107
|
+
ctx side effects outside.
|
|
108
|
+
- **Caching an RSC component** — a component that fetches its own data can cache
|
|
109
|
+
its entire render.
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
async function getProductData(slug: string) {
|
|
113
|
+
"use cache: short";
|
|
114
|
+
return await db.query("SELECT * FROM products WHERE slug = ?", [slug]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Handler calls cached function, sets headers outside
|
|
118
|
+
async function ProductPage(ctx) {
|
|
119
|
+
const data = await getProductData(ctx.params.slug);
|
|
120
|
+
ctx.header("X-Product", data.id);
|
|
121
|
+
return <Product data={data} />;
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Combining Both
|
|
126
|
+
|
|
127
|
+
They compose naturally. Use `cache()` for the route boundary and `"use cache"`
|
|
128
|
+
for shared data functions:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// urls.tsx — route-level cache for the rendered segment tree
|
|
132
|
+
cache({ ttl: 60 }, () => [
|
|
133
|
+
path("/product/:slug", ProductPage, { name: "product" }),
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
// data.ts — function-level cache for the database query
|
|
137
|
+
export async function getProductData(slug: string) {
|
|
138
|
+
"use cache: long";
|
|
139
|
+
return await db.query("SELECT * FROM products WHERE slug = ?", [slug]);
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
On cache hit for the route, the handler doesn't run and `getProductData` is never
|
|
144
|
+
called. On cache miss, the handler runs and `getProductData` may itself return a
|
|
145
|
+
cached value from a previous call with the same slug.
|
|
146
|
+
|
|
147
|
+
## Headers and Cookies
|
|
148
|
+
|
|
149
|
+
Neither mechanism caches response headers or cookies.
|
|
150
|
+
|
|
151
|
+
- **cache()**: Headers set by handlers are naturally absent on hit because no
|
|
152
|
+
handler runs. If you need headers on every response, set them in middleware
|
|
153
|
+
(which runs before cache lookup).
|
|
154
|
+
- **"use cache"**: cookies() and headers() throw inside the cached function
|
|
155
|
+
(both reads and writes). ctx.header() also throws. Move them outside.
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// Set headers that must appear on every response in middleware
|
|
159
|
+
middleware(async (ctx, next) => {
|
|
160
|
+
ctx.header("X-Frame-Options", "DENY");
|
|
161
|
+
await next();
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Context Variable Cache Safety
|
|
166
|
+
|
|
167
|
+
Context variables created with `createVar()` are cacheable by default and can
|
|
168
|
+
be read freely inside `cache()` and `"use cache"` scopes. Non-cacheable vars
|
|
169
|
+
throw at read time to prevent request-specific data from being captured.
|
|
170
|
+
|
|
171
|
+
There are two ways to mark a value as non-cacheable:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
// Var-level policy — inherently request-specific data
|
|
175
|
+
const Session = createVar<SessionData>({ cache: false });
|
|
176
|
+
|
|
177
|
+
// Write-level escalation — this specific write is non-cacheable
|
|
178
|
+
ctx.set(Theme, derivedTheme, { cache: false });
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
"Least cacheable wins": if either the var definition or the `ctx.set()` call
|
|
182
|
+
specifies `cache: false`, the value is non-cacheable.
|
|
183
|
+
|
|
184
|
+
**Behavior inside cache scopes:**
|
|
185
|
+
|
|
186
|
+
| Operation | Inside `cache()` / `"use cache"` |
|
|
187
|
+
| ----------------------------------- | -------------------------------- |
|
|
188
|
+
| `ctx.get(cacheableVar)` | Allowed |
|
|
189
|
+
| `ctx.get(nonCacheableVar)` | Throws |
|
|
190
|
+
| `ctx.set(var, value)` (cacheable) | Allowed |
|
|
191
|
+
| `ctx.header()`, `ctx.cookie()`, etc | Throws (response side effects) |
|
|
192
|
+
|
|
193
|
+
Write is dumb — `ctx.set()` stores the cache metadata but does not enforce.
|
|
194
|
+
Enforcement happens at read time (`ctx.get()`), where ALS detects the cache
|
|
195
|
+
scope and rejects non-cacheable reads.
|
|
196
|
+
|
|
197
|
+
## Loaders Are Always Fresh
|
|
198
|
+
|
|
199
|
+
Loaders are **never cached** by route-level `cache()`. Even on a full cache hit
|
|
200
|
+
where all UI segments are served from cache, loaders are re-resolved fresh on
|
|
201
|
+
every request. This is enforced at two levels:
|
|
202
|
+
|
|
203
|
+
1. **Storage**: `cacheRoute()` filters out loader segments before serialization
|
|
204
|
+
(`segments.filter(s => s.type !== "loader")`).
|
|
205
|
+
2. **Retrieval**: On cache hit, `resolveLoadersOnly()` runs after yielding cached
|
|
206
|
+
UI segments, ensuring fresh data regardless of cache state.
|
|
207
|
+
|
|
208
|
+
This means `cache()` gives you cached UI + fresh data by default. To also cache
|
|
209
|
+
a loader's data, explicitly opt in with `loader(Fn, () => [cache({...})])`.
|
|
210
|
+
|
|
211
|
+
## cache() Placement Patterns
|
|
212
|
+
|
|
213
|
+
### Wrapping children of a path
|
|
214
|
+
|
|
215
|
+
An orphan `cache()` inside a path's children becomes the parent for all
|
|
216
|
+
subsequent siblings. Everything below the cache boundary is cached as one unit:
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
path("/dashboard", DashboardPage, { name: "dashboard" }, () => [
|
|
220
|
+
cache("long"),
|
|
221
|
+
layout(DashboardSidebar, () => [
|
|
222
|
+
parallel("@stats", StatsPanel),
|
|
223
|
+
parallel("@activity", ActivityFeed),
|
|
224
|
+
]),
|
|
225
|
+
]),
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
On hit: DashboardPage, DashboardSidebar, StatsPanel, and ActivityFeed are all
|
|
229
|
+
served from cache. On miss: all handlers run, all segments cached together.
|
|
230
|
+
|
|
231
|
+
### Uncached layout with cached children
|
|
232
|
+
|
|
233
|
+
The cache boundary only covers what's inside it. Parent segments above the
|
|
234
|
+
boundary are not cached and always re-render:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
layout(RootLayout, () => [
|
|
238
|
+
// RootLayout is NOT cached — runs every request
|
|
239
|
+
path("/products/:slug", ProductPage, { name: "product" }, () => [
|
|
240
|
+
cache("long"),
|
|
241
|
+
layout(ProductSidebar),
|
|
242
|
+
parallel("@reviews", ReviewsPanel),
|
|
243
|
+
parallel("@related", RelatedProducts),
|
|
244
|
+
]),
|
|
245
|
+
]),
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
RootLayout renders fresh every request. ProductPage, ProductSidebar,
|
|
249
|
+
ReviewsPanel, and RelatedProducts are all inside the cache boundary and
|
|
250
|
+
served from cache on hit. This is useful when the root layout depends on
|
|
251
|
+
request-specific data (user session, theme) but the product content is
|
|
252
|
+
cacheable.
|
|
253
|
+
|
|
254
|
+
### Loader-level caching
|
|
255
|
+
|
|
256
|
+
Loaders are excluded from route-level `cache()` by default — they always
|
|
257
|
+
resolve fresh. To opt a specific loader into caching, give it its own
|
|
258
|
+
`cache()` child:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
path("/product/:slug", ProductPage, { name: "product" }, () => [
|
|
262
|
+
// This loader is cached for 5 minutes
|
|
263
|
+
loader(ProductLoader, () => [cache({ ttl: 300 })]),
|
|
264
|
+
|
|
265
|
+
// This loader is always fresh
|
|
266
|
+
loader(CartLoader),
|
|
267
|
+
]),
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
This attaches the cache config directly to the loader entry. The loader's
|
|
271
|
+
data is cached independently from the route's segment cache. Loader caching
|
|
272
|
+
supports custom keys, tags, SWR, conditional bypass, and per-loader store
|
|
273
|
+
overrides — see `/loader` for the full reference.
|
|
274
|
+
|
|
275
|
+
## Decision Flowchart
|
|
276
|
+
|
|
277
|
+
1. Do you want to cache an entire route or group of routes?
|
|
278
|
+
**Yes** -> `cache()`
|
|
279
|
+
2. Do you need runtime conditions (skip for auth users, key by locale)?
|
|
280
|
+
**Yes** -> `cache()` with `condition` / `key`
|
|
281
|
+
3. Do you want to cache a data fetch shared across routes?
|
|
282
|
+
**Yes** -> `"use cache"`
|
|
283
|
+
4. Do you need different cache entries for different arguments?
|
|
284
|
+
**Yes** -> `"use cache"` (keyed by args)
|
|
285
|
+
5. Is the expensive part rendering, not data fetching?
|
|
286
|
+
**Yes** -> `cache()` (caches rendered segments)
|
|
287
|
+
6. Is the expensive part a single query inside a larger handler?
|
|
288
|
+
**Yes** -> `"use cache"` on the query function
|
|
289
|
+
|
|
290
|
+
## See Also
|
|
291
|
+
|
|
292
|
+
- `/caching` — cache() DSL setup, stores, nested boundaries
|
|
293
|
+
- `/use-cache` — "use cache" directive details, profiles, transforms, guards
|
|
294
|
+
- `/document-cache` — Edge caching with Cache-Control headers (different layer)
|
package/skills/caching/SKILL.md
CHANGED
|
@@ -30,14 +30,45 @@ export const urlpatterns = urls(({ path, cache }) => [
|
|
|
30
30
|
## Cache Options
|
|
31
31
|
|
|
32
32
|
```typescript
|
|
33
|
-
cache(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
cache(
|
|
34
|
+
{
|
|
35
|
+
ttl: 60, // Time-to-live in seconds (default: 60)
|
|
36
|
+
swr: 300, // Stale-while-revalidate window (default: 300)
|
|
37
|
+
},
|
|
38
|
+
() => [
|
|
39
|
+
// Cached routes
|
|
40
|
+
],
|
|
41
|
+
);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Named Profile Shorthand
|
|
45
|
+
|
|
46
|
+
Use a named cache profile string instead of an options object. The profile must be
|
|
47
|
+
defined in `createRouter({ cacheProfiles })`. Unknown names throw at boot time.
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// Define profiles in router
|
|
51
|
+
createRouter({
|
|
52
|
+
cacheProfiles: {
|
|
53
|
+
default: { ttl: 900, swr: 1800 },
|
|
54
|
+
short: { ttl: 60, swr: 120 },
|
|
55
|
+
long: { ttl: 3600, swr: 7200 },
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Use by name in urls
|
|
60
|
+
export const urlpatterns = urls(({ path, cache }) => [
|
|
61
|
+
cache("long", () => [path("/blog", BlogIndex, { name: "blog" })]),
|
|
62
|
+
|
|
63
|
+
// Also works without children (orphan cache boundary)
|
|
64
|
+
cache("short"),
|
|
65
|
+
path("/feed", FeedPage, { name: "feed" }),
|
|
66
|
+
]);
|
|
39
67
|
```
|
|
40
68
|
|
|
69
|
+
These profile names are shared with the `"use cache: <name>"` directive. See
|
|
70
|
+
`/use-cache` for function-level caching.
|
|
71
|
+
|
|
41
72
|
## Loader-Level Caching
|
|
42
73
|
|
|
43
74
|
Cache individual loaders:
|
|
@@ -45,13 +76,11 @@ Cache individual loaders:
|
|
|
45
76
|
```typescript
|
|
46
77
|
path("/product/:slug", ProductPage, { name: "product" }, () => [
|
|
47
78
|
// Cache this loader's results
|
|
48
|
-
loader(ProductLoader, () => [
|
|
49
|
-
cache({ ttl: 300 }),
|
|
50
|
-
]),
|
|
79
|
+
loader(ProductLoader, () => [cache({ ttl: 300 })]),
|
|
51
80
|
|
|
52
81
|
// This loader is not cached
|
|
53
82
|
loader(CartLoader),
|
|
54
|
-
])
|
|
83
|
+
]);
|
|
55
84
|
```
|
|
56
85
|
|
|
57
86
|
## Global Cache Configuration
|
|
@@ -60,7 +89,7 @@ Configure a cache store in the router:
|
|
|
60
89
|
|
|
61
90
|
```typescript
|
|
62
91
|
import { createRouter } from "@rangojs/router";
|
|
63
|
-
import { MemorySegmentCacheStore } from "@rangojs/router/
|
|
92
|
+
import { MemorySegmentCacheStore } from "@rangojs/router/cache";
|
|
64
93
|
|
|
65
94
|
const store = new MemorySegmentCacheStore({
|
|
66
95
|
defaults: { ttl: 60, swr: 300 },
|
|
@@ -83,34 +112,75 @@ const router = createRouter({
|
|
|
83
112
|
For single-instance deployments:
|
|
84
113
|
|
|
85
114
|
```typescript
|
|
86
|
-
import { MemorySegmentCacheStore } from "@rangojs/router/
|
|
115
|
+
import { MemorySegmentCacheStore } from "@rangojs/router/cache";
|
|
87
116
|
|
|
88
117
|
const store = new MemorySegmentCacheStore({
|
|
89
118
|
defaults: { ttl: 60, swr: 300 },
|
|
90
|
-
maxSize: 1000,
|
|
119
|
+
maxSize: 1000, // Max entries
|
|
91
120
|
});
|
|
92
121
|
```
|
|
93
122
|
|
|
94
|
-
### Cloudflare
|
|
123
|
+
### Cloudflare Edge Cache Store
|
|
95
124
|
|
|
96
|
-
For distributed caching on Cloudflare Workers:
|
|
125
|
+
For distributed caching on Cloudflare Workers using the Cache API:
|
|
97
126
|
|
|
98
127
|
```typescript
|
|
99
|
-
import { CFCacheStore } from "@rangojs/router/cache
|
|
128
|
+
import { CFCacheStore } from "@rangojs/router/cache";
|
|
100
129
|
|
|
101
|
-
const router = createRouter({
|
|
130
|
+
const router = createRouter<AppBindings>({
|
|
131
|
+
document: Document,
|
|
132
|
+
urls: urlpatterns,
|
|
133
|
+
cache: (env, ctx) => ({
|
|
134
|
+
store: new CFCacheStore({
|
|
135
|
+
ctx,
|
|
136
|
+
defaults: { ttl: 60, swr: 300 },
|
|
137
|
+
}),
|
|
138
|
+
enabled: true,
|
|
139
|
+
}),
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### With KV L2 Persistence
|
|
144
|
+
|
|
145
|
+
Add a KV namespace for global cross-colo persistence. On Cache API miss, KV is
|
|
146
|
+
checked and hits are promoted back to L1. Writes go to both layers.
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import { CFCacheStore } from "@rangojs/router/cache";
|
|
150
|
+
|
|
151
|
+
const router = createRouter<AppBindings>({
|
|
102
152
|
document: Document,
|
|
103
153
|
urls: urlpatterns,
|
|
104
|
-
cache: (env) => ({
|
|
154
|
+
cache: (env, ctx) => ({
|
|
105
155
|
store: new CFCacheStore({
|
|
106
|
-
|
|
107
|
-
|
|
156
|
+
ctx,
|
|
157
|
+
kv: env.CACHE_KV, // optional KV namespace binding
|
|
158
|
+
defaults: { ttl: 60, swr: 300 },
|
|
108
159
|
}),
|
|
109
160
|
enabled: true,
|
|
110
161
|
}),
|
|
111
162
|
});
|
|
112
163
|
```
|
|
113
164
|
|
|
165
|
+
**How the two layers work:**
|
|
166
|
+
|
|
167
|
+
| Scenario | L1 (Cache API) | L2 (KV) | Result |
|
|
168
|
+
| ------------ | -------------- | ------- | ----------------------------- |
|
|
169
|
+
| Hot request | HIT | — | Serve from L1 (fast) |
|
|
170
|
+
| Cold colo | MISS | HIT | Serve from KV, promote to L1 |
|
|
171
|
+
| First render | MISS | MISS | Render, write to both L1 + KV |
|
|
172
|
+
|
|
173
|
+
KV entries require `expirationTtl >= 60s`. Short-lived entries (< 60s total TTL)
|
|
174
|
+
are only cached in L1.
|
|
175
|
+
|
|
176
|
+
## Context Variables Inside Cache Boundaries
|
|
177
|
+
|
|
178
|
+
Context variables (`createVar`) are cacheable by default and can be read and
|
|
179
|
+
written inside `cache()` scopes. Variables marked with `{ cache: false }` (at
|
|
180
|
+
the var level or write level) throw when read inside a cache scope. Response
|
|
181
|
+
side effects (`ctx.header()`, `ctx.cookie()`) always throw inside cache
|
|
182
|
+
boundaries. See `/cache-guide` for the full cache safety table.
|
|
183
|
+
|
|
114
184
|
## Nested Cache Boundaries
|
|
115
185
|
|
|
116
186
|
Override cache settings for specific sections:
|
|
@@ -124,7 +194,7 @@ cache({ ttl: 300 }, () => [
|
|
|
124
194
|
cache({ ttl: 30 }, () => [
|
|
125
195
|
path("/blog/:slug", BlogPost, { name: "blogPost" }),
|
|
126
196
|
]),
|
|
127
|
-
])
|
|
197
|
+
]);
|
|
128
198
|
```
|
|
129
199
|
|
|
130
200
|
## Custom Cache Store
|
|
@@ -139,14 +209,14 @@ const checkoutCache = new MemorySegmentCacheStore({
|
|
|
139
209
|
// In urls
|
|
140
210
|
cache({ store: checkoutCache }, () => [
|
|
141
211
|
path("/checkout", CheckoutPage, { name: "checkout" }),
|
|
142
|
-
])
|
|
212
|
+
]);
|
|
143
213
|
```
|
|
144
214
|
|
|
145
215
|
## Complete Example
|
|
146
216
|
|
|
147
217
|
```typescript
|
|
148
218
|
import { urls } from "@rangojs/router";
|
|
149
|
-
import { MemorySegmentCacheStore } from "@rangojs/router/
|
|
219
|
+
import { MemorySegmentCacheStore } from "@rangojs/router/cache";
|
|
150
220
|
|
|
151
221
|
// Custom store for checkout (short TTL)
|
|
152
222
|
const checkoutCache = new MemorySegmentCacheStore({
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: composability
|
|
3
|
+
description: Reusable composition patterns with globally importable route helpers in @rangojs/router
|
|
4
|
+
argument-hint: "pattern-name"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Composability
|
|
8
|
+
|
|
9
|
+
Route helpers can be imported directly from `@rangojs/router` and used to build reusable composition factories. This enables sharing common route configurations across multiple routes and modules.
|
|
10
|
+
|
|
11
|
+
## Globally Importable Helpers
|
|
12
|
+
|
|
13
|
+
These helpers can be imported and called outside the `urls()` callback parameter:
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import {
|
|
17
|
+
layout,
|
|
18
|
+
cache,
|
|
19
|
+
middleware,
|
|
20
|
+
revalidate,
|
|
21
|
+
loader,
|
|
22
|
+
loading,
|
|
23
|
+
parallel,
|
|
24
|
+
intercept,
|
|
25
|
+
when,
|
|
26
|
+
errorBoundary,
|
|
27
|
+
notFoundBoundary,
|
|
28
|
+
} from "@rangojs/router";
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
They work because they use AsyncLocalStorage internally and resolve context at call time, not import time.
|
|
32
|
+
|
|
33
|
+
## Why path() and include() Are Not Global
|
|
34
|
+
|
|
35
|
+
`path()` and `include()` remain exclusive to the `urls()` callback:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
urls(({ path, include }) => [
|
|
39
|
+
path("/blog", BlogPage, { name: "blog" }),
|
|
40
|
+
include("/shop", shopPatterns, { name: "shop" }),
|
|
41
|
+
]);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
They define the route structure -- the URL patterns and how modules compose. Keeping them in the `urls()` callback makes the route tree readable at a glance. When scanning a URL file, `path()` and `include()` calls show what renders where. Moving them into factories would hide the routing structure and make it harder to understand which URLs exist and how they nest.
|
|
45
|
+
|
|
46
|
+
The globally importable helpers (`cache`, `middleware`, `loading`, etc.) are configuration -- they modify behavior of routes but don't define routes themselves. Extracting them into factories doesn't obscure the route structure.
|
|
47
|
+
|
|
48
|
+
## Composition Factories
|
|
49
|
+
|
|
50
|
+
Define reusable factories that return arrays of use items:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { cache, revalidate, loading, errorBoundary, middleware } from "@rangojs/router";
|
|
54
|
+
|
|
55
|
+
// Shared caching configuration
|
|
56
|
+
const withCaching = () => [
|
|
57
|
+
cache({ ttl: 600_000 }),
|
|
58
|
+
revalidate(({ actionId }) => !!actionId),
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
// Shared loading and error handling
|
|
62
|
+
const withLoadingAndError = (skeleton: ReactNode) => [
|
|
63
|
+
loading(skeleton),
|
|
64
|
+
errorBoundary(() => <div>Something went wrong</div>),
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
// Shared auth middleware
|
|
68
|
+
const withAuth = () => [
|
|
69
|
+
middleware(authMiddleware),
|
|
70
|
+
middleware(loggingMiddleware),
|
|
71
|
+
];
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Using Factories in Routes
|
|
75
|
+
|
|
76
|
+
Place factory calls inside `path()` or `layout()` use callbacks. The returned arrays are flattened automatically (up to 3 levels):
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { urls } from "@rangojs/router";
|
|
80
|
+
import { withCaching, withLoadingAndError, withAuth } from "./route-config";
|
|
81
|
+
|
|
82
|
+
export const urlpatterns = urls(({ path, layout }) => [
|
|
83
|
+
layout(<AppLayout />, () => [
|
|
84
|
+
withAuth(),
|
|
85
|
+
|
|
86
|
+
path("/blog", BlogIndex, { name: "blog" }, () => [
|
|
87
|
+
withCaching(),
|
|
88
|
+
withLoadingAndError(<BlogSkeleton />),
|
|
89
|
+
]),
|
|
90
|
+
|
|
91
|
+
path("/shop", ShopIndex, { name: "shop" }, () => [
|
|
92
|
+
withCaching(),
|
|
93
|
+
withLoadingAndError(<ShopSkeleton />),
|
|
94
|
+
]),
|
|
95
|
+
]),
|
|
96
|
+
]);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Sharing Across Modules
|
|
100
|
+
|
|
101
|
+
Factories can be defined in shared modules and reused across separate `urls()` definitions:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// src/route-config.ts
|
|
105
|
+
import { cache, revalidate, middleware } from "@rangojs/router";
|
|
106
|
+
import { authMiddleware } from "./middleware/auth";
|
|
107
|
+
|
|
108
|
+
export const withPublicDefaults = () => [
|
|
109
|
+
cache({ ttl: 300 }),
|
|
110
|
+
revalidate(({ actionId }) => !!actionId),
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
export const withProtectedDefaults = () => [
|
|
114
|
+
middleware(authMiddleware),
|
|
115
|
+
cache({ ttl: 60 }),
|
|
116
|
+
];
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// src/urls/blog.ts
|
|
121
|
+
import { urls } from "@rangojs/router";
|
|
122
|
+
import { withPublicDefaults } from "../route-config";
|
|
123
|
+
|
|
124
|
+
export const blogPatterns = urls(({ path }) => [
|
|
125
|
+
path("/", BlogIndex, { name: "index" }, () => [withPublicDefaults()]),
|
|
126
|
+
]);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// src/urls/admin.ts
|
|
131
|
+
import { urls } from "@rangojs/router";
|
|
132
|
+
import { withProtectedDefaults } from "../route-config";
|
|
133
|
+
|
|
134
|
+
export const adminPatterns = urls(({ path }) => [
|
|
135
|
+
path("/", AdminDashboard, { name: "index" }, () => [withProtectedDefaults()]),
|
|
136
|
+
]);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Composition Types
|
|
140
|
+
|
|
141
|
+
For typed factories, import the composition types:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import type { RouteUseItem, LayoutUseItem, UseItems } from "@rangojs/router";
|
|
145
|
+
|
|
146
|
+
// Factory for path() use callbacks
|
|
147
|
+
const withCaching = (): RouteUseItem[] => [
|
|
148
|
+
cache({ ttl: 600_000 }),
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
// Factory for layout() use callbacks
|
|
152
|
+
const withAuth = (): LayoutUseItem[] => [
|
|
153
|
+
middleware(authMiddleware),
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
// Factory that nests other factories (use UseItems for nested arrays)
|
|
157
|
+
const withEverything = (): UseItems<RouteUseItem> => [
|
|
158
|
+
withCaching(),
|
|
159
|
+
loading(<Skeleton />),
|
|
160
|
+
];
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
- `RouteUseItem[]` -- flat array for `path()` use callbacks
|
|
164
|
+
- `LayoutUseItem[]` -- flat array for `layout()` use callbacks
|
|
165
|
+
- `UseItems<T>` -- allows nested arrays from composing factories together
|
|
166
|
+
|
|
167
|
+
## Rules
|
|
168
|
+
|
|
169
|
+
- Helpers execute lazily -- factory functions are defined anywhere, but only called inside a `urls()` context (within `path()` or `layout()` use callbacks)
|
|
170
|
+
- Calling helpers outside a `urls()` context throws an error
|
|
171
|
+
- Nested arrays from factories are flattened automatically via `.flat(3)`
|
|
172
|
+
- `path()` and `include()` cannot be used in factories -- they define route structure and must remain visible in the `urls()` callback
|