@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
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import type { RequestController, DisposableAbortController } from "./types.js";
|
|
2
|
-
|
|
3
|
-
// Polyfill Symbol.dispose for Safari and older browsers
|
|
4
|
-
if (typeof Symbol.dispose === "undefined") {
|
|
5
|
-
(Symbol as any).dispose = Symbol("Symbol.dispose");
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Create a request controller for managing concurrent abort controllers
|
|
10
|
-
*
|
|
11
|
-
* This utility helps manage concurrent navigation requests by providing
|
|
12
|
-
* a way to abort all pending requests when a new navigation starts.
|
|
13
|
-
*
|
|
14
|
-
* @returns RequestController instance
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```typescript
|
|
18
|
-
* const controller = createRequestController();
|
|
19
|
-
*
|
|
20
|
-
* // Start a new request
|
|
21
|
-
* const abortController = controller.create();
|
|
22
|
-
* fetch(url, { signal: abortController.signal });
|
|
23
|
-
*
|
|
24
|
-
* // Abort all pending requests (e.g., when starting new navigation)
|
|
25
|
-
* controller.abortAll();
|
|
26
|
-
*
|
|
27
|
-
* // Clean up completed request
|
|
28
|
-
* controller.remove(abortController);
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
export function createRequestController(): RequestController {
|
|
32
|
-
// Navigation controllers - aborted on new navigation
|
|
33
|
-
// Using WeakRef to allow GC if controller is no longer referenced elsewhere
|
|
34
|
-
const controllers: WeakRef<AbortController>[] = [];
|
|
35
|
-
// Action controllers - NOT aborted by navigation, only by errors
|
|
36
|
-
const actionControllers: WeakRef<AbortController>[] = [];
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Remove stale (garbage collected) refs from an array
|
|
40
|
-
*/
|
|
41
|
-
function pruneStaleRefs(refs: WeakRef<AbortController>[]): void {
|
|
42
|
-
for (let i = refs.length - 1; i >= 0; i--) {
|
|
43
|
-
if (!refs[i].deref()) {
|
|
44
|
-
refs.splice(i, 1);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
/**
|
|
51
|
-
* Create a new abort controller and track it for navigation
|
|
52
|
-
*
|
|
53
|
-
* @returns A new AbortController
|
|
54
|
-
*/
|
|
55
|
-
create(): AbortController {
|
|
56
|
-
const controller = new AbortController();
|
|
57
|
-
controllers.push(new WeakRef(controller));
|
|
58
|
-
console.log(
|
|
59
|
-
`[Browser] Created abort controller, total: ${controllers.length}`,
|
|
60
|
-
);
|
|
61
|
-
return controller;
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Create a disposable abort controller for navigation use with `using` keyword
|
|
66
|
-
*
|
|
67
|
-
* The controller will be automatically removed from tracking when
|
|
68
|
-
* it goes out of scope, regardless of how the scope is exited.
|
|
69
|
-
*
|
|
70
|
-
* @returns A DisposableAbortController
|
|
71
|
-
*
|
|
72
|
-
* @example
|
|
73
|
-
* ```typescript
|
|
74
|
-
* async function handleNavigation() {
|
|
75
|
-
* requestController.abortAll();
|
|
76
|
-
* using { controller } = requestController.createDisposable();
|
|
77
|
-
* // ... use controller.signal ...
|
|
78
|
-
* // controller is automatically removed on scope exit
|
|
79
|
-
* }
|
|
80
|
-
* ```
|
|
81
|
-
*/
|
|
82
|
-
createDisposable(): DisposableAbortController {
|
|
83
|
-
const controller = this.create();
|
|
84
|
-
return {
|
|
85
|
-
controller,
|
|
86
|
-
[Symbol.dispose]: () => {
|
|
87
|
-
this.remove(controller);
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Create a disposable abort controller for actions
|
|
94
|
-
*
|
|
95
|
-
* Action controllers are NOT aborted by navigation - they complete
|
|
96
|
-
* independently. Only aborted by abortAllActions() on error.
|
|
97
|
-
*
|
|
98
|
-
* @returns A DisposableAbortController
|
|
99
|
-
*/
|
|
100
|
-
createActionDisposable(): DisposableAbortController {
|
|
101
|
-
const controller = new AbortController();
|
|
102
|
-
const ref = new WeakRef(controller);
|
|
103
|
-
actionControllers.push(ref);
|
|
104
|
-
console.log(
|
|
105
|
-
`[Browser] Created action controller, total: ${actionControllers.length}`,
|
|
106
|
-
);
|
|
107
|
-
return {
|
|
108
|
-
controller,
|
|
109
|
-
[Symbol.dispose]: () => {
|
|
110
|
-
const index = actionControllers.indexOf(ref);
|
|
111
|
-
if (index !== -1) {
|
|
112
|
-
actionControllers.splice(index, 1);
|
|
113
|
-
console.log(
|
|
114
|
-
`[Browser] Removed action controller, remaining: ${actionControllers.length}`,
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
};
|
|
119
|
-
},
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Abort all navigation controllers (NOT actions)
|
|
123
|
-
*
|
|
124
|
-
* Called when starting new navigation. Actions continue
|
|
125
|
-
* to complete in the background.
|
|
126
|
-
*/
|
|
127
|
-
abortAll(): void {
|
|
128
|
-
controllers.forEach((ref) => ref.deref()?.abort());
|
|
129
|
-
controllers.length = 0;
|
|
130
|
-
console.log(`[Browser] Aborted all navigation controllers`);
|
|
131
|
-
},
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Abort all action controllers
|
|
135
|
-
*
|
|
136
|
-
* Called when an action error occurs - prevents other actions
|
|
137
|
-
* from completing and overwriting the error UI.
|
|
138
|
-
*/
|
|
139
|
-
abortAllActions(): void {
|
|
140
|
-
actionControllers.forEach((ref) => ref.deref()?.abort());
|
|
141
|
-
actionControllers.length = 0;
|
|
142
|
-
console.log(`[Browser] Aborted all action controllers`);
|
|
143
|
-
},
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Remove a specific controller from tracking
|
|
147
|
-
*
|
|
148
|
-
* Call this when a request completes successfully.
|
|
149
|
-
*
|
|
150
|
-
* @param controller - The controller to remove
|
|
151
|
-
*/
|
|
152
|
-
remove(controller: AbortController): void {
|
|
153
|
-
// Prune any stale refs while searching
|
|
154
|
-
pruneStaleRefs(controllers);
|
|
155
|
-
const index = controllers.findIndex((ref) => ref.deref() === controller);
|
|
156
|
-
if (index !== -1) {
|
|
157
|
-
controllers.splice(index, 1);
|
|
158
|
-
console.log(
|
|
159
|
-
`[Browser] Removed abort controller, remaining: ${controllers.length}`,
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
};
|
|
164
|
-
}
|
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* In-Memory Cache Store
|
|
3
|
-
*
|
|
4
|
-
* Simple implementation for development and testing.
|
|
5
|
-
* Not suitable for production (no persistence, single-instance only).
|
|
6
|
-
*
|
|
7
|
-
* @internal This is reserved for future extensibility.
|
|
8
|
-
* For segment caching, use MemorySegmentCacheStore instead.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type {
|
|
12
|
-
CacheStore,
|
|
13
|
-
CacheEntry,
|
|
14
|
-
CacheValue,
|
|
15
|
-
CachePutOptions,
|
|
16
|
-
CacheMetadata,
|
|
17
|
-
CacheValueType,
|
|
18
|
-
} from "./types.js";
|
|
19
|
-
|
|
20
|
-
// ============================================================================
|
|
21
|
-
// Constants
|
|
22
|
-
// ============================================================================
|
|
23
|
-
|
|
24
|
-
/** Default TTL when no explicit value is provided */
|
|
25
|
-
const DEFAULT_TTL_SECONDS = 60;
|
|
26
|
-
|
|
27
|
-
// ============================================================================
|
|
28
|
-
// Types
|
|
29
|
-
// ============================================================================
|
|
30
|
-
|
|
31
|
-
interface StoredEntry {
|
|
32
|
-
/** Stored value (streams/responses converted to ArrayBuffer) */
|
|
33
|
-
value: ArrayBuffer | string | object;
|
|
34
|
-
metadata: CacheMetadata;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* In-memory cache store implementation
|
|
39
|
-
*/
|
|
40
|
-
export class MemoryCacheStore implements CacheStore {
|
|
41
|
-
private cache = new Map<string, StoredEntry>();
|
|
42
|
-
|
|
43
|
-
async match<T = CacheValue>(key: string): Promise<CacheEntry<T> | undefined> {
|
|
44
|
-
const entry = this.cache.get(key);
|
|
45
|
-
|
|
46
|
-
if (!entry) {
|
|
47
|
-
return undefined;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Check expiration
|
|
51
|
-
if (entry.metadata.expiresAt && Date.now() > entry.metadata.expiresAt) {
|
|
52
|
-
this.cache.delete(key);
|
|
53
|
-
return undefined;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Reconstruct value based on original type
|
|
57
|
-
const value = this.reconstructValue(entry);
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
value: value as T,
|
|
61
|
-
metadata: entry.metadata,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async put<T extends CacheValue>(
|
|
66
|
-
key: string,
|
|
67
|
-
value: T,
|
|
68
|
-
options?: CachePutOptions
|
|
69
|
-
): Promise<void> {
|
|
70
|
-
const ttl = options?.ttl ?? DEFAULT_TTL_SECONDS;
|
|
71
|
-
const expiresAt = Date.now() + ttl * 1000;
|
|
72
|
-
|
|
73
|
-
// Detect value type and convert for storage
|
|
74
|
-
const { storedValue, valueType, responseHeaders, responseStatus } =
|
|
75
|
-
await this.prepareForStorage(value);
|
|
76
|
-
|
|
77
|
-
const metadata: CacheMetadata = {
|
|
78
|
-
...options?.metadata,
|
|
79
|
-
expiresAt,
|
|
80
|
-
valueType,
|
|
81
|
-
responseHeaders,
|
|
82
|
-
responseStatus,
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
this.cache.set(key, {
|
|
86
|
-
value: storedValue,
|
|
87
|
-
metadata,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async delete(key: string): Promise<boolean> {
|
|
92
|
-
return this.cache.delete(key);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Clear all entries (useful for testing)
|
|
97
|
-
*/
|
|
98
|
-
clear(): void {
|
|
99
|
-
this.cache.clear();
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Get current cache size (useful for testing/debugging)
|
|
104
|
-
*/
|
|
105
|
-
get size(): number {
|
|
106
|
-
return this.cache.size;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Manually purge expired entries
|
|
111
|
-
*/
|
|
112
|
-
purgeExpired(): number {
|
|
113
|
-
const now = Date.now();
|
|
114
|
-
let purged = 0;
|
|
115
|
-
|
|
116
|
-
for (const [key, entry] of this.cache) {
|
|
117
|
-
if (entry.metadata.expiresAt && now > entry.metadata.expiresAt) {
|
|
118
|
-
this.cache.delete(key);
|
|
119
|
-
purged++;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return purged;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Prepare a value for storage
|
|
128
|
-
* Converts streams and responses to ArrayBuffer, detects type
|
|
129
|
-
*/
|
|
130
|
-
private async prepareForStorage(value: CacheValue): Promise<{
|
|
131
|
-
storedValue: ArrayBuffer | string | object;
|
|
132
|
-
valueType: CacheValueType;
|
|
133
|
-
responseHeaders?: Record<string, string>;
|
|
134
|
-
responseStatus?: number;
|
|
135
|
-
}> {
|
|
136
|
-
// ReadableStream -> ArrayBuffer
|
|
137
|
-
if (value instanceof ReadableStream) {
|
|
138
|
-
return {
|
|
139
|
-
storedValue: await streamToArrayBuffer(value),
|
|
140
|
-
valueType: "stream",
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Response -> ArrayBuffer + headers/status
|
|
145
|
-
if (value instanceof Response) {
|
|
146
|
-
const headers: Record<string, string> = {};
|
|
147
|
-
value.headers.forEach((v, k) => {
|
|
148
|
-
headers[k] = v;
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
storedValue: await value.clone().arrayBuffer(),
|
|
153
|
-
valueType: "response",
|
|
154
|
-
responseHeaders: headers,
|
|
155
|
-
responseStatus: value.status,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// ArrayBuffer -> store as-is
|
|
160
|
-
if (value instanceof ArrayBuffer) {
|
|
161
|
-
return {
|
|
162
|
-
storedValue: value,
|
|
163
|
-
valueType: "arraybuffer",
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// String -> store as-is
|
|
168
|
-
if (typeof value === "string") {
|
|
169
|
-
return {
|
|
170
|
-
storedValue: value,
|
|
171
|
-
valueType: "string",
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Object -> store as-is (JSON-serializable)
|
|
176
|
-
return {
|
|
177
|
-
storedValue: value,
|
|
178
|
-
valueType: "object",
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Reconstruct original value type from stored entry
|
|
184
|
-
*/
|
|
185
|
-
private reconstructValue(entry: StoredEntry): CacheValue {
|
|
186
|
-
const { value, metadata } = entry;
|
|
187
|
-
|
|
188
|
-
switch (metadata.valueType) {
|
|
189
|
-
case "stream":
|
|
190
|
-
return arrayBufferToStream(value as ArrayBuffer);
|
|
191
|
-
|
|
192
|
-
case "response": {
|
|
193
|
-
const status = metadata.responseStatus ?? 200;
|
|
194
|
-
// Status codes 204 (No Content) and 304 (Not Modified) cannot have a body
|
|
195
|
-
const isNullBodyStatus = status === 204 || status === 304;
|
|
196
|
-
return new Response(isNullBodyStatus ? null : (value as ArrayBuffer), {
|
|
197
|
-
status,
|
|
198
|
-
headers: metadata.responseHeaders,
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
case "arraybuffer":
|
|
203
|
-
case "string":
|
|
204
|
-
case "object":
|
|
205
|
-
default:
|
|
206
|
-
return value as CacheValue;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Convert a ReadableStream to ArrayBuffer.
|
|
213
|
-
* @internal
|
|
214
|
-
*/
|
|
215
|
-
async function streamToArrayBuffer(
|
|
216
|
-
stream: ReadableStream<Uint8Array>
|
|
217
|
-
): Promise<ArrayBuffer> {
|
|
218
|
-
const chunks: Uint8Array[] = [];
|
|
219
|
-
const reader = stream.getReader();
|
|
220
|
-
|
|
221
|
-
while (true) {
|
|
222
|
-
const { done, value } = await reader.read();
|
|
223
|
-
if (done) break;
|
|
224
|
-
chunks.push(value);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Concatenate chunks
|
|
228
|
-
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
229
|
-
const result = new Uint8Array(totalLength);
|
|
230
|
-
let offset = 0;
|
|
231
|
-
|
|
232
|
-
for (const chunk of chunks) {
|
|
233
|
-
result.set(chunk, offset);
|
|
234
|
-
offset += chunk.length;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return result.buffer;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Convert an ArrayBuffer to a ReadableStream.
|
|
242
|
-
* @internal
|
|
243
|
-
*/
|
|
244
|
-
function arrayBufferToStream(buffer: ArrayBuffer): ReadableStream<Uint8Array> {
|
|
245
|
-
const uint8 = new Uint8Array(buffer);
|
|
246
|
-
|
|
247
|
-
return new ReadableStream({
|
|
248
|
-
start(controller) {
|
|
249
|
-
controller.enqueue(uint8);
|
|
250
|
-
controller.close();
|
|
251
|
-
},
|
|
252
|
-
});
|
|
253
|
-
}
|
package/src/href-context.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Href Context for route name resolution
|
|
5
|
-
*
|
|
6
|
-
* This module is marked "use client" so it can be imported by both:
|
|
7
|
-
* - Server (segment-system): Gets a client reference for createElement
|
|
8
|
-
* - Client (useHref): Uses the actual context for useContext
|
|
9
|
-
*
|
|
10
|
-
* The context stores:
|
|
11
|
-
* - routeMap: Map of route names to URL patterns
|
|
12
|
-
* - routeName: Current matched route name (for local name resolution)
|
|
13
|
-
*/
|
|
14
|
-
import { createContext, type Context } from "react";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Context value for href resolution
|
|
18
|
-
*/
|
|
19
|
-
export interface HrefContextValue {
|
|
20
|
-
/** Route map: route name -> URL pattern */
|
|
21
|
-
routeMap: Record<string, string>;
|
|
22
|
-
/** Current matched route name (includes name prefix from include()) */
|
|
23
|
-
routeName?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Context for href resolution (route map and current route name)
|
|
28
|
-
*
|
|
29
|
-
* On the server: Populated by renderSegments() via HrefContext.Provider
|
|
30
|
-
* On the client: Populated by NavigationProvider from RSC metadata
|
|
31
|
-
*/
|
|
32
|
-
export const HrefContext: Context<HrefContextValue | null> =
|
|
33
|
-
createContext<HrefContextValue | null>(null);
|
package/src/href.ts
DELETED
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
import type { ExtractParams } from "./types.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Sanitize prefix string by removing leading slash
|
|
5
|
-
* "/shop" -> "shop", "blog" -> "blog", "" -> ""
|
|
6
|
-
*/
|
|
7
|
-
export type SanitizePrefix<T extends string> = T extends `/${infer P}` ? P : T;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Helper type to merge multiple route definitions into a single accumulated type.
|
|
11
|
-
* Note: When using createRouter, types accumulate automatically through the
|
|
12
|
-
* builder chain, so this type is typically not needed.
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```typescript
|
|
16
|
-
* // Manual type merging (rarely needed):
|
|
17
|
-
* type AppRoutes = MergeRoutes<[
|
|
18
|
-
* typeof homeRoutes,
|
|
19
|
-
* PrefixRoutePatterns<typeof blogRoutes, "/blog">,
|
|
20
|
-
* ]>;
|
|
21
|
-
*
|
|
22
|
-
* // Preferred: Let router accumulate types automatically
|
|
23
|
-
* const router = createRouter<AppEnv>()
|
|
24
|
-
* .routes(homeRoutes).map(...)
|
|
25
|
-
* .routes("/blog", blogRoutes).map(...);
|
|
26
|
-
* type AppRoutes = typeof router.routeMap;
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
export type MergeRoutes<T extends unknown[]> = T extends [
|
|
30
|
-
infer First,
|
|
31
|
-
...infer Rest
|
|
32
|
-
]
|
|
33
|
-
? First & MergeRoutes<Rest>
|
|
34
|
-
: {};
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Add key prefix to all entries in a route map
|
|
38
|
-
* { "cart": "/cart" } with prefix "shop" -> { "shop.cart": "/shop/cart" }
|
|
39
|
-
*/
|
|
40
|
-
export type PrefixRouteKeys<
|
|
41
|
-
T,
|
|
42
|
-
Prefix extends string
|
|
43
|
-
> = Prefix extends ""
|
|
44
|
-
? T
|
|
45
|
-
: { [K in keyof T as `${Prefix}.${K & string}`]: T[K] };
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Add path prefix to all patterns in a route map
|
|
49
|
-
* { "cart": "/cart" } with prefix "/shop" -> { "cart": "/shop/cart" }
|
|
50
|
-
*/
|
|
51
|
-
export type PrefixRoutePatterns<
|
|
52
|
-
T,
|
|
53
|
-
PathPrefix extends string
|
|
54
|
-
> = {
|
|
55
|
-
[K in keyof T]: PathPrefix extends "" | "/"
|
|
56
|
-
? T[K]
|
|
57
|
-
: T[K] extends "/"
|
|
58
|
-
? PathPrefix
|
|
59
|
-
: T[K] extends string
|
|
60
|
-
? `${PathPrefix}${T[K]}`
|
|
61
|
-
: T[K];
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Combined: prefix both keys and patterns
|
|
66
|
-
* Used for module augmentation registration
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* ```typescript
|
|
70
|
-
* // Given shopRoutes = { "index": "/", "cart": "/cart", "products.detail": "/product/:slug" }
|
|
71
|
-
* // PrefixedRoutes<typeof shopRoutes, "shop"> produces:
|
|
72
|
-
* // { "shop.index": "/shop", "shop.cart": "/shop/cart", "shop.products.detail": "/shop/product/:slug" }
|
|
73
|
-
* ```
|
|
74
|
-
*/
|
|
75
|
-
export type PrefixedRoutes<
|
|
76
|
-
T,
|
|
77
|
-
KeyPrefix extends string,
|
|
78
|
-
PathPrefix extends string = KeyPrefix extends "" ? "" : `/${KeyPrefix}`
|
|
79
|
-
> = PrefixRouteKeys<PrefixRoutePatterns<T, PathPrefix>, KeyPrefix>;
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Helper to safely extract route patterns from a routes object
|
|
83
|
-
* Handles both Record<string, string> and interface types (like RegisteredRoutes)
|
|
84
|
-
*/
|
|
85
|
-
type RoutePatternFor<TRoutes, TName extends keyof TRoutes> =
|
|
86
|
-
TRoutes[TName] extends string ? TRoutes[TName] : string;
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Extract params type for a route
|
|
90
|
-
*/
|
|
91
|
-
export type ParamsFor<
|
|
92
|
-
TRoutes,
|
|
93
|
-
TName extends keyof TRoutes
|
|
94
|
-
> = ExtractParams<RoutePatternFor<TRoutes, TName>>;
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Check if an object type has any keys
|
|
98
|
-
*/
|
|
99
|
-
type IsEmptyObject<T> = keyof T extends never ? true : false;
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Type-safe href function signature
|
|
103
|
-
*
|
|
104
|
-
* Validates route names and params at compile time.
|
|
105
|
-
* Use route names instead of raw paths for full type safety.
|
|
106
|
-
*
|
|
107
|
-
* @example
|
|
108
|
-
* ```typescript
|
|
109
|
-
* href("cart") // ✓ Validates route exists
|
|
110
|
-
* href("product.detail", { id: "123" }) // ✓ Validates route + params
|
|
111
|
-
* ```
|
|
112
|
-
*/
|
|
113
|
-
export type HrefFunction<TRoutes> = {
|
|
114
|
-
/**
|
|
115
|
-
* Route without params - validates route name exists
|
|
116
|
-
*/
|
|
117
|
-
<TName extends keyof TRoutes & string>(
|
|
118
|
-
name: IsEmptyObject<ExtractParams<RoutePatternFor<TRoutes, TName>>> extends true ? TName : never
|
|
119
|
-
): string;
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Route with params - validates both route name and params
|
|
123
|
-
*/
|
|
124
|
-
<TName extends keyof TRoutes & string>(
|
|
125
|
-
name: TName,
|
|
126
|
-
params: ExtractParams<RoutePatternFor<TRoutes, TName>>
|
|
127
|
-
): string;
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Type-safe scoped href function signature for use with scopedHref<typeof patterns>()
|
|
132
|
-
*
|
|
133
|
-
* **Recommended: Use route names for type safety.**
|
|
134
|
-
* Route names validate both the route exists and params are correct.
|
|
135
|
-
* Path-based URLs (`/...`) are an escape hatch with no validation.
|
|
136
|
-
*
|
|
137
|
-
* @example
|
|
138
|
-
* ```typescript
|
|
139
|
-
* // RECOMMENDED: Use route names for type safety
|
|
140
|
-
* href("blog.post", { slug: "hello" }) // ✓ Validates route + params
|
|
141
|
-
* href("shop.cart") // ✓ Validates route exists
|
|
142
|
-
*
|
|
143
|
-
* // ESCAPE HATCH: Path-based URLs (no validation)
|
|
144
|
-
* href("/about") // ⚠ No type checking
|
|
145
|
-
* href("/typo/in/path") // ⚠ Won't catch errors
|
|
146
|
-
* ```
|
|
147
|
-
*/
|
|
148
|
-
export type ScopedHrefFunction<TLocalRoutes> = {
|
|
149
|
-
/**
|
|
150
|
-
* Route without params - validates route name exists
|
|
151
|
-
* @recommended Use this for type-safe URL generation
|
|
152
|
-
*/
|
|
153
|
-
<TName extends keyof TLocalRoutes & string>(
|
|
154
|
-
name: IsEmptyObject<ExtractParams<RoutePatternFor<TLocalRoutes, TName>>> extends true ? TName : never
|
|
155
|
-
): string;
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Route with params - validates both route name and params
|
|
159
|
-
* @recommended Use this for type-safe URL generation with parameters
|
|
160
|
-
*/
|
|
161
|
-
<TName extends keyof TLocalRoutes & string>(
|
|
162
|
-
name: TName,
|
|
163
|
-
params: ExtractParams<RoutePatternFor<TLocalRoutes, TName>>
|
|
164
|
-
): string;
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Absolute route name (contains dot) - global lookup
|
|
168
|
-
* Use for cross-module navigation: "shop.cart", "blog.post"
|
|
169
|
-
*/
|
|
170
|
-
(name: `${string}.${string}`, params?: Record<string, string>): string;
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Path-based URL - ESCAPE HATCH, no type validation
|
|
174
|
-
* Prefer route names for type safety. Only use paths when necessary.
|
|
175
|
-
*/
|
|
176
|
-
(name: `/${string}`, params?: Record<string, string>): string;
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Extract local routes type from UrlPatterns
|
|
181
|
-
* Used with scopedHref() to get the routes type from patterns
|
|
182
|
-
*/
|
|
183
|
-
export type ExtractLocalRoutes<TPatterns> =
|
|
184
|
-
TPatterns extends { readonly _routes?: infer TRoutes } ? TRoutes : Record<string, string>;
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Get a locally-typed href function from ctx.href for composable modules.
|
|
188
|
-
*
|
|
189
|
-
* This is a type-only cast - ctx.href already resolves local names at runtime
|
|
190
|
-
* based on the current route prefix. This helper just provides type safety
|
|
191
|
-
* for local route names within a url module.
|
|
192
|
-
*
|
|
193
|
-
* @param href - The ctx.href function from HandlerContext
|
|
194
|
-
* @returns The same href function, but typed for local routes
|
|
195
|
-
*
|
|
196
|
-
* @example
|
|
197
|
-
* ```typescript
|
|
198
|
-
* // urls/blog.tsx
|
|
199
|
-
* export const blogPatterns = urls(({ path }) => [
|
|
200
|
-
* path("/", (ctx) => {
|
|
201
|
-
* // Get locally-typed href for this module's routes
|
|
202
|
-
* const href = scopedHref<typeof blogPatterns>(ctx.href);
|
|
203
|
-
*
|
|
204
|
-
* href("index"); // ✓ Type-safe local route
|
|
205
|
-
* href("post", { slug: "x" }); // ✓ Type-safe with params
|
|
206
|
-
* href("shop.cart"); // ✓ Cross-module (absolute name)
|
|
207
|
-
*
|
|
208
|
-
* return <BlogIndex />;
|
|
209
|
-
* }, { name: "index" }),
|
|
210
|
-
*
|
|
211
|
-
* path("/:slug", BlogPost, { name: "post" }),
|
|
212
|
-
* ]);
|
|
213
|
-
* ```
|
|
214
|
-
*/
|
|
215
|
-
export function scopedHref<TPatterns>(
|
|
216
|
-
href: (name: string, params?: Record<string, string>) => string
|
|
217
|
-
): ScopedHrefFunction<ExtractLocalRoutes<TPatterns>> {
|
|
218
|
-
return href as ScopedHrefFunction<ExtractLocalRoutes<TPatterns>>;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Create a type-safe href function for URL generation
|
|
223
|
-
*
|
|
224
|
-
* @param routeMap - Flattened route map with all registered routes
|
|
225
|
-
* @returns Type-safe href function
|
|
226
|
-
*
|
|
227
|
-
* @example
|
|
228
|
-
* ```typescript
|
|
229
|
-
* // Given routes: { cart: "/shop/cart", detail: "/shop/product/:slug" }
|
|
230
|
-
* const href = createHref(routeMap);
|
|
231
|
-
* href("cart"); // "/shop/cart"
|
|
232
|
-
* href("detail", { slug: "my-product" }); // "/shop/product/my-product"
|
|
233
|
-
* ```
|
|
234
|
-
*/
|
|
235
|
-
export function createHref<TRoutes extends Record<string, string>>(
|
|
236
|
-
routeMap: TRoutes
|
|
237
|
-
): HrefFunction<TRoutes & Record<string, string>> {
|
|
238
|
-
return ((name: string, params?: Record<string, string>) => {
|
|
239
|
-
const pattern = routeMap[name];
|
|
240
|
-
if (!pattern) {
|
|
241
|
-
throw new Error(`Unknown route: ${name}`);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (!params) return pattern;
|
|
245
|
-
|
|
246
|
-
// Replace :param placeholders with actual values
|
|
247
|
-
return pattern.replace(/:([^/]+)/g, (_, key) => {
|
|
248
|
-
const value = params[key];
|
|
249
|
-
if (value === undefined) {
|
|
250
|
-
throw new Error(`Missing param "${key}" for route "${name}"`);
|
|
251
|
-
}
|
|
252
|
-
return encodeURIComponent(value);
|
|
253
|
-
});
|
|
254
|
-
}) as HrefFunction<TRoutes>;
|
|
255
|
-
}
|