@rangojs/router 0.0.0-experimental.9 → 0.0.0-experimental.a5f27bd5
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 +5 -0
- package/README.md +884 -4
- package/dist/bin/rango.js +1531 -155
- package/dist/vite/index.js +4440 -2170
- package/package.json +60 -54
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +50 -21
- 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 +6 -4
- package/skills/hooks/SKILL.md +333 -71
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +131 -8
- package/skills/layout/SKILL.md +100 -3
- package/skills/links/SKILL.md +74 -15
- package/skills/loader/SKILL.md +388 -38
- package/skills/middleware/SKILL.md +171 -34
- package/skills/mime-routes/SKILL.md +15 -11
- package/skills/parallel/SKILL.md +78 -1
- package/skills/prerender/SKILL.md +405 -45
- package/skills/rango/SKILL.md +85 -21
- package/skills/response-routes/SKILL.md +144 -91
- package/skills/route/SKILL.md +226 -14
- package/skills/router-setup/SKILL.md +123 -30
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +316 -87
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +87 -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 +285 -553
- package/src/browser/navigation-client.ts +123 -73
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +261 -309
- package/src/browser/prefetch/cache.ts +154 -0
- package/src/browser/prefetch/fetch.ts +135 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +88 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +182 -70
- package/src/browser/react/NavigationProvider.tsx +51 -11
- package/src/browser/react/context.ts +6 -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 +29 -70
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +22 -63
- 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 +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +106 -27
- package/src/browser/scroll-restoration.ts +92 -16
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +504 -599
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +107 -47
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +82 -21
- package/src/build/generate-route-types.ts +36 -752
- package/src/build/index.ts +6 -5
- package/src/build/route-trie.ts +39 -13
- 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 +469 -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 +120 -301
- package/src/cache/cf/cf-cache-store.ts +119 -7
- package/src/cache/cf/index.ts +8 -2
- package/src/cache/document-cache.ts +101 -72
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +0 -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 +98 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +3 -1
- package/src/client.tsx +84 -126
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +86 -0
- package/src/debug.ts +17 -7
- package/src/errors.ts +77 -7
- package/src/handle.ts +15 -10
- 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 +65 -45
- package/src/index.rsc.ts +133 -21
- package/src/index.ts +164 -52
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +25 -143
- 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 +4 -2
- package/src/prerender/store.ts +158 -13
- package/src/prerender.ts +333 -26
- package/src/reverse.ts +184 -121
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- 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 -1431
- package/src/route-map-builder.ts +156 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +48 -9
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +158 -0
- package/src/router/handler-context.ts +374 -81
- package/src/router/intercept-resolution.ts +24 -16
- package/src/router/lazy-includes.ts +234 -0
- package/src/router/loader-resolution.ts +215 -122
- package/src/router/logging.ts +248 -0
- package/src/router/manifest.ts +83 -32
- package/src/router/match-api.ts +118 -119
- package/src/router/match-context.ts +4 -2
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +80 -93
- package/src/router/match-middleware/cache-lookup.ts +336 -84
- package/src/router/match-middleware/cache-store.ts +43 -24
- package/src/router/match-middleware/intercept-resolution.ts +45 -20
- package/src/router/match-middleware/segment-resolution.ts +16 -8
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +34 -28
- package/src/router/metrics.ts +235 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +324 -367
- package/src/router/pattern-matching.ts +197 -41
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/router-context.ts +36 -21
- 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 +1239 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -1315
- package/src/router/segment-wrappers.ts +289 -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 +96 -29
- package/src/router/types.ts +16 -9
- package/src/router.ts +590 -1983
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +661 -1015
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +14 -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 +38 -11
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +25 -13
- package/src/server/context.ts +173 -48
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +430 -70
- package/src/server.ts +35 -155
- package/src/ssr/index.tsx +100 -31
- package/src/static-handler.ts +114 -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 +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 +102 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -1757
- 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 -1282
- package/src/use-loader.tsx +85 -77
- 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 +110 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +11 -1963
- package/src/vite/plugin-types.ts +131 -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 -51
- 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 +254 -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 +510 -0
- package/src/vite/router-discovery.ts +785 -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 +189 -0
- package/src/vite/utils/shared-utils.ts +169 -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/router.gen.ts +0 -6
- package/src/urls.gen.ts +0 -8
- 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/expose-prerender-handler-id.ts +0 -429
- /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/router.gen.ts
DELETED
package/src/urls.gen.ts
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import type { Plugin, ResolvedConfig } from "vite";
|
|
2
|
-
import MagicString from "magic-string";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import crypto from "node:crypto";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Normalize path to forward slashes
|
|
8
|
-
*/
|
|
9
|
-
function normalizePath(p: string): string {
|
|
10
|
-
return p.split(path.sep).join("/");
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Generate a short hash for a handle ID
|
|
15
|
-
* Uses first 8 chars of SHA-256 hash for uniqueness while keeping IDs short
|
|
16
|
-
* Appends export name for easier debugging: "abc123#Breadcrumbs"
|
|
17
|
-
*/
|
|
18
|
-
function hashHandleId(filePath: string, exportName: string): string {
|
|
19
|
-
const input = `${filePath}#${exportName}`;
|
|
20
|
-
const hash = crypto.createHash("sha256").update(input).digest("hex");
|
|
21
|
-
return `${hash.slice(0, 8)}#${exportName}`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Check if file imports createHandle from rsc-router
|
|
26
|
-
*/
|
|
27
|
-
function hasCreateHandleImport(code: string): boolean {
|
|
28
|
-
// Match: import { createHandle } from "@rangojs/router" or "@rangojs/router/..."
|
|
29
|
-
const pattern =
|
|
30
|
-
/import\s*\{[^}]*\bcreateHandle\b[^}]*\}\s*from\s*["']@rangojs\/router(?:\/[^"']+)?["']/;
|
|
31
|
-
return pattern.test(code);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Analyze createHandle arguments to determine injection strategy
|
|
36
|
-
* Returns: { hasArgs: boolean, firstArgIsString: boolean, firstArgIsFunction: boolean }
|
|
37
|
-
*/
|
|
38
|
-
function analyzeCreateHandleArgs(
|
|
39
|
-
code: string,
|
|
40
|
-
startPos: number,
|
|
41
|
-
endPos: number
|
|
42
|
-
): { hasArgs: boolean; firstArgIsString: boolean; firstArgIsFunction: boolean } {
|
|
43
|
-
// Extract the content between parentheses
|
|
44
|
-
const content = code.slice(startPos, endPos).trim();
|
|
45
|
-
|
|
46
|
-
if (!content) {
|
|
47
|
-
return { hasArgs: false, firstArgIsString: false, firstArgIsFunction: false };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Check if first arg starts with a quote (string literal)
|
|
51
|
-
const firstArgIsString = /^["']/.test(content);
|
|
52
|
-
|
|
53
|
-
// Check if first arg starts with ( for arrow function or function keyword
|
|
54
|
-
const firstArgIsFunction =
|
|
55
|
-
content.startsWith("(") ||
|
|
56
|
-
content.startsWith("function") ||
|
|
57
|
-
// Check for identifier that could be a collect function reference
|
|
58
|
-
/^[a-zA-Z_$][a-zA-Z0-9_$]*\s*(?:,|$)/.test(content);
|
|
59
|
-
|
|
60
|
-
return { hasArgs: true, firstArgIsString, firstArgIsFunction };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Transform export const X = createHandle(...) patterns to inject $$id
|
|
65
|
-
*
|
|
66
|
-
* Handles these cases:
|
|
67
|
-
* 1. createHandle() - no args -> inject (undefined, "id")
|
|
68
|
-
* 2. createHandle("name") - string name -> inject (, "id") after existing arg
|
|
69
|
-
* 3. createHandle(collectFn) - collect function -> inject (collectFn, "id")
|
|
70
|
-
* 4. createHandle("name", collectFn) - both -> inject (, "id") after existing args
|
|
71
|
-
*/
|
|
72
|
-
function transformHandleExports(
|
|
73
|
-
code: string,
|
|
74
|
-
filePath: string,
|
|
75
|
-
sourceId?: string,
|
|
76
|
-
isBuild: boolean = false
|
|
77
|
-
): { code: string; map: ReturnType<MagicString["generateMap"]> } | null {
|
|
78
|
-
// Quick bail-out
|
|
79
|
-
if (!code.includes("createHandle")) {
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Must have direct import from rsc-router
|
|
84
|
-
if (!hasCreateHandleImport(code)) {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Match: export const X = createHandle<...>(
|
|
89
|
-
// Captures the export name (X)
|
|
90
|
-
const pattern = /export\s+const\s+(\w+)\s*=\s*createHandle\s*(?:<[^>]*>)?\s*\(/g;
|
|
91
|
-
|
|
92
|
-
const s = new MagicString(code);
|
|
93
|
-
let hasChanges = false;
|
|
94
|
-
let match: RegExpExecArray | null;
|
|
95
|
-
|
|
96
|
-
while ((match = pattern.exec(code)) !== null) {
|
|
97
|
-
const exportName = match[1];
|
|
98
|
-
const matchEnd = match.index + match[0].length;
|
|
99
|
-
|
|
100
|
-
// Find the end of the createHandle(...) call
|
|
101
|
-
let parenDepth = 1;
|
|
102
|
-
let i = matchEnd;
|
|
103
|
-
while (i < code.length && parenDepth > 0) {
|
|
104
|
-
if (code[i] === "(") parenDepth++;
|
|
105
|
-
if (code[i] === ")") parenDepth--;
|
|
106
|
-
i++;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// i now points just after the closing )
|
|
110
|
-
const closeParenPos = i - 1;
|
|
111
|
-
|
|
112
|
-
// Analyze what arguments exist
|
|
113
|
-
const args = analyzeCreateHandleArgs(code, matchEnd, closeParenPos);
|
|
114
|
-
|
|
115
|
-
// Find the semicolon or end of statement
|
|
116
|
-
let statementEnd = i;
|
|
117
|
-
while (statementEnd < code.length && /\s/.test(code[statementEnd])) {
|
|
118
|
-
statementEnd++;
|
|
119
|
-
}
|
|
120
|
-
if (code[statementEnd] === ";") {
|
|
121
|
-
statementEnd++;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Generate ID: hashed in production, readable in dev
|
|
125
|
-
const handleId = isBuild
|
|
126
|
-
? hashHandleId(filePath, exportName)
|
|
127
|
-
: `${filePath}#${exportName}`;
|
|
128
|
-
|
|
129
|
-
// Inject $$id as the last parameter
|
|
130
|
-
let paramInjection: string;
|
|
131
|
-
if (!args.hasArgs) {
|
|
132
|
-
// No args: createHandle() -> createHandle(undefined, "id")
|
|
133
|
-
paramInjection = `undefined, "${handleId}"`;
|
|
134
|
-
} else {
|
|
135
|
-
// Has args: createHandle(x) -> createHandle(x, "id")
|
|
136
|
-
paramInjection = `, "${handleId}"`;
|
|
137
|
-
}
|
|
138
|
-
s.appendLeft(closeParenPos, paramInjection);
|
|
139
|
-
|
|
140
|
-
// Also set $$id property for external access
|
|
141
|
-
const propInjection = `\n${exportName}.$$id = "${handleId}";`;
|
|
142
|
-
s.appendRight(statementEnd, propInjection);
|
|
143
|
-
hasChanges = true;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (!hasChanges) {
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return {
|
|
151
|
-
code: s.toString(),
|
|
152
|
-
map: s.generateMap({ source: sourceId, includeContent: true }),
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Vite plugin that exposes $$id on createHandle calls.
|
|
158
|
-
*
|
|
159
|
-
* When users create handles with createHandle(), this plugin:
|
|
160
|
-
* 1. Injects a $$id as the last parameter (used as the handle name)
|
|
161
|
-
* 2. Sets $$id property on the exported constant for external access
|
|
162
|
-
*
|
|
163
|
-
* This allows handles to be created without explicit names:
|
|
164
|
-
* - Before: export const Breadcrumbs = createHandle<Item>("breadcrumbs")
|
|
165
|
-
* - After: export const Breadcrumbs = createHandle<Item>()
|
|
166
|
-
*
|
|
167
|
-
* The name is auto-generated from file path + export name.
|
|
168
|
-
*
|
|
169
|
-
* Requirements:
|
|
170
|
-
* - Must use direct import: import { createHandle } from "@rangojs/router"
|
|
171
|
-
* - Must use named export: export const MyHandle = createHandle(...)
|
|
172
|
-
*/
|
|
173
|
-
export function exposeHandleId(): Plugin {
|
|
174
|
-
let config: ResolvedConfig;
|
|
175
|
-
let isBuild = false;
|
|
176
|
-
|
|
177
|
-
return {
|
|
178
|
-
name: "@rangojs/router:expose-handle-id",
|
|
179
|
-
enforce: "post",
|
|
180
|
-
|
|
181
|
-
configResolved(resolvedConfig) {
|
|
182
|
-
config = resolvedConfig;
|
|
183
|
-
isBuild = config.command === "build";
|
|
184
|
-
},
|
|
185
|
-
|
|
186
|
-
transform(code, id) {
|
|
187
|
-
// Skip node_modules
|
|
188
|
-
if (id.includes("/node_modules/")) {
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Quick bail-out
|
|
193
|
-
if (!code.includes("createHandle")) {
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Must have direct import from rsc-router
|
|
198
|
-
if (!hasCreateHandleImport(code)) {
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Get relative path for the ID
|
|
203
|
-
const relativePath = normalizePath(path.relative(config.root, id));
|
|
204
|
-
|
|
205
|
-
// Transform: inject $$id
|
|
206
|
-
return transformHandleExports(code, relativePath, id, isBuild);
|
|
207
|
-
},
|
|
208
|
-
};
|
|
209
|
-
}
|